Compare commits

...

7 Commits
3.8.0 ... main

24 changed files with 469 additions and 312 deletions

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.CustomInteractions.Prepatch</AssemblyName> <AssemblyName>IcyClawz.CustomInteractions.Prepatch</AssemblyName>
<Version>1.3.1</Version> <Version>1.6.0</Version>
<RootNamespace>IcyClawz.CustomInteractions</RootNamespace> <RootNamespace>IcyClawz.CustomInteractions</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>

View File

@ -10,8 +10,7 @@ public static class Prepatch
public static void Patch(AssemblyDefinition assembly) public static void Patch(AssemblyDefinition assembly)
{ {
TypeDefinition type = assembly.MainModule.GetType("GClass2816"); // DynamicInteraction TypeDefinition type = assembly.MainModule.GetType("DynamicInteractionClass");
type.IsSealed = false;
FieldDefinition field = type.Fields.SingleOrDefault(c => c.Name is "action_0"); FieldDefinition field = type.Fields.SingleOrDefault(c => c.Name is "action_0");
field.IsFamily = true; field.IsFamily = true;
field.IsInitOnly = false; field.IsInitOnly = false;

View File

@ -1,24 +1,20 @@
using Comfort.Common;
using EFT.InventoryLogic; using EFT.InventoryLogic;
using EFT.UI; using EFT.UI;
using JetBrains.Annotations; using JetBrains.Annotations;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
using DynamicInteraction = GClass2816; using EmptyInteractionsAbstractClass = GClass3423;
using EmptyInteractions = GClass2817<System.Enum>;
namespace IcyClawz.CustomInteractions; namespace IcyClawz.CustomInteractions;
[EditorBrowsable(EditorBrowsableState.Never)] public interface ICustomInteractionsProvider
public interface ICustomInteractionsProvider { } // Do not implement this directly
public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider
{ {
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item); IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext context, EItemViewType viewType, Item item);
} }
public static class CustomInteractionsManager public static class CustomInteractionsManager
@ -32,72 +28,46 @@ public static class CustomInteractionsManager
} }
} }
public class CustomInteraction() public class CustomInteraction(ItemUiContext context)
{ {
internal readonly CustomInteractionImpl Impl = new(); internal readonly CustomInteractionImpl Impl = new(context, UnityEngine.Random.Range(0, int.MaxValue).ToString("x4"));
public Func<string> Caption { get => Impl.Caption; set => Impl.Caption = value; } public Func<string> Caption { get => Impl.Caption; set => Impl.Caption = value; }
public Func<Sprite> Icon { get => Impl.Icon; set => Impl.Icon = value; } public Func<Sprite> Icon { get => Impl.Icon; set => Impl.Icon = value; }
public Action Action { get => Impl.Action; set => Impl.Action = value; } public Action Action { get => Impl.Action; set => Impl.Action = value; }
public Func<CustomSubInteractions> SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; } public Func<IEnumerable<CustomInteraction>> SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; }
public Func<bool> Enabled { get => Impl.Enabled; set => Impl.Enabled = value; } public Func<bool> Enabled { get => Impl.Enabled; set => Impl.Enabled = value; }
public Func<string> Error { get => Impl.Error; set => Impl.Error = value; } public Func<string> Error { get => Impl.Error; set => Impl.Error = value; }
} }
internal sealed class CustomInteractionImpl() internal sealed class CustomInteractionImpl(ItemUiContext context, string id) : DynamicInteractionClass(id, id)
: DynamicInteraction(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4"))
{ {
internal readonly ItemUiContext Context = context;
public Func<string> Caption { get; set; } public Func<string> Caption { get; set; }
public new Func<Sprite> Icon { get; set; } public new Func<Sprite> Icon { get; set; }
public Action Action { get => action_0; set => action_0 = value; } public Action Action { get => action_0; set => action_0 = value; }
public Func<CustomSubInteractions> SubMenu { get; set; } public Func<IEnumerable<CustomInteraction>> SubMenu { get; set; }
public Func<bool> Enabled { get; set; } public Func<bool> Enabled { get; set; }
public Func<string> Error { get; set; } public Func<string> Error { get; set; }
public bool IsInteractive() => Enabled?.Invoke() ?? true; public bool IsInteractive() => Enabled?.Invoke() ?? true;
} }
public abstract class CustomSubInteractions(ItemUiContext uiContext) internal sealed class CustomInteractionsImpl(ItemUiContext context) : EmptyInteractionsAbstractClass(context)
{
internal readonly CustomSubInteractionsImpl Impl = new(uiContext);
public bool ExaminationRequired
{
get => Impl.ExaminationRequiredInternal;
set => Impl.ExaminationRequiredInternal = value;
}
public void Add(CustomInteraction interaction) => Impl.AddCustomInteraction(interaction);
public void AddRange(IEnumerable<CustomInteraction> interactions) => interactions.ExecuteForEach(Impl.AddCustomInteraction);
public void Remove(CustomInteraction interaction) => Impl.RemoveCustomInteraction(interaction);
public void RemoveRange(IEnumerable<CustomInteraction> interactions) => interactions.ExecuteForEach(Impl.RemoveCustomInteraction);
public void CallRedraw() => Impl.CallRedraw();
public void CallRedraw(string templateId) => Impl.CallRedraw(templateId);
}
internal sealed class CustomSubInteractionsImpl(ItemUiContext uiContext)
: EmptyInteractions(uiContext)
{ {
public IEnumerable<CustomInteractionImpl> CustomInteractions => DynamicInteractions.OfType<CustomInteractionImpl>(); public IEnumerable<CustomInteractionImpl> CustomInteractions => DynamicInteractions.OfType<CustomInteractionImpl>();
public bool ExaminationRequiredInternal { get; set; } = true;
public override bool ExaminationRequired => ExaminationRequiredInternal;
public override bool HasIcons => CustomInteractions.Any(interaction => interaction.Icon is not null); public override bool HasIcons => CustomInteractions.Any(interaction => interaction.Icon is not null);
public void CallRedraw() => itemUiContext_0.RedrawContextMenus(null);
} }
internal static class AbstractInteractionsExtensions internal static class AbstractInteractionsExtensions
{ {
private static Dictionary<string, DynamicInteraction> GetDynamicInteractions<T>(this GClass2817<T> instance) where T : Enum => private static Dictionary<string, DynamicInteractionClass> GetDynamicInteractions<T>(this ItemInfoInteractionsAbstractClass<T> instance) where T : struct, Enum =>
typeof(GClass2817<T>).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance) typeof(ItemInfoInteractionsAbstractClass<T>).GetField("dictionary_0", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(instance) as Dictionary<string, DynamicInteraction>; .GetValue(instance) as Dictionary<string, DynamicInteractionClass>;
public static void AddCustomInteraction<T>(this GClass2817<T> instance, CustomInteraction interaction) where T : Enum => public static void AddCustomInteraction<T>(this ItemInfoInteractionsAbstractClass<T> instance, CustomInteractionImpl impl) where T : struct, Enum =>
instance.GetDynamicInteractions()[interaction.Impl.Key] = interaction.Impl; instance.GetDynamicInteractions()[impl.Key] = impl;
public static void RemoveCustomInteraction<T>(this GClass2817<T> instance, CustomInteraction interaction) where T : Enum =>
instance.GetDynamicInteractions().Remove(interaction.Impl.Key);
} }
internal static class InteractionButtonsContainerExtensions internal static class InteractionButtonsContainerExtensions
@ -121,7 +91,7 @@ internal static class InteractionButtonsContainerExtensions
CurrentButtonField.SetValue(instance, button); CurrentButtonField.SetValue(instance, button);
private static readonly MethodInfo CreateButtonMethod = private static readonly MethodInfo CreateButtonMethod =
typeof(InteractionButtonsContainer).GetMethod("method_1", BindingFlags.NonPublic | BindingFlags.Instance); typeof(InteractionButtonsContainer).GetMethod("method_1", BindingFlags.Public | BindingFlags.Instance);
private static SimpleContextMenuButton CreateButton(this InteractionButtonsContainer instance, private static SimpleContextMenuButton CreateButton(this InteractionButtonsContainer instance,
string key, string caption, SimpleContextMenuButton template, RectTransform container, string key, string caption, SimpleContextMenuButton template, RectTransform container,
@ -132,13 +102,13 @@ internal static class InteractionButtonsContainerExtensions
]); ]);
private static readonly MethodInfo CloseSubMenuMethod = private static readonly MethodInfo CloseSubMenuMethod =
typeof(InteractionButtonsContainer).GetMethod("method_4", BindingFlags.NonPublic | BindingFlags.Instance); typeof(InteractionButtonsContainer).GetMethod("method_4", BindingFlags.Public | BindingFlags.Instance);
private static void CloseSubMenu(this InteractionButtonsContainer instance) => private static void CloseSubMenu(this InteractionButtonsContainer instance) =>
CloseSubMenuMethod.Invoke(instance, null); CloseSubMenuMethod.Invoke(instance, null);
private static readonly MethodInfo AddButtonMethod = private static readonly MethodInfo AddButtonMethod =
typeof(InteractionButtonsContainer).GetMethod("method_5", BindingFlags.NonPublic | BindingFlags.Instance); typeof(InteractionButtonsContainer).GetMethod("method_5", BindingFlags.Public | BindingFlags.Instance);
private static void AddButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) => private static void AddButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) =>
AddButtonMethod.Invoke(instance, [button]); AddButtonMethod.Invoke(instance, [button]);
@ -146,6 +116,7 @@ internal static class InteractionButtonsContainerExtensions
public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl impl) public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl impl)
{ {
bool isInteractive = impl.IsInteractive(); bool isInteractive = impl.IsInteractive();
IEnumerable<CustomInteraction> subMenu = impl.SubMenu?.Invoke();
SimpleContextMenuButton button = null; SimpleContextMenuButton button = null;
button = instance.CreateButton( button = instance.CreateButton(
impl.Key, impl.Key,
@ -162,18 +133,19 @@ internal static class InteractionButtonsContainerExtensions
{ {
instance.SetCurrentButton(button); instance.SetCurrentButton(button);
instance.CloseSubMenu(); instance.CloseSubMenu();
if (isInteractive) if (isInteractive && subMenu != null)
{ {
CustomSubInteractions subMenu = impl.SubMenu?.Invoke(); CustomInteractionsImpl subInteractions = new CustomInteractionsImpl(impl.Context);
if (subMenu is not null) foreach (CustomInteractionImpl subImpl in subMenu.Select(item => item.Impl))
instance.SetSubInteractions(subMenu.Impl); subInteractions.AddCustomInteraction(subImpl);
instance.SetSubInteractions(subInteractions);
} }
}, },
impl.SubMenu is not null, subMenu?.Any() ?? false,
false false
); );
button.SetButtonInteraction( button.SetButtonInteraction(
(isInteractive, impl.Error?.Invoke() ?? "") isInteractive ? SuccessfulResult.New : new FailedResult(impl.Error?.Invoke() ?? "", 0)
); );
instance.AddButton(button); instance.AddButton(button);
} }

View File

@ -3,24 +3,27 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.CustomInteractions</AssemblyName> <AssemblyName>IcyClawz.CustomInteractions</AssemblyName>
<Version>1.3.1</Version> <Version>1.6.0</Version>
<RootNamespace>IcyClawz.CustomInteractions</RootNamespace> <RootNamespace>IcyClawz.CustomInteractions</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Aki.Reflection">
<HintPath>..\Shared\Aki.Reflection.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp-CustomInteractions.dll</HintPath> <HintPath>..\Shared\Assembly-CSharp-CustomInteractions.dll</HintPath>
</Reference> </Reference>
<Reference Include="BepInEx"> <Reference Include="BepInEx">
<HintPath>..\Shared\BepInEx.dll</HintPath> <HintPath>..\Shared\BepInEx.dll</HintPath>
</Reference> </Reference>
<Reference Include="Comfort">
<HintPath>..\Shared\Comfort.dll</HintPath>
</Reference>
<Reference Include="Sirenix.Serialization"> <Reference Include="Sirenix.Serialization">
<HintPath>..\Shared\Sirenix.Serialization.dll</HintPath> <HintPath>..\Shared\Sirenix.Serialization.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,16 +1,12 @@
using Aki.Reflection.Patching;
using BepInEx; using BepInEx;
using EFT.UI; using EFT.UI;
using SPT.Reflection.Patching;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using DynamicInteraction = GClass2816;
using ItemContext = GClass2623;
using ItemInfoInteractions = GClass2817<EFT.InventoryLogic.EItemInfoButton>;
namespace IcyClawz.CustomInteractions; namespace IcyClawz.CustomInteractions;
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.3.1")] [BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.6.0")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private void Awake() private void Awake()
@ -26,15 +22,16 @@ internal class ItemUiContextPatch : ModulePatch
typeof(ItemUiContext).GetMethod("GetItemContextInteractions", BindingFlags.Public | BindingFlags.Instance); typeof(ItemUiContext).GetMethod("GetItemContextInteractions", BindingFlags.Public | BindingFlags.Instance);
[PatchPostfix] [PatchPostfix]
private static void Postfix(ref ItemInfoInteractions __result, ref ItemUiContext __instance, ItemContext itemContext) private static void Postfix(ref ItemInfoInteractionsAbstractClass<EFT.InventoryLogic.EItemInfoButton> __result,
ref ItemUiContext __instance, ItemContextClass itemContext)
{ {
foreach (var provider in CustomInteractionsManager.Providers.OfType<IItemCustomInteractionsProvider>()) foreach (var provider in CustomInteractionsManager.Providers)
{ {
var interactions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item); var interactions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item);
if (interactions is null) if (interactions is null)
continue; continue;
foreach (CustomInteraction interaction in interactions) foreach (CustomInteractionImpl impl in interactions.Select(interaction => interaction.Impl))
__result.AddCustomInteraction(interaction); __result.AddCustomInteraction(impl);
} }
} }
} }
@ -42,10 +39,10 @@ internal class ItemUiContextPatch : ModulePatch
internal class InteractionButtonsContainerPatch : ModulePatch internal class InteractionButtonsContainerPatch : ModulePatch
{ {
protected override MethodBase GetTargetMethod() => protected override MethodBase GetTargetMethod() =>
typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.NonPublic | BindingFlags.Instance); typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix] [PatchPrefix]
private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteraction interaction) private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteractionClass interaction)
{ {
if (interaction is CustomInteractionImpl impl) if (interaction is CustomInteractionImpl impl)
{ {

View File

@ -3,21 +3,21 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemAttributeFix</AssemblyName> <AssemblyName>IcyClawz.ItemAttributeFix</AssemblyName>
<Version>1.2.0</Version> <Version>1.5.0</Version>
<RootNamespace>IcyClawz.ItemAttributeFix</RootNamespace> <RootNamespace>IcyClawz.ItemAttributeFix</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Aki.Reflection">
<HintPath>..\Shared\Aki.Reflection.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp.dll</HintPath> <HintPath>..\Shared\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
<Reference Include="BepInEx"> <Reference Include="BepInEx">
<HintPath>..\Shared\BepInEx.dll</HintPath> <HintPath>..\Shared\BepInEx.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,11 +1,11 @@
using Aki.Reflection.Patching;
using BepInEx; using BepInEx;
using EFT.UI; using EFT.UI;
using SPT.Reflection.Patching;
using System.Reflection; using System.Reflection;
namespace IcyClawz.ItemAttributeFix; namespace IcyClawz.ItemAttributeFix;
[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.2.0")] [BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.5.0")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private void Awake() => private void Awake() =>

View File

@ -5,89 +5,71 @@ using EFT.UI;
using IcyClawz.CustomInteractions; using IcyClawz.CustomInteractions;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
using ILightTemplate = GInterface246; using ILightTemplate = GInterface357;
using ResourceCache = GClass1977; using ISightTemplate = GInterface365;
using GlobalEvents = GClass3400;
namespace IcyClawz.ItemContextMenuExt; namespace IcyClawz.ItemContextMenuExt;
internal static class PlayerExtensions internal static class SightComponentExtensions
{ {
private static readonly FieldInfo InventoryControllerField = public static int[] GetScopeCalibrationDistances(this SightComponent instance, int scopeIndex) =>
typeof(Player).GetField("_inventoryController", BindingFlags.NonPublic | BindingFlags.Instance); ((ISightTemplate)instance.Item.Template).CalibrationDistances[scopeIndex];
public static InventoryControllerClass GetInventoryController(this Player player) => public static float[] GetScopeZooms(this SightComponent instance, int scopeIndex) =>
InventoryControllerField.GetValue(player) as InventoryControllerClass; ((ISightTemplate)instance.Item.Template).Zooms[scopeIndex];
} }
internal static class LightComponentExtensions internal static class LightComponentExtensions
{ {
public static int GetModesCount(this LightComponent component) => public static int GetModesCount(this LightComponent instance) =>
((ILightTemplate)component.Item.Template).ModesCount; ((ILightTemplate)instance.Item.Template).ModesCount;
} }
internal sealed class CustomInteractionsProvider : IItemCustomInteractionsProvider internal sealed class CustomInteractionsProvider : ICustomInteractionsProvider
{ {
internal const string IconsPrefix = "Characteristics/Icons/"; private const string IconsPrefix = "Characteristics/Icons/";
internal static StaticIcons StaticIcons => EFTHardSettings.Instance.StaticIcons; private static StaticIcons StaticIcons => EFTHardSettings.Instance.StaticIcons;
public IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item) public IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext context, EItemViewType viewType, Item item)
{
if (viewType is EItemViewType.Inventory)
{ {
if (viewType is not EItemViewType.Inventory)
yield break;
FireModeComponent fireModeComponent = item.GetItemComponent<FireModeComponent>(); FireModeComponent fireModeComponent = item.GetItemComponent<FireModeComponent>();
if (fireModeComponent is not null) if (fireModeComponent is not null)
{ return GetFireModeInteractions(context, fireModeComponent);
// Firing mode
yield return new() SightComponent sightComponent = item.GetItemComponent<SightComponent>();
{ if (sightComponent is not null)
Caption = () => "Firing mode", return GetSightInteractions(context, sightComponent);
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.Weapon),
SubMenu = () => new FireModeSubMenu(uiContext, fireModeComponent),
Enabled = () => fireModeComponent.AvailableEFireModes.Length > 1,
Error = () => "This weapon is incapable of selective fire"
};
yield break;
}
LightComponent lightComponent = item.GetItemComponent<LightComponent>(); LightComponent lightComponent = item.GetItemComponent<LightComponent>();
if (lightComponent is not null) if (lightComponent is not null)
{ return GetLightInteractions(context, lightComponent);
// Turn on/off
yield return new()
{
Caption = () => (lightComponent.IsActive ? "TurnOff" : "TurnOn").Localized(),
Icon = () => ResourceCache.Pop<Sprite>(IconsPrefix + (lightComponent.IsActive ? "TurnOff" : "TurnOn")),
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetLightState(lightComponent, !lightComponent.IsActive, lightComponent.SelectedMode);
uiContext.RedrawContextMenus([item.TemplateId]);
} }
}; return [];
// Switch mode
yield return new()
{
Caption = () => "Switch mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => new LightModeSubMenu(uiContext, lightComponent),
Enabled = () => lightComponent.GetModesCount() > 1,
Error = () => "This device has no alternative modes"
};
yield break;
} }
private IEnumerable<CustomInteraction> GetFireModeInteractions(ItemUiContext context, FireModeComponent component)
{
if (component.AvailableEFireModes.Length > 1)
{
yield return new(context)
{
Caption = () => "Firing mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.Weapon),
SubMenu = () => GetFireModeSubInteractions(context, component),
};
} }
} }
internal class FireModeSubMenu : CustomSubInteractions private IEnumerable<CustomInteraction> GetFireModeSubInteractions(ItemUiContext context, FireModeComponent component)
{ {
public FireModeSubMenu(ItemUiContext uiContext, FireModeComponent component) foreach (Weapon.EFireMode fireMode in component.AvailableEFireModes)
: base(uiContext)
{ {
AddRange(component.AvailableEFireModes.Select(fireMode => new CustomInteraction() yield return new(context)
{ {
Caption = () => fireMode.ToString().Localized(), Caption = () => fireMode.ToString().Localized(),
Action = () => Action = () =>
@ -95,26 +77,137 @@ internal class FireModeSubMenu : CustomSubInteractions
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu); Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetFireMode(component, fireMode); ComponentUtils.SetFireMode(component, fireMode);
}, },
Enabled = () => fireMode != component.FireMode Enabled = () => fireMode != component.FireMode,
})); };
} }
} }
internal class LightModeSubMenu : CustomSubInteractions private IEnumerable<CustomInteraction> GetSightInteractions(ItemUiContext context, SightComponent component)
{ {
public LightModeSubMenu(ItemUiContext uiContext, LightComponent component) if (component.ScopesCount > 1)
: base(uiContext)
{ {
AddRange(Enumerable.Range(0, component.GetModesCount()).Select(lightMode => new CustomInteraction() yield return new(context)
{
Caption = () => "Active scope",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => GetScopeIndexSubInteractions(context, component),
};
}
if (component.GetScopeModesCount(component.SelectedScope) > 1)
{
yield return new(context)
{
Caption = () => "Active mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => GetScopeModeSubInteractions(context, component),
};
}
if (component.GetScopeCalibrationDistances(component.SelectedScope).Length > 1)
{
yield return new(context)
{
Caption = () => "Zero distance",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => GetScopeCalibSubInteractions(context, component),
};
}
}
private IEnumerable<CustomInteraction> GetScopeIndexSubInteractions(ItemUiContext context, SightComponent component)
{
foreach (int scopeIndex in Enumerable.Range(0, component.ScopesCount))
{
yield return new(context)
{
Caption = () => $"Scope {scopeIndex + 1}",
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetScopeIndex(component, scopeIndex);
},
Enabled = () => scopeIndex != component.SelectedScope,
};
}
}
private IEnumerable<CustomInteraction> GetScopeModeSubInteractions(ItemUiContext context, SightComponent component)
{
int scopeIndex = component.SelectedScope;
float[] scopeZooms = component.GetScopeZooms(scopeIndex);
foreach (int scopeMode in Enumerable.Range(0, component.GetScopeModesCount(scopeIndex)))
{
float scopeZoom = scopeZooms.Length > 0 ? scopeZooms[scopeMode] : 0f;
yield return new(context)
{
Caption = () => $"Mode {scopeMode + 1} ({scopeZoom}x)",
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetScopeMode(component, scopeMode);
},
Enabled = () => scopeMode != component.ScopesSelectedModes[scopeIndex],
};
}
}
private IEnumerable<CustomInteraction> GetScopeCalibSubInteractions(ItemUiContext context, SightComponent component)
{
int scopeIndex = component.SelectedScope;
int[] scopeCalibDistances = component.GetScopeCalibrationDistances(scopeIndex);
foreach (int scopeCalibIndex in Enumerable.Range(0, scopeCalibDistances.Length))
{
yield return new(context)
{
Caption = () => $"{scopeCalibDistances[scopeCalibIndex]}",
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetScopeCalibIndex(component, scopeCalibIndex);
},
Enabled = () => scopeCalibIndex != component.ScopesCurrentCalibPointIndexes[scopeIndex],
};
}
}
private IEnumerable<CustomInteraction> GetLightInteractions(ItemUiContext context, LightComponent component)
{
yield return new(context)
{
Caption = () => component.IsActive ? "Turn off" : "Turn on",
Icon = () => CacheResourcesPopAbstractClass.Pop<Sprite>(IconsPrefix + (component.IsActive ? "TurnOff" : "TurnOn")),
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetLightActive(component, !component.IsActive);
GlobalEvents.RequestGlobalRedraw();
},
};
if (component.GetModesCount() > 1)
{
yield return new(context)
{
Caption = () => "Active mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => GetLightModeSubInteractions(context, component),
};
}
}
private IEnumerable<CustomInteraction> GetLightModeSubInteractions(ItemUiContext context, LightComponent component)
{
foreach (int lightMode in Enumerable.Range(0, component.GetModesCount()))
{
yield return new(context)
{ {
Caption = () => $"Mode {lightMode + 1}", Caption = () => $"Mode {lightMode + 1}",
Action = () => Action = () =>
{ {
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu); Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetLightState(component, component.IsActive, lightMode); ComponentUtils.SetLightMode(component, lightMode);
}, },
Enabled = () => lightMode != component.SelectedMode Enabled = () => lightMode != component.SelectedMode,
})); };
}
} }
} }
@ -129,7 +222,7 @@ internal static class ComponentUtils
if (fc.Item.MalfState.State is not Weapon.EMalfunctionState.None) if (fc.Item.MalfState.State is not Weapon.EMalfunctionState.None)
{ {
fc.FirearmsAnimator.MisfireSlideUnknown(false); fc.FirearmsAnimator.MisfireSlideUnknown(false);
player.GetInventoryController().ExamineMalfunction(fc.Item, false); player.InventoryController.ExamineMalfunction(fc.Item, false);
return; return;
} }
@ -140,18 +233,89 @@ internal static class ComponentUtils
component.SetFireMode(fireMode); component.SetFireMode(fireMode);
} }
public static void SetLightState(LightComponent component, bool isActive, int lightMode) public static void SetScopeIndex(SightComponent component, int scopeIndex)
{
SetScopeState(component, new()
{
Id = component.Item.Id,
ScopeIndexInsideSight = scopeIndex,
ScopeMode = component.ScopesSelectedModes[scopeIndex],
ScopeCalibrationIndex = component.ScopesCurrentCalibPointIndexes[scopeIndex],
});
}
public static void SetScopeMode(SightComponent component, int scopeMode)
{
int scopeIndex = component.SelectedScope;
SetScopeState(component, new()
{
Id = component.Item.Id,
ScopeIndexInsideSight = scopeIndex,
ScopeMode = scopeMode,
ScopeCalibrationIndex = component.ScopesCurrentCalibPointIndexes[scopeIndex],
});
}
public static void SetScopeCalibIndex(SightComponent component, int scopeCalibIndex)
{
int scopeIndex = component.SelectedScope;
SetScopeState(component, new()
{
Id = component.Item.Id,
ScopeIndexInsideSight = scopeIndex,
ScopeMode = component.ScopesSelectedModes[scopeIndex],
ScopeCalibrationIndex = scopeCalibIndex,
});
}
private static void SetScopeState(SightComponent component, FirearmScopeStateStruct scopeState)
{ {
Player player = GamePlayerOwner.MyPlayer; Player player = GamePlayerOwner.MyPlayer;
if (player is not null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item)) if (player is not null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item))
{ {
fc.SetLightsState([new() { Id = component.Item.Id, IsActive = isActive, LightMode = lightMode }]); fc.SetScopeMode([scopeState]);
return; return;
} }
component.IsActive = isActive; int scopeIndex = scopeState.ScopeIndexInsideSight;
component.SelectedMode = lightMode; component.SelectedScope = scopeIndex;
component.ScopesSelectedModes[scopeIndex] = scopeState.ScopeMode;
component.ScopesCurrentCalibPointIndexes[scopeIndex] = scopeState.ScopeCalibrationIndex;
}
public static void SetLightActive(LightComponent component, bool isActive)
{
SetLightState(component, new()
{
Id = component.Item.Id,
IsActive = isActive,
LightMode = component.SelectedMode,
});
}
public static void SetLightMode(LightComponent component, int lightMode)
{
SetLightState(component, new()
{
Id = component.Item.Id,
IsActive = component.IsActive,
LightMode = lightMode,
});
}
private static void SetLightState(LightComponent component, FirearmLightStateStruct lightState)
{
Player player = GamePlayerOwner.MyPlayer;
if (player is not null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item))
{
fc.SetLightsState([lightState]);
return;
}
component.IsActive = lightState.IsActive;
component.SelectedMode = lightState.LightMode;
if (player is not null) if (player is not null)
{ {

View File

@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemContextMenuExt</AssemblyName> <AssemblyName>IcyClawz.ItemContextMenuExt</AssemblyName>
<Version>1.2.1</Version> <Version>1.6.0</Version>
<RootNamespace>IcyClawz.ItemContextMenuExt</RootNamespace> <RootNamespace>IcyClawz.ItemContextMenuExt</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
@ -27,6 +27,9 @@
<Reference Include="ItemTemplate.Types"> <Reference Include="ItemTemplate.Types">
<HintPath>..\Shared\ItemTemplate.Types.dll</HintPath> <HintPath>..\Shared\ItemTemplate.Types.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,12 +1,35 @@
using BepInEx; using BepInEx;
using BepInEx.Configuration;
using EFT.UI;
using IcyClawz.CustomInteractions; using IcyClawz.CustomInteractions;
using SPT.Reflection.Patching;
using System.Reflection;
namespace IcyClawz.ItemContextMenuExt; namespace IcyClawz.ItemContextMenuExt;
[BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.2.1")] [BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.6.0")]
[BepInDependency("com.IcyClawz.CustomInteractions")] [BepInDependency("com.IcyClawz.CustomInteractions")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private void Awake() => private static ConfigEntry<float> ScaleConfig { get; set; }
private void Awake()
{
const string SECTION = "Item Context Menu";
ScaleConfig = Config.Bind(SECTION, "Scale", 1f);
new ItemUiContextPatch().Enable();
CustomInteractionsManager.Register(new CustomInteractionsProvider()); CustomInteractionsManager.Register(new CustomInteractionsProvider());
} }
internal static float Scale => ScaleConfig.Value;
}
internal class ItemUiContextPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() =>
typeof(ItemUiContext).GetMethod("ShowContextMenu", BindingFlags.Public | BindingFlags.Instance);
[PatchPostfix]
private static void Postfix(ref ItemUiContext __instance) =>
__instance.ContextMenu.Transform.localScale = new(Plugin.Scale, Plugin.Scale);
}

View File

@ -1,14 +1,14 @@
using Aki.Reflection.Utils;
using Comfort.Common; using Comfort.Common;
using EFT; using EFT;
using EFT.InventoryLogic; using EFT.InventoryLogic;
using SPT.Reflection.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using UnityEngine; using UnityEngine;
using CurrencyUtil = GClass2334; using CurrencyUtil = GClass2867;
namespace IcyClawz.ItemSellPrice; namespace IcyClawz.ItemSellPrice;
@ -55,7 +55,7 @@ internal static class ItemExtensions
["po"] = ["Preço de venda ({0})", "Não pode ser vendido a comerciantes"], ["po"] = ["Preço de venda ({0})", "Não pode ser vendido a comerciantes"],
["ru"] = ["Цена продажи ({0})", "Невозможно продать торговцам"], ["ru"] = ["Цена продажи ({0})", "Невозможно продать торговцам"],
["sk"] = ["Predajná cena ({0})", "Nedá sa predať obchodníkom"], ["sk"] = ["Predajná cena ({0})", "Nedá sa predať obchodníkom"],
["tu"] = ["Satış fiyatı ({0})", "Tüccarlara satılamaz"] ["tu"] = ["Satış fiyatı ({0})", "Tüccarlara satılamaz"],
}; };
private static ISession _Session; private static ISession _Session;
@ -91,11 +91,11 @@ internal static class ItemExtensions
FullStringValue = () => FullStringValue = () =>
{ {
IEnumerable<TraderOffer> offers = GetAllTraderOffers(item); IEnumerable<TraderOffer> offers = GetAllTraderOffers(item);
return offers is not null return offers.Any()
? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}")) ? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}"))
: ""; : "";
}, },
DisplayType = () => EItemAttributeDisplayType.Compact DisplayType = () => EItemAttributeDisplayType.Compact,
}; };
item.Attributes = [attribute, .. item.Attributes]; item.Attributes = [attribute, .. item.Attributes];
} }
@ -111,38 +111,34 @@ internal static class ItemExtensions
private static TraderOffer GetTraderOffer(Item item, TraderClass trader) private static TraderOffer GetTraderOffer(Item item, TraderClass trader)
{ {
var result = trader.GetUserItemPrice(item); var price = trader.GetUserItemPrice(item);
return result is null ? null : new( return price.HasValue ? new(
trader.LocalizedName, trader.LocalizedName,
result.Value.Amount, price.Value.Amount,
CurrencyUtil.GetCurrencyCharById(result.Value.CurrencyId), CurrencyUtil.GetCurrencyCharById(price.Value.CurrencyId.Value),
trader.GetSupplyData().CurrencyCourses[result.Value.CurrencyId], trader.GetSupplyData().CurrencyCourses[price.Value.CurrencyId.Value],
item.StackObjectsCount item.StackObjectsCount
); ) : null;
} }
private static IEnumerable<TraderOffer> GetAllTraderOffers(Item item) private static IEnumerable<TraderOffer> GetAllTraderOffers(Item item)
{ {
if (!Session.Profile.Examined(item)) if (!Session.Profile.Examined(item))
return null; return [];
switch (item.Owner?.OwnerType) if (item.Owner?.OwnerType is EOwnerType.RagFair or EOwnerType.Trader
{ && (item.StackObjectsCount > 1 || item.UnlimitedCount))
case EOwnerType.RagFair:
case EOwnerType.Trader:
if (item.StackObjectsCount > 1 || item.UnlimitedCount)
{ {
item = item.CloneItem(); item = item.CloneItem();
item.StackObjectsCount = 1; item.StackObjectsCount = 1;
item.UnlimitedCount = false; item.UnlimitedCount = false;
} }
break;
}
return Session.Traders return Session.Traders
.Where(trader => !trader.Settings.AvailableInRaid)
.Select(trader => GetTraderOffer(item, trader)) .Select(trader => GetTraderOffer(item, trader))
.Where(offer => offer is not null) .Where(offer => offer is not null)
.OrderByDescending(offer => offer.Price * offer.Course); .OrderByDescending(offer => offer.Price * offer.Course);
} }
private static TraderOffer GetBestTraderOffer(Item item) => private static TraderOffer GetBestTraderOffer(Item item) =>
GetAllTraderOffers(item)?.FirstOrDefault() ?? null; GetAllTraderOffers(item).FirstOrDefault();
} }

View File

@ -3,15 +3,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemSellPrice</AssemblyName> <AssemblyName>IcyClawz.ItemSellPrice</AssemblyName>
<Version>1.2.1</Version> <Version>1.5.0</Version>
<RootNamespace>IcyClawz.ItemSellPrice</RootNamespace> <RootNamespace>IcyClawz.ItemSellPrice</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Aki.Reflection">
<HintPath>..\Shared\Aki.Reflection.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp.dll</HintPath> <HintPath>..\Shared\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
@ -21,6 +18,9 @@
<Reference Include="Comfort"> <Reference Include="Comfort">
<HintPath>..\Shared\Comfort.dll</HintPath> <HintPath>..\Shared\Comfort.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,20 +1,20 @@
using Aki.Reflection.Patching;
using BepInEx; using BepInEx;
using EFT;
using EFT.InventoryLogic; using EFT.InventoryLogic;
using SPT.Reflection.Patching;
using System.Reflection; using System.Reflection;
namespace IcyClawz.ItemSellPrice; namespace IcyClawz.ItemSellPrice;
[BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.2.1")] [BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.5.0")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private void Awake() private void Awake()
{ {
new TraderPatch().Enable(); new TraderPatch().Enable();
new ItemPatch().Enable(); new ItemPatch().Enable();
new AmmoPatch().Enable(); new AmmoItemPatch().Enable();
new GrenadePatch().Enable(); new ThrowWeapItemPatch().Enable();
new SecureContainerPatch().Enable();
} }
} }
@ -38,32 +38,22 @@ internal class ItemPatch : ModulePatch
__instance.AddTraderOfferAttribute(); __instance.AddTraderOfferAttribute();
} }
internal class AmmoPatch : ModulePatch internal class AmmoItemPatch : ModulePatch
{ {
protected override MethodBase GetTargetMethod() => protected override MethodBase GetTargetMethod() =>
typeof(BulletClass).GetConstructors()[0]; typeof(AmmoItemClass).GetConstructors()[0];
[PatchPostfix] [PatchPostfix]
private static void PatchPostfix(ref BulletClass __instance) => private static void PatchPostfix(ref AmmoItemClass __instance) =>
__instance.AddTraderOfferAttribute(); __instance.AddTraderOfferAttribute();
} }
internal class GrenadePatch : ModulePatch internal class ThrowWeapItemPatch : ModulePatch
{ {
protected override MethodBase GetTargetMethod() => protected override MethodBase GetTargetMethod() =>
typeof(GrenadeClass).GetConstructors()[0]; typeof(ThrowWeapItemClass).GetConstructors()[0];
[PatchPostfix] [PatchPostfix]
private static void PatchPostfix(ref GrenadeClass __instance) => private static void PatchPostfix(ref ThrowWeapItemClass __instance) =>
__instance.AddTraderOfferAttribute();
}
internal class SecureContainerPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() =>
typeof(ItemContainerClass).GetConstructors()[0];
[PatchPostfix]
private static void PatchPostfix(ref ItemContainerClass __instance) =>
__instance.AddTraderOfferAttribute(); __instance.AddTraderOfferAttribute();
} }

View File

@ -1,13 +1,13 @@
using Aki.Reflection.Utils;
using Comfort.Common; using Comfort.Common;
using EFT; using EFT;
using EFT.InventoryLogic; using EFT.InventoryLogic;
using SPT.Reflection.Utils;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using UnityEngine; using UnityEngine;
using InGameStatus = GClass1716; using InGameStatus = GClass2064;
namespace IcyClawz.MagazineInspector; namespace IcyClawz.MagazineInspector;
@ -30,7 +30,7 @@ internal static class MagazineClassExtensions
["po"] = "Munição carregada", ["po"] = "Munição carregada",
["ru"] = "Заряженные боеприпасы", ["ru"] = "Заряженные боеприпасы",
["sk"] = "Nabitá munícia", ["sk"] = "Nabitá munícia",
["tu"] = "Yüklü mühimmat" ["tu"] = "Yüklü mühimmat",
}; };
private static ISession _Session; private static ISession _Session;
@ -38,7 +38,7 @@ internal static class MagazineClassExtensions
private static Profile ActiveProfile => InGameStatus.InRaid ? GamePlayerOwner.MyPlayer.Profile : Session.Profile; private static Profile ActiveProfile => InGameStatus.InRaid ? GamePlayerOwner.MyPlayer.Profile : Session.Profile;
public static void AddAmmoCountAttribute(this MagazineClass magazine) public static void AddAmmoCountAttribute(this MagazineItemClass magazine)
{ {
ItemAttributeClass attribute = magazine.Attributes.Find(attr => attr.Id is EItemAttributeId.MaxCount); ItemAttributeClass attribute = magazine.Attributes.Find(attr => attr.Id is EItemAttributeId.MaxCount);
if (attribute is null) if (attribute is null)
@ -74,7 +74,7 @@ internal static class MagazineClassExtensions
}; };
} }
private static int? GetAmmoCount(MagazineClass magazine, Profile profile, out bool magChecked) private static int? GetAmmoCount(MagazineItemClass magazine, Profile profile, out bool magChecked)
{ {
if (!InGameStatus.InRaid || magazine.Count == 0) if (!InGameStatus.InRaid || magazine.Count == 0)
{ {

View File

@ -3,15 +3,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.MagazineInspector</AssemblyName> <AssemblyName>IcyClawz.MagazineInspector</AssemblyName>
<Version>1.2.1</Version> <Version>1.5.0</Version>
<RootNamespace>IcyClawz.MagazineInspector</RootNamespace> <RootNamespace>IcyClawz.MagazineInspector</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Aki.Reflection">
<HintPath>..\Shared\Aki.Reflection.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp.dll</HintPath> <HintPath>..\Shared\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
@ -21,6 +18,9 @@
<Reference Include="Comfort"> <Reference Include="Comfort">
<HintPath>..\Shared\Comfort.dll</HintPath> <HintPath>..\Shared\Comfort.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,10 +1,10 @@
using Aki.Reflection.Patching;
using BepInEx; using BepInEx;
using SPT.Reflection.Patching;
using System.Reflection; using System.Reflection;
namespace IcyClawz.MagazineInspector; namespace IcyClawz.MagazineInspector;
[BepInPlugin("com.IcyClawz.MagazineInspector", "IcyClawz.MagazineInspector", "1.2.1")] [BepInPlugin("com.IcyClawz.MagazineInspector", "IcyClawz.MagazineInspector", "1.5.0")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private void Awake() => private void Awake() =>
@ -14,9 +14,9 @@ public class Plugin : BaseUnityPlugin
internal class MagazinePatch : ModulePatch internal class MagazinePatch : ModulePatch
{ {
protected override MethodBase GetTargetMethod() => protected override MethodBase GetTargetMethod() =>
typeof(MagazineClass).GetConstructors()[0]; typeof(MagazineItemClass).GetConstructors()[0];
[PatchPostfix] [PatchPostfix]
private static void PatchPostfix(ref MagazineClass __instance) => private static void PatchPostfix(ref MagazineItemClass __instance) =>
__instance.AddAmmoCountAttribute(); __instance.AddAmmoCountAttribute();
} }

View File

@ -39,7 +39,7 @@ internal sealed class ConfigurationManagerAttributes
/// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager). /// Custom setting editor (OnGUI code that replaces the default editor provided by ConfigurationManager).
/// See below for a deeper explanation. Using a custom drawer will cause many of the other fields to do nothing. /// See below for a deeper explanation. Using a custom drawer will cause many of the other fields to do nothing.
/// </summary> /// </summary>
public System.Action<BepInEx.Configuration.ConfigEntryBase> CustomDrawer; public System.Action<ConfigEntryBase> CustomDrawer;
/// <summary> /// <summary>
/// Show this setting in the settings screen at all? If false, don't show. /// Show this setting in the settings screen at all? If false, don't show.

View File

@ -31,7 +31,7 @@ internal static class ColorCache
[ColorName.Silver] = new Color32(150, 150, 150, ALPHA), [ColorName.Silver] = new Color32(150, 150, 150, ALPHA),
[ColorName.Tan] = new Color32(175, 145, 100, ALPHA), [ColorName.Tan] = new Color32(175, 145, 100, ALPHA),
[ColorName.Violet] = new Color32(80, 50, 180, ALPHA), [ColorName.Violet] = new Color32(80, 50, 180, ALPHA),
[ColorName.Yellow] = new Color32(170, 170, 0, ALPHA) [ColorName.Yellow] = new Color32(170, 170, 0, ALPHA),
}; };
public static Color Get(ColorName name) => Cache.TryGetValue(name, out Color color) ? color : default; public static Color Get(ColorName name) => Cache.TryGetValue(name, out Color color) ? color : default;
@ -39,7 +39,7 @@ internal static class ColorCache
internal enum EAmmoExtraAttributeId internal enum EAmmoExtraAttributeId
{ {
Damage, PenetrationPower, ArmorDamage, FragmentationChance, RicochetChance ArmorDamage, FragmentationChance, RicochetChance
} }
internal static class ImageExtensions internal static class ImageExtensions
@ -48,8 +48,8 @@ internal static class ImageExtensions
{ {
using MemoryStream ms = new(); using MemoryStream ms = new();
instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png); instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
Texture2D texture = new(instance.Width, instance.Height); Texture2D texture = new(instance.Width, instance.Height, TextureFormat.RGBA32, false);
ImageConversion.LoadImage(texture, ms.ToArray()); texture.LoadImage(ms.ToArray());
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
} }
} }
@ -58,11 +58,9 @@ internal static class IconCache
{ {
private static readonly Dictionary<Enum, Sprite> Cache = new() private static readonly Dictionary<Enum, Sprite> Cache = new()
{ {
[EAmmoExtraAttributeId.Damage] = Properties.Resources.Damage.ToSprite(),
[EAmmoExtraAttributeId.PenetrationPower] = Properties.Resources.PenetrationPower.ToSprite(),
[EAmmoExtraAttributeId.ArmorDamage] = Properties.Resources.ArmorDamage.ToSprite(), [EAmmoExtraAttributeId.ArmorDamage] = Properties.Resources.ArmorDamage.ToSprite(),
[EAmmoExtraAttributeId.FragmentationChance] = Properties.Resources.FragmentationChance.ToSprite(), [EAmmoExtraAttributeId.FragmentationChance] = Properties.Resources.FragmentationChance.ToSprite(),
[EAmmoExtraAttributeId.RicochetChance] = Properties.Resources.RicochetChance.ToSprite() [EAmmoExtraAttributeId.RicochetChance] = Properties.Resources.RicochetChance.ToSprite(),
}; };
public static Sprite Get(Enum id) => Cache.TryGetValue(id, out Sprite sprite) ? sprite : default; public static Sprite Get(Enum id) => Cache.TryGetValue(id, out Sprite sprite) ? sprite : default;
@ -70,6 +68,15 @@ internal static class IconCache
internal static class AmmoTemplateExtensions internal static class AmmoTemplateExtensions
{ {
private static readonly string[] MalfChancesKeys = (string[])
typeof(AmmoTemplate).GetField("MalfChancesKeys", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
private static readonly FieldInfo MaxMalfMisfireChance =
typeof(AmmoTemplate).GetField("MaxMalfMisfireChance", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly FieldInfo MaxMalfFeedChance =
typeof(AmmoTemplate).GetField("MaxMalfFeedChance", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly FieldInfo CachedQualitiesField = private static readonly FieldInfo CachedQualitiesField =
typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance); typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance);
@ -78,41 +85,33 @@ internal static class AmmoTemplateExtensions
public static void AddExtraAttributes(this AmmoTemplate instance) public static void AddExtraAttributes(this AmmoTemplate instance)
{ {
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.Damage)
{
Name = EAmmoExtraAttributeId.Damage.ToString(),
DisplayNameFunc = () => "Damage",
Base = () => instance.Damage * instance.ProjectileCount,
StringValue = () =>
{
int totalDamage = instance.Damage * instance.ProjectileCount;
return instance.ProjectileCount > 1
? $"{instance.ProjectileCount} × {instance.Damage} = {totalDamage}"
: totalDamage.ToString();
},
DisplayType = () => EItemAttributeDisplayType.Compact
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.PenetrationPower)
{
Name = EAmmoExtraAttributeId.PenetrationPower.ToString(),
DisplayNameFunc = () => "Penetration power",
Base = () => instance.PenetrationPower,
StringValue = () =>
{
int armorClass = instance.GetPenetrationArmorClass();
return (armorClass > 0 ? $"Class {armorClass}" : "Unarmored") + $" ({instance.PenetrationPower})";
},
DisplayType = () => EItemAttributeDisplayType.Compact
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.ArmorDamage) instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.ArmorDamage)
{ {
Name = EAmmoExtraAttributeId.ArmorDamage.ToString(), Name = EAmmoExtraAttributeId.ArmorDamage.ToString(),
DisplayNameFunc = () => "Armor damage", DisplayNameFunc = () => "Armor damage",
Base = () => instance.ArmorDamage, Base = () => instance.ArmorDamage,
StringValue = () => $"{instance.ArmorDamage}%", StringValue = () => $"{instance.ArmorDamage}%",
DisplayType = () => EItemAttributeDisplayType.Compact DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.DurabilityBurn)
{
Name = EItemAttributeId.DurabilityBurn.GetName(),
Base = () => instance.DurabilityBurnModificator - 1f,
StringValue = () => $"{(instance.DurabilityBurnModificator - 1f) * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
LabelVariations = EItemAttributeLabelVariations.Colored,
LessIsGood = true,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.HeatFactor)
{
Name = EItemAttributeId.HeatFactor.GetName(),
Base = () => instance.HeatFactor - 1f,
StringValue = () => $"{(instance.HeatFactor - 1f) * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
LabelVariations = EItemAttributeLabelVariations.Colored,
LessIsGood = true,
}); });
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance) instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance)
@ -120,8 +119,8 @@ internal static class AmmoTemplateExtensions
Name = EAmmoExtraAttributeId.FragmentationChance.ToString(), Name = EAmmoExtraAttributeId.FragmentationChance.ToString(),
DisplayNameFunc = () => "Fragmentation chance", DisplayNameFunc = () => "Fragmentation chance",
Base = () => instance.FragmentationChance, Base = () => instance.FragmentationChance,
StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%", StringValue = () => $"{instance.FragmentationChance * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact DisplayType = () => EItemAttributeDisplayType.Compact,
}); });
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance) instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance)
@ -129,8 +128,44 @@ internal static class AmmoTemplateExtensions
Name = EAmmoExtraAttributeId.RicochetChance.ToString(), Name = EAmmoExtraAttributeId.RicochetChance.ToString(),
DisplayNameFunc = () => "Ricochet chance", DisplayNameFunc = () => "Ricochet chance",
Base = () => instance.RicochetChance, Base = () => instance.RicochetChance,
StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%", StringValue = () => $"{(instance.RicochetChance * 100f):F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.MalfMisfireChance)
{
Name = EItemAttributeId.MalfMisfireChance.GetName(),
Base = () => instance.MalfMisfireChance,
StringValue = () =>
{
float maxMalfMisfireChance = (float)MaxMalfMisfireChance.GetValue(null);
int index = instance.MalfMisfireChance <= 0f ? 0
: instance.MalfMisfireChance < 3f * maxMalfMisfireChance / 7f ? 1
: instance.MalfMisfireChance < 4f * maxMalfMisfireChance / 7f ? 2
: instance.MalfMisfireChance < 5f * maxMalfMisfireChance / 7f ? 3
: instance.MalfMisfireChance < 6f * maxMalfMisfireChance / 7f ? 4
: 5;
return MalfChancesKeys[index].Localized();
},
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.MalfFeedChance)
{
Name = EItemAttributeId.MalfFeedChance.GetName(),
Base = () => instance.MalfFeedChance,
StringValue = () =>
{
float maxMalfFeedChance = (float)MaxMalfFeedChance.GetValue(null);
int index = instance.MalfFeedChance <= 0f ? 0
: instance.MalfFeedChance < 1f * maxMalfFeedChance / 7f ? 1
: instance.MalfFeedChance < 3f * maxMalfFeedChance / 7f ? 2
: instance.MalfFeedChance < 5f * maxMalfFeedChance / 7f ? 3
: instance.MalfFeedChance < 6f * maxMalfFeedChance / 7f ? 4
: 5;
return MalfChancesKeys[index].Localized();
},
DisplayType = () => EItemAttributeDisplayType.Compact,
}); });
} }

View File

@ -3,15 +3,12 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net472</TargetFramework> <TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.MunitionsExpert</AssemblyName> <AssemblyName>IcyClawz.MunitionsExpert</AssemblyName>
<Version>1.2.1</Version> <Version>1.5.0</Version>
<RootNamespace>IcyClawz.MunitionsExpert</RootNamespace> <RootNamespace>IcyClawz.MunitionsExpert</RootNamespace>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Aki.Reflection">
<HintPath>..\Shared\Aki.Reflection.dll</HintPath>
</Reference>
<Reference Include="Assembly-CSharp"> <Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp.dll</HintPath> <HintPath>..\Shared\Assembly-CSharp.dll</HintPath>
</Reference> </Reference>
@ -24,6 +21,9 @@
<Reference Include="Sirenix.Serialization"> <Reference Include="Sirenix.Serialization">
<HintPath>..\Shared\Sirenix.Serialization.dll</HintPath> <HintPath>..\Shared\Sirenix.Serialization.dll</HintPath>
</Reference> </Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine"> <Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath> <HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference> </Reference>

View File

@ -1,10 +1,10 @@
using Aki.Reflection.Patching;
using BepInEx; using BepInEx;
using BepInEx.Configuration; using BepInEx.Configuration;
using EFT.HandBook; using EFT.HandBook;
using EFT.InventoryLogic; using EFT.InventoryLogic;
using EFT.UI; using EFT.UI;
using EFT.UI.DragAndDrop; using EFT.UI.DragAndDrop;
using SPT.Reflection.Patching;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
@ -13,24 +13,24 @@ using UnityEngine.UI;
namespace IcyClawz.MunitionsExpert; namespace IcyClawz.MunitionsExpert;
[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.2.1")] [BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.5.0")]
public class Plugin : BaseUnityPlugin public class Plugin : BaseUnityPlugin
{ {
private static ConfigEntry<bool> ColorizeSwitch { get; set; } private static ConfigEntry<bool> ColorizeConfig { get; set; }
private static ConfigEntry<ColorName>[] ArmorClassColors { get; set; } private static ConfigEntry<ColorName>[] ArmorClassColorConfigs { get; set; }
private void Awake() private void Awake()
{ {
const string COLORIZE = "Colorize Icon Backgrounds"; const string SECTION = "Colorize Icon Backgrounds";
ColorizeSwitch = Config.Bind(COLORIZE, "", true, new ConfigurationManagerAttributes { Order = 7 }); ColorizeConfig = Config.Bind(SECTION, "", true, new ConfigurationManagerAttributes { Order = 7 });
ArmorClassColors = [ ArmorClassColorConfigs = [
Config.Bind(COLORIZE, "Unarmored", ColorName.Purple, new ConfigurationManagerAttributes { Order = 6 }), Config.Bind(SECTION, "Unarmored", ColorName.Purple, new ConfigurationManagerAttributes { Order = 6 }),
Config.Bind(COLORIZE, "Class 1", ColorName.Blue, new ConfigurationManagerAttributes { Order = 5 }), Config.Bind(SECTION, "Class 1", ColorName.Blue, new ConfigurationManagerAttributes { Order = 5 }),
Config.Bind(COLORIZE, "Class 2", ColorName.Cyan, new ConfigurationManagerAttributes { Order = 4 }), Config.Bind(SECTION, "Class 2", ColorName.Cyan, new ConfigurationManagerAttributes { Order = 4 }),
Config.Bind(COLORIZE, "Class 3", ColorName.Green, new ConfigurationManagerAttributes { Order = 3 }), Config.Bind(SECTION, "Class 3", ColorName.Green, new ConfigurationManagerAttributes { Order = 3 }),
Config.Bind(COLORIZE, "Class 4", ColorName.Yellow, new ConfigurationManagerAttributes { Order = 2 }), Config.Bind(SECTION, "Class 4", ColorName.Yellow, new ConfigurationManagerAttributes { Order = 2 }),
Config.Bind(COLORIZE, "Class 5", ColorName.Orange, new ConfigurationManagerAttributes { Order = 1 }), Config.Bind(SECTION, "Class 5", ColorName.Orange, new ConfigurationManagerAttributes { Order = 1 }),
Config.Bind(COLORIZE, "Class 6+", ColorName.Red, new ConfigurationManagerAttributes { Order = 0 }) Config.Bind(SECTION, "Class 6+", ColorName.Red, new ConfigurationManagerAttributes { Order = 0 }),
]; ];
new StaticIconsPatch().Enable(); new StaticIconsPatch().Enable();
new AmmoTemplatePatch().Enable(); new AmmoTemplatePatch().Enable();
@ -38,12 +38,12 @@ public class Plugin : BaseUnityPlugin
new EntityIconPatch().Enable(); new EntityIconPatch().Enable();
} }
internal static bool Colorize => ColorizeSwitch.Value; internal static bool Colorize => ColorizeConfig.Value;
internal static Color GetArmorClassColor(int index) internal static Color GetArmorClassColor(int index)
{ {
index = Mathf.Clamp(index, 0, ArmorClassColors.Length - 1); index = Mathf.Clamp(index, 0, ArmorClassColorConfigs.Length - 1);
return ColorCache.Get(ArmorClassColors[index].Value); return ColorCache.Get(ArmorClassColorConfigs[index].Value);
} }
} }
@ -82,14 +82,14 @@ internal class ItemViewPatch : ModulePatch
typeof(ItemView).GetField("BackgroundColor", BindingFlags.NonPublic | BindingFlags.Instance); typeof(ItemView).GetField("BackgroundColor", BindingFlags.NonPublic | BindingFlags.Instance);
protected override MethodBase GetTargetMethod() => protected override MethodBase GetTargetMethod() =>
typeof(ItemView).GetMethod("UpdateColor", BindingFlags.NonPublic | BindingFlags.Instance); typeof(ItemView).GetMethod("UpdateColor", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix] [PatchPrefix]
private static void PatchPrefix(ref ItemView __instance) private static void PatchPrefix(ref ItemView __instance)
{ {
if (!Plugin.Colorize || __instance.Item is not BulletClass bullet || bullet.PenetrationPower <= 0) if (!Plugin.Colorize || __instance.Item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return; return;
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass(); int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
BackgroundColorField.SetValue(__instance, Plugin.GetArmorClassColor(armorClass)); BackgroundColorField.SetValue(__instance, Plugin.GetArmorClassColor(armorClass));
} }
} }
@ -105,10 +105,11 @@ internal class EntityIconPatch : ModulePatch
[PatchPostfix] [PatchPostfix]
private static void PatchPostfix(ref EntityIcon __instance, Item item) private static void PatchPostfix(ref EntityIcon __instance, Item item)
{ {
if (!Plugin.Colorize || item is not BulletClass bullet || bullet.PenetrationPower <= 0) if (!Plugin.Colorize || item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return; return;
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass(); if (ColorPanelField.GetValue(__instance) is not Image image)
if (ColorPanelField.GetValue(__instance) is Image image) return;
int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
image.color = Plugin.GetArmorClassColor(armorClass); image.color = Plugin.GetArmorClassColor(armorClass);
} }
} }

View File

@ -70,16 +70,6 @@ internal class Resources {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Damage {
get {
object obj = ResourceManager.GetObject("Damage", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>
@ -90,16 +80,6 @@ internal class Resources {
} }
} }
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap PenetrationPower {
get {
object obj = ResourceManager.GetObject("PenetrationPower", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary> /// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap. /// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary> /// </summary>

View File

@ -121,15 +121,9 @@
<data name="ArmorDamage" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="ArmorDamage" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\ArmorDamage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\ArmorDamage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="Damage" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Damage.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="FragmentationChance" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="FragmentationChance" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\FragmentationChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\FragmentationChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>
<data name="PenetrationPower" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\PenetrationPower.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="RicochetChance" type="System.Resources.ResXFileRef, System.Windows.Forms"> <data name="RicochetChance" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\RicochetChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> <value>..\Resources\RicochetChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data> </data>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B