Compare commits

...

11 Commits

31 changed files with 1327 additions and 1389 deletions

60
ClientMods.sln Normal file
View File

@ -0,0 +1,60 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomInteractions", "CustomInteractions\CustomInteractions.csproj", "{F296FF55-A8D8-47E3-90C0-6975CA0787F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomInteractions.Prepatch", "CustomInteractions.Prepatch\CustomInteractions.Prepatch.csproj", "{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ItemAttributeFix", "ItemAttributeFix\ItemAttributeFix.csproj", "{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ItemContextMenuExt", "ItemContextMenuExt\ItemContextMenuExt.csproj", "{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ItemSellPrice", "ItemSellPrice\ItemSellPrice.csproj", "{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MagazineInspector", "MagazineInspector\MagazineInspector.csproj", "{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MunitionsExpert", "MunitionsExpert\MunitionsExpert.csproj", "{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Release|Any CPU.Build.0 = Release|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Release|Any CPU.Build.0 = Release|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Release|Any CPU.Build.0 = Release|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Release|Any CPU.Build.0 = Release|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Release|Any CPU.Build.0 = Release|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Release|Any CPU.Build.0 = Release|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BF50C8C8-82A6-496A-B4A4-DB383A88705D}
EndGlobalSection
EndGlobal

View File

@ -3,8 +3,9 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.CustomInteractions.Prepatch</AssemblyName>
<Version>1.2.0</Version>
<Version>1.6.0</Version>
<RootNamespace>IcyClawz.CustomInteractions</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -2,37 +2,23 @@ using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
namespace IcyClawz.CustomInteractions
{
namespace IcyClawz.CustomInteractions;
public static class Prepatch
{
public static IEnumerable<string> TargetDLLs => new[] { "Assembly-CSharp.dll" };
public static IEnumerable<string> TargetDLLs => ["Assembly-CSharp.dll"];
public static void Patch(ref AssemblyDefinition assembly)
{
var type = assembly.MainModule.GetType("GClass2900"); // DynamicInteraction
if (type != null)
{
type.IsSealed = false;
var field = type.Fields.SingleOrDefault(c => c.Name == "action_0");
if (field != null)
public static void Patch(AssemblyDefinition assembly)
{
TypeDefinition type = assembly.MainModule.GetType("DynamicInteractionClass");
FieldDefinition field = type.Fields.SingleOrDefault(c => c.Name is "action_0");
field.IsFamily = true;
field.IsInitOnly = false;
}
var ctor = type.Methods.SingleOrDefault(c => c.Name == ".ctor");
if (ctor != null)
{
var param = ctor.Parameters.SingleOrDefault(c => c.Name == "callback");
if (param != null)
{
MethodDefinition ctor = type.Methods.SingleOrDefault(c => c.Name is ".ctor");
ParameterDefinition param = ctor.Parameters.SingleOrDefault(c => c.Name is "callback");
param.IsOptional = true;
param.HasDefault = true;
param.Constant = null;
}
}
}
//assembly.Write("Assembly-CSharp-CustomInteractions.dll");
}
}
}

View File

@ -1,30 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomInteractions", "CustomInteractions\CustomInteractions.csproj", "{F296FF55-A8D8-47E3-90C0-6975CA0787F9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomInteractions.Prepatch", "CustomInteractions.Prepatch\Prepatch.csproj", "{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F296FF55-A8D8-47E3-90C0-6975CA0787F9}.Release|Any CPU.Build.0 = Release|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E8C5359B-95CE-4547-B72E-C9BEBB9220CC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BF50C8C8-82A6-496A-B4A4-DB383A88705D}
EndGlobalSection
EndGlobal

View File

@ -1,124 +1,73 @@
using Comfort.Common;
using EFT.InventoryLogic;
using EFT.UI;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;
using DynamicInteraction = GClass2900;
using EmptyInteractions = GClass2901<System.Enum>;
using EmptyInteractionsAbstractClass = GClass3423;
namespace IcyClawz.CustomInteractions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public interface ICustomInteractionsProvider { } // Do not implement this directly
namespace IcyClawz.CustomInteractions;
public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider
public interface ICustomInteractionsProvider
{
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item);
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext context, EItemViewType viewType, Item item);
}
public static class CustomInteractionsManager
{
internal static readonly List<ICustomInteractionsProvider> Providers = new List<ICustomInteractionsProvider>();
internal static readonly LinkedList<ICustomInteractionsProvider> Providers = [];
public static void Register(ICustomInteractionsProvider provider)
{
if (!Providers.Contains(provider))
{
Providers.Add(provider);
}
Providers.AddLast(provider);
}
}
public class CustomInteraction
public class CustomInteraction(ItemUiContext context)
{
internal readonly CustomInteractionImpl Impl;
public CustomInteraction()
{
Impl = new CustomInteractionImpl();
}
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<Sprite> Icon { get => Impl.Icon; set => Impl.Icon = 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<string> Error { get => Impl.Error; set => Impl.Error = value; }
}
internal sealed class CustomInteractionImpl : DynamicInteraction
internal sealed class CustomInteractionImpl(ItemUiContext context, string id) : DynamicInteractionClass(id, id)
{
public CustomInteractionImpl()
: base(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4")) { }
internal readonly ItemUiContext Context = context;
public Func<string> Caption { get; set; }
public new Func<Sprite> Icon { get; set; }
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<string> Error { get; set; }
public bool IsInteractive() =>
Enabled?.Invoke() ?? true;
public bool IsInteractive() => Enabled?.Invoke() ?? true;
}
public abstract class CustomSubInteractions
internal sealed class CustomInteractionsImpl(ItemUiContext context) : EmptyInteractionsAbstractClass(context)
{
internal readonly CustomSubInteractionsImpl Impl;
public CustomSubInteractions(ItemUiContext uiContext)
{
Impl = new CustomSubInteractionsImpl(uiContext);
}
public bool ExaminationRequired { get => Impl._ExaminationRequired; set => Impl._ExaminationRequired = value; }
public void Add(CustomInteraction customInteraction) =>
Impl.AddCustomInteraction(customInteraction);
public void Remove(CustomInteraction customInteraction) =>
Impl.RemoveCustomInteraction(customInteraction);
public void CallRedraw() =>
Impl.CallRedraw();
public void CallRedraw(string templateId) =>
Impl.CallRedraw(templateId);
}
internal sealed class CustomSubInteractionsImpl : EmptyInteractions
{
public CustomSubInteractionsImpl(ItemUiContext uiContext)
: base(uiContext) { }
public IEnumerable<CustomInteractionImpl> CustomInteractions => DynamicInteractions.OfType<CustomInteractionImpl>();
public bool _ExaminationRequired { get; set; } = true;
public override bool ExaminationRequired => _ExaminationRequired;
public override bool HasIcons => CustomInteractions.Any(customInteraction => customInteraction.Icon != null);
public void CallRedraw() =>
itemUiContext_0.RedrawContextMenus(null);
public override bool HasIcons => CustomInteractions.Any(interaction => interaction.Icon is not null);
}
internal static class AbstractInteractionsExtensions
{
private static Dictionary<string, DynamicInteraction> GetDynamicInteractions<T>(this GClass2901<T> instance) where T : Enum =>
typeof(GClass2901<T>).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(instance) as Dictionary<string, DynamicInteraction>;
private static Dictionary<string, DynamicInteractionClass> GetDynamicInteractions<T>(this ItemInfoInteractionsAbstractClass<T> instance) where T : struct, Enum =>
typeof(ItemInfoInteractionsAbstractClass<T>).GetField("dictionary_0", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(instance) as Dictionary<string, DynamicInteractionClass>;
public static void AddCustomInteraction<T>(this GClass2901<T> instance, CustomInteraction customInteraction) where T : Enum =>
instance.GetDynamicInteractions()[customInteraction.Impl.Key] = customInteraction.Impl;
public static void RemoveCustomInteraction<T>(this GClass2901<T> instance, CustomInteraction customInteraction) where T : Enum =>
instance.GetDynamicInteractions().Remove(customInteraction.Impl.Key);
public static void AddCustomInteraction<T>(this ItemInfoInteractionsAbstractClass<T> instance, CustomInteractionImpl impl) where T : struct, Enum =>
instance.GetDynamicInteractions()[impl.Key] = impl;
}
internal static class InteractionButtonsContainerExtensions
@ -142,65 +91,62 @@ namespace IcyClawz.CustomInteractions
CurrentButtonField.SetValue(instance, button);
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,
string key, string caption, SimpleContextMenuButton template, RectTransform container,
[CanBeNull] Sprite sprite, [CanBeNull] Action onButtonClicked, [CanBeNull] Action onMouseHover,
bool subMenu = false, bool autoClose = true) =>
(SimpleContextMenuButton)CreateButtonMethod.Invoke(instance, new object[] {
(SimpleContextMenuButton)CreateButtonMethod.Invoke(instance, [
key, caption, template, container, sprite, onButtonClicked, onMouseHover, subMenu, autoClose
});
]);
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) =>
CloseSubMenuMethod.Invoke(instance, null);
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) =>
AddButtonMethod.Invoke(instance, new object[] { button });
AddButtonMethod.Invoke(instance, [button]);
public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl customInteractionImpl)
public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl impl)
{
bool isInteractive = customInteractionImpl.IsInteractive();
bool isInteractive = impl.IsInteractive();
IEnumerable<CustomInteraction> subMenu = impl.SubMenu?.Invoke();
SimpleContextMenuButton button = null;
button = instance.CreateButton(
customInteractionImpl.Key,
customInteractionImpl.Caption?.Invoke() ?? string.Empty,
impl.Key,
impl.Caption?.Invoke() ?? "",
instance.GetButtonTemplate(),
instance.GetButtonsContainer(),
customInteractionImpl.Icon?.Invoke(),
impl.Icon?.Invoke(),
() =>
{
if (isInteractive)
{
customInteractionImpl.Execute();
}
impl.Execute();
},
() =>
{
instance.SetCurrentButton(button);
instance.CloseSubMenu();
if (isInteractive)
if (isInteractive && subMenu != null)
{
var subMenu = customInteractionImpl.SubMenu?.Invoke();
if (subMenu != null)
{
instance.SetSubInteractions(subMenu.Impl);
}
CustomInteractionsImpl subInteractions = new CustomInteractionsImpl(impl.Context);
foreach (CustomInteractionImpl subImpl in subMenu.Select(item => item.Impl))
subInteractions.AddCustomInteraction(subImpl);
instance.SetSubInteractions(subInteractions);
}
},
customInteractionImpl.SubMenu != null,
subMenu?.Any() ?? false,
false
);
button.SetButtonInteraction(
(isInteractive, customInteractionImpl.Error?.Invoke() ?? string.Empty)
isInteractive ? SuccessfulResult.New : new FailedResult(impl.Error?.Invoke() ?? "", 0)
);
instance.AddButton(button);
}
}
}

View File

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

View File

@ -1,16 +1,12 @@
using Aki.Reflection.Patching;
using BepInEx;
using EFT.UI;
using SPT.Reflection.Patching;
using System.Linq;
using System.Reflection;
using DynamicInteraction = GClass2900;
using ItemContext = GClass2710;
using ItemInfoInteractions = GClass2901<EFT.InventoryLogic.EItemInfoButton>;
namespace IcyClawz.CustomInteractions;
namespace IcyClawz.CustomInteractions
{
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.2.0")]
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.6.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake()
@ -26,18 +22,16 @@ namespace IcyClawz.CustomInteractions
typeof(ItemUiContext).GetMethod("GetItemContextInteractions", BindingFlags.Public | BindingFlags.Instance);
[PatchPostfix]
private static void Postfix(ref ItemInfoInteractions __result, 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 customInteractions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item);
if (customInteractions != null)
{
foreach (CustomInteraction customInteraction in customInteractions)
{
__result.AddCustomInteraction(customInteraction);
}
}
var interactions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item);
if (interactions is null)
continue;
foreach (CustomInteractionImpl impl in interactions.Select(interaction => interaction.Impl))
__result.AddCustomInteraction(impl);
}
}
}
@ -45,17 +39,16 @@ namespace IcyClawz.CustomInteractions
internal class InteractionButtonsContainerPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() =>
typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.NonPublic | BindingFlags.Instance);
typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix]
private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteraction interaction)
private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteractionClass interaction)
{
if (interaction is CustomInteractionImpl customInteractionImpl)
if (interaction is CustomInteractionImpl impl)
{
__instance.AddCustomButton(customInteractionImpl);
__instance.AddCustomButton(impl);
return false;
}
return true;
}
}
}

View File

@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{1A806857-2204-4EF9-ADDA-36F4C5688DC1}") = "ItemAttributeFix", "ItemAttributeFix\ItemAttributeFix.csproj", "{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3186B2D-F170-4E31-98BF-4CC8CDAD565D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BD4846A6-29F2-4AD6-91A5-7A0CA28D88CC}
EndGlobalSection
EndGlobal

View File

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

View File

@ -1,18 +1,16 @@
using Aki.Reflection.Patching;
using BepInEx;
using EFT.UI;
using SPT.Reflection.Patching;
using System.Reflection;
namespace IcyClawz.ItemAttributeFix
{
[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.1.0")]
namespace IcyClawz.ItemAttributeFix;
[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake()
{
private void Awake() =>
new CompactCharacteristicPanelPatch().Enable();
}
}
internal class CompactCharacteristicPanelPatch : ModulePatch
{
@ -28,8 +26,7 @@ namespace IcyClawz.ItemAttributeFix
[PatchPostfix]
private static void PatchPostfix(ref CompactCharacteristicPanel __instance)
{
ItemAttributeClass attribute = ItemAttributeField.GetValue(__instance) as ItemAttributeClass;
if (ItemAttributeField.GetValue(__instance) is ItemAttributeClass attribute)
StringField.SetValue(__instance, attribute.FullStringValue());
}
}
}

View File

@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{16C72132-089A-4D00-BDA9-5DFC76E223AC}") = "ItemContextMenuExt", "ItemContextMenuExt\ItemContextMenuExt.csproj", "{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F9FF8A66-A96D-45D2-BBD5-D976F101CCF7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B44F10FA-AB5A-4200-80B2-4ED636043F28}
EndGlobalSection
EndGlobal

View File

@ -5,96 +5,71 @@ using EFT.UI;
using IcyClawz.CustomInteractions;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using ILightTemplate = GInterface252;
using LightsState = GStruct154;
using ResourceCache = GClass2076;
using ILightTemplate = GInterface357;
using ISightTemplate = GInterface365;
using GlobalEvents = GClass3400;
namespace IcyClawz.ItemContextMenuExt
{
internal static class PlayerExtensions
{
private static readonly FieldInfo InventoryControllerField =
typeof(Player).GetField("_inventoryController", BindingFlags.NonPublic | BindingFlags.Instance);
namespace IcyClawz.ItemContextMenuExt;
public static InventoryControllerClass GetInventoryController(this Player player) =>
InventoryControllerField.GetValue(player) as InventoryControllerClass;
internal static class SightComponentExtensions
{
public static int[] GetScopeCalibrationDistances(this SightComponent instance, int scopeIndex) =>
((ISightTemplate)instance.Item.Template).CalibrationDistances[scopeIndex];
public static float[] GetScopeZooms(this SightComponent instance, int scopeIndex) =>
((ISightTemplate)instance.Item.Template).Zooms[scopeIndex];
}
internal static class LightComponentExtensions
{
public static int GetModesCount(this LightComponent component) =>
((ILightTemplate)component.Item.Template).ModesCount;
public static int GetModesCount(this LightComponent instance) =>
((ILightTemplate)instance.Item.Template).ModesCount;
}
internal sealed class CustomInteractionsProvider : IItemCustomInteractionsProvider
internal sealed class CustomInteractionsProvider : ICustomInteractionsProvider
{
internal const string IconsPrefix = "Characteristics/Icons/";
internal static StaticIcons StaticIcons => EFTHardSettings.Instance.StaticIcons;
private const string IconsPrefix = "Characteristics/Icons/";
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 != EItemViewType.Inventory)
if (viewType is EItemViewType.Inventory)
{
yield break;
FireModeComponent fireModeComponent = item.GetItemComponent<FireModeComponent>();
if (fireModeComponent is not null)
return GetFireModeInteractions(context, fireModeComponent);
SightComponent sightComponent = item.GetItemComponent<SightComponent>();
if (sightComponent is not null)
return GetSightInteractions(context, sightComponent);
LightComponent lightComponent = item.GetItemComponent<LightComponent>();
if (lightComponent is not null)
return GetLightInteractions(context, lightComponent);
}
return [];
}
private IEnumerable<CustomInteraction> GetFireModeInteractions(ItemUiContext context, FireModeComponent component)
{
var component = item.GetItemComponent<FireModeComponent>();
if (component != null)
if (component.AvailableEFireModes.Length > 1)
{
// Firing mode
yield return new CustomInteraction()
yield return new(context)
{
Caption = () => "Firing mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.Weapon),
SubMenu = () => new FireModeSubMenu(uiContext, component),
Enabled = () => component.AvailableEFireModes.Length > 1,
Error = () => "This weapon is incapable of selective fire"
SubMenu = () => GetFireModeSubInteractions(context, component),
};
yield break;
}
}
{
var component = item.GetItemComponent<LightComponent>();
if (component != null)
{
// Turn on/off
yield return new CustomInteraction()
{
Caption = () => (component.IsActive ? "TurnOff" : "TurnOn").Localized(),
Icon = () => ResourceCache.Pop<Sprite>(IconsPrefix + (component.IsActive ? "TurnOff" : "TurnOn")),
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetLightState(component, !component.IsActive, component.SelectedMode);
uiContext.RedrawContextMenus(new[] { item.TemplateId });
}
};
// Switch mode
yield return new CustomInteraction()
{
Caption = () => "Switch mode",
Icon = () => StaticIcons.GetAttributeIcon(EItemAttributeId.EncodeState),
SubMenu = () => new LightModeSubMenu(uiContext, component),
Enabled = () => component.GetModesCount() > 1,
Error = () => "This device has no alternative modes"
};
yield break;
}
}
}
}
internal class FireModeSubMenu : CustomSubInteractions
private IEnumerable<CustomInteraction> GetFireModeSubInteractions(ItemUiContext context, FireModeComponent component)
{
public FireModeSubMenu(ItemUiContext uiContext, FireModeComponent component)
: base(uiContext)
foreach (Weapon.EFireMode fireMode in component.AvailableEFireModes)
{
foreach (var fireMode in component.AvailableEFireModes)
{
Add(new CustomInteraction()
yield return new(context)
{
Caption = () => fireMode.ToString().Localized(),
Action = () =>
@ -102,29 +77,136 @@ namespace IcyClawz.ItemContextMenuExt
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
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)
: base(uiContext)
if (component.ScopesCount > 1)
{
foreach (var lightMode in Enumerable.Range(0, component.GetModesCount()))
yield return new(context)
{
Add(new CustomInteraction()
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}",
Action = () =>
{
Singleton<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
ComponentUtils.SetLightState(component, component.IsActive, lightMode);
ComponentUtils.SetLightMode(component, lightMode);
},
Enabled = () => lightMode != component.SelectedMode
});
Enabled = () => lightMode != component.SelectedMode,
};
}
}
}
@ -133,45 +215,111 @@ namespace IcyClawz.ItemContextMenuExt
{
public static void SetFireMode(FireModeComponent component, Weapon.EFireMode fireMode)
{
var player = GamePlayerOwner.MyPlayer;
if (player != null && player.HandsController is Player.FirearmController fc && component.Item == fc.Item)
Player player = GamePlayerOwner.MyPlayer;
if (player is not null && player.HandsController is Player.FirearmController fc && component.Item == fc.Item)
{
if (fc.Item.MalfState.State == Weapon.EMalfunctionState.None)
{
fc.ChangeFireMode(fireMode);
}
else
if (fc.Item.MalfState.State is not Weapon.EMalfunctionState.None)
{
fc.FirearmsAnimator.MisfireSlideUnknown(false);
player.GetInventoryController().ExamineMalfunction(fc.Item, false);
player.InventoryController.ExamineMalfunction(fc.Item, false);
return;
}
fc.ChangeFireMode(fireMode);
return;
}
component.SetFireMode(fireMode);
}
public static void SetLightState(LightComponent component, bool isActive, int lightMode)
public static void SetScopeIndex(SightComponent component, int scopeIndex)
{
var player = GamePlayerOwner.MyPlayer;
if (player != null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item))
{
var state = new LightsState
SetScopeState(component, new()
{
Id = component.Item.Id,
IsActive = isActive,
LightMode = lightMode
};
fc.SetLightsState(new[] { state });
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;
if (player is not null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item))
{
fc.SetScopeMode([scopeState]);
return;
}
component.IsActive = isActive;
component.SelectedMode = lightMode;
int scopeIndex = scopeState.ScopeIndexInsideSight;
component.SelectedScope = scopeIndex;
component.ScopesSelectedModes[scopeIndex] = scopeState.ScopeMode;
component.ScopesCurrentCalibPointIndexes[scopeIndex] = scopeState.ScopeCalibrationIndex;
}
if (player != null)
public static void SetLightActive(LightComponent component, bool isActive)
{
foreach (var tcvc in player.GetComponentsInChildren<TacticalComboVisualController>())
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)
{
foreach (TacticalComboVisualController tcvc in player.GetComponentsInChildren<TacticalComboVisualController>())
{
if (ReferenceEquals(tcvc.LightMod, component))
{
@ -182,4 +330,3 @@ namespace IcyClawz.ItemContextMenuExt
}
}
}
}

View File

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

View File

@ -1,14 +1,35 @@
using BepInEx;
using BepInEx.Configuration;
using EFT.UI;
using IcyClawz.CustomInteractions;
using SPT.Reflection.Patching;
using System.Reflection;
namespace IcyClawz.ItemContextMenuExt
{
[BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.1.0")]
namespace IcyClawz.ItemContextMenuExt;
[BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.6.0")]
[BepInDependency("com.IcyClawz.CustomInteractions")]
public class Plugin : BaseUnityPlugin
{
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());
}
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,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{1A9E4EC8-A583-446A-8918-2608C278F194}") = "ItemSellPrice", "ItemSellPrice\ItemSellPrice.csproj", "{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA2EAB19-7A7F-452F-80B3-DEAD18E43D88}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B6B3C802-40C3-4FF0-9BAA-1DAC503CA619}
EndGlobalSection
EndGlobal

View File

@ -1,20 +1,21 @@
using Aki.Reflection.Utils;
using Comfort.Common;
using EFT;
using EFT.InventoryLogic;
using SPT.Reflection.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using CurrencyUtil = GClass2424;
using CurrencyUtil = GClass2867;
namespace IcyClawz.ItemSellPrice;
namespace IcyClawz.ItemSellPrice
{
internal static class TraderClassExtensions
{
private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession();
private static ISession _Session;
private static ISession Session => _Session ??= ClientAppUtils.GetMainApp().GetClientBackEndSession();
private static readonly FieldInfo SupplyDataField =
typeof(TraderClass).GetField("supplyData_0", BindingFlags.NonPublic | BindingFlags.Instance);
@ -28,185 +29,116 @@ namespace IcyClawz.ItemSellPrice
public static async void UpdateSupplyData(this TraderClass trader)
{
Result<SupplyData> result = await Session.GetSupplyData(trader.Id);
if (result.Failed)
{
Debug.LogError("Failed to download supply data");
return;
}
if (result.Succeed)
trader.SetSupplyData(result.Value);
else
Debug.LogError("Failed to download supply data");
}
}
internal static class ItemExtensions
{
private static readonly Dictionary<string, string[]> DisplayNames = new Dictionary<string, string[]>()
private static readonly Dictionary<string, string[]> DisplayNames = new()
{
{ "ch", new[] { "售价({0}", "无法出售给商人" } },
{ "cz", new[] { "Prodejní cena ({0})", "Nemůže být prodán obchodníkům" } },
{ "en", new[] { "Selling price ({0})", "Cannot be sold to traders" } },
{ "es", new[] { "Precio de venta ({0})", "No puede ser vendido a los vendedores" } },
{ "es-mx", new[] { "Precio de venta ({0})", "No puede ser vendido a comerciantes" } },
{ "fr", new[] { "Prix de vente ({0})", "Ne peut pas être vendu aux marchands" } },
{ "ge", new[] { "Verkaufspreis ({0})", "Kann nicht an Händler verkauft werden" } },
{ "hu", new[] { "Eladási ár ({0})", "Kereskedőknek nem adható el" } },
{ "it", new[] { "Prezzo di vendita ({0})", "Non vendibile ai mercanti" } },
{ "jp", new[] { "販売価格({0}", "トレーダーには販売できません" } },
{ "kr", new[] { "판매 가격 ({0})", "상인에게 판매 불가" } },
{ "pl", new[] { "Cena sprzedaży ({0})", "Nie można sprzedawać handlarzom" } },
{ "po", new[] { "Preço de venda ({0})", "Não pode ser vendido a comerciantes" } },
{ "ru", new[] { "Цена продажи ({0})", "Невозможно продать торговцам" } },
{ "sk", new[] { "Predajná cena ({0})", "Nedá sa predať obchodníkom" } },
{ "tu", new[] { "Satış fiyatı ({0})", "Tüccarlara satılamaz" } }
["ch"] = ["售价({0}", "无法出售给商人"],
["cz"] = ["Prodejní cena ({0})", "Nemůže být prodán obchodníkům"],
["en"] = ["Selling price ({0})", "Cannot be sold to traders"],
["es"] = ["Precio de venta ({0})", "No puede ser vendido a los vendedores"],
["es-mx"] = ["Precio de venta ({0})", "No puede ser vendido a comerciantes"],
["fr"] = ["Prix de vente ({0})", "Ne peut pas être vendu aux marchands"],
["ge"] = ["Verkaufspreis ({0})", "Kann nicht an Händler verkauft werden"],
["hu"] = ["Eladási ár ({0})", "Kereskedőknek nem adható el"],
["it"] = ["Prezzo di vendita ({0})", "Non vendibile ai mercanti"],
["jp"] = ["販売価格({0}", "トレーダーには販売できません"],
["kr"] = ["판매 가격 ({0})", "상인에게 판매 불가"],
["pl"] = ["Cena sprzedaży ({0})", "Nie można sprzedawać handlarzom"],
["po"] = ["Preço de venda ({0})", "Não pode ser vendido a comerciantes"],
["ru"] = ["Цена продажи ({0})", "Невозможно продать торговцам"],
["sk"] = ["Predajná cena ({0})", "Nedá sa predať obchodníkom"],
["tu"] = ["Satış fiyatı ({0})", "Tüccarlara satılamaz"],
};
private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession();
private static ISession _Session;
private static ISession Session => _Session ??= ClientAppUtils.GetMainApp().GetClientBackEndSession();
public static void AddTraderOfferAttribute(this Item item)
{
ItemAttributeClass attribute = new ItemAttributeClass(EItemAttributeId.MoneySum)
ItemAttributeClass attribute = new(EItemAttributeId.MoneySum)
{
Name = EItemAttributeId.MoneySum.GetName(),
DisplayNameFunc = () =>
{
string language = Singleton<SharedGameSettingsClass>.Instance?.Game?.Settings?.Language?.GetValue();
if (language == null || !DisplayNames.ContainsKey(language))
{
if (language is null || !DisplayNames.ContainsKey(language))
language = "en";
}
if (GetBestTraderOffer(item) is TraderOffer offer)
{
return string.Format(DisplayNames[language][0], offer.Name);
}
else
{
return DisplayNames[language][1];
}
TraderOffer offer = GetBestTraderOffer(item);
return offer is not null
? string.Format(DisplayNames[language][0], offer.Name)
: DisplayNames[language][1];
},
Base = () =>
{
if (GetBestTraderOffer(item) is TraderOffer offer)
{
return offer.Price;
}
else
{
return 0.01f;
}
TraderOffer offer = GetBestTraderOffer(item);
return offer is not null ? offer.Price : 0.01f;
},
StringValue = () =>
{
if (GetBestTraderOffer(item) is TraderOffer offer)
{
string value = $"{offer.Currency} {offer.Price}";
if (offer.Count > 1)
{
value += $" ({offer.Count})";
}
return value;
}
else
{
return string.Empty;
}
TraderOffer offer = GetBestTraderOffer(item);
return offer is not null
? $"{offer.Currency} {offer.Price}" + (offer.Count > 1 ? $" ({offer.Count})" : "")
: "";
},
FullStringValue = () =>
{
if (GetAllTraderOffers(item) is List<TraderOffer> offers)
{
string[] lines = new string[offers.Count];
for (int i = 0; i < offers.Count; i++)
{
TraderOffer offer = offers[i];
lines[i] = $"{offer.Name}: {offer.Currency} {offer.Price}";
}
return string.Join(Environment.NewLine, lines);
}
else
{
return string.Empty;
}
IEnumerable<TraderOffer> offers = GetAllTraderOffers(item);
return offers.Any()
? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}"))
: "";
},
DisplayType = () => EItemAttributeDisplayType.Compact
DisplayType = () => EItemAttributeDisplayType.Compact,
};
List<ItemAttributeClass> attributes = new List<ItemAttributeClass> { attribute };
attributes.AddRange(item.Attributes);
item.Attributes = attributes;
item.Attributes = [attribute, .. item.Attributes];
}
private sealed class TraderOffer
private sealed class TraderOffer(string name, int price, string currency, double course, int count)
{
public string Name;
public int Price;
public string Currency;
public double Course;
public int Count;
public TraderOffer(string name, int price, string currency, double course, int count)
{
Name = name;
Price = price;
Currency = currency;
Course = course;
Count = count;
}
public string Name = name;
public int Price = price;
public string Currency = currency;
public double Course = course;
public int Count = count;
}
private static TraderOffer GetTraderOffer(Item item, TraderClass trader)
{
var result = trader.GetUserItemPrice(item);
if (result == null)
{
return null;
}
return new TraderOffer(
var price = trader.GetUserItemPrice(item);
return price.HasValue ? new(
trader.LocalizedName,
result.Value.Amount,
CurrencyUtil.GetCurrencyCharById(result.Value.CurrencyId),
trader.GetSupplyData().CurrencyCourses[result.Value.CurrencyId],
price.Value.Amount,
CurrencyUtil.GetCurrencyCharById(price.Value.CurrencyId.Value),
trader.GetSupplyData().CurrencyCourses[price.Value.CurrencyId.Value],
item.StackObjectsCount
);
) : null;
}
private static List<TraderOffer> GetAllTraderOffers(Item item)
private static IEnumerable<TraderOffer> GetAllTraderOffers(Item item)
{
if (!Session.Profile.Examined(item))
{
return null;
}
switch (item.Owner?.OwnerType)
{
case EOwnerType.RagFair:
case EOwnerType.Trader:
if (item.StackObjectsCount > 1 || item.UnlimitedCount)
return [];
if (item.Owner?.OwnerType is EOwnerType.RagFair or EOwnerType.Trader
&& (item.StackObjectsCount > 1 || item.UnlimitedCount))
{
item = item.CloneItem();
item.StackObjectsCount = 1;
item.UnlimitedCount = false;
}
break;
}
List<TraderOffer> offers = new List<TraderOffer>();
foreach (TraderClass trader in Session.Traders)
{
if (GetTraderOffer(item, trader) is TraderOffer offer)
{
offers.Add(offer);
}
}
offers.Sort((x, y) => (y.Price * y.Course).CompareTo(x.Price * x.Course));
return offers;
return Session.Traders
.Where(trader => !trader.Settings.AvailableInRaid)
.Select(trader => GetTraderOffer(item, trader))
.Where(offer => offer is not null)
.OrderByDescending(offer => offer.Price * offer.Course);
}
private static TraderOffer GetBestTraderOffer(Item item)
{
if (GetAllTraderOffers(item) is List<TraderOffer> offers)
{
return offers.FirstOrDefault();
}
else
{
return null;
}
}
}
private static TraderOffer GetBestTraderOffer(Item item) =>
GetAllTraderOffers(item).FirstOrDefault();
}

View File

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

View File

@ -1,20 +1,20 @@
using Aki.Reflection.Patching;
using BepInEx;
using EFT;
using EFT.InventoryLogic;
using SPT.Reflection.Patching;
using System.Reflection;
namespace IcyClawz.ItemSellPrice
{
[BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.1.0")]
namespace IcyClawz.ItemSellPrice;
[BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake()
{
new TraderPatch().Enable();
new ItemPatch().Enable();
new AmmoPatch().Enable();
new GrenadePatch().Enable();
new SecureContainerPatch().Enable();
new AmmoItemPatch().Enable();
new ThrowWeapItemPatch().Enable();
}
}
@ -24,11 +24,9 @@ namespace IcyClawz.ItemSellPrice
typeof(TraderClass).GetConstructors()[0];
[PatchPostfix]
private static void PatchPostfix(ref TraderClass __instance)
{
private static void PatchPostfix(ref TraderClass __instance) =>
__instance.UpdateSupplyData();
}
}
internal class ItemPatch : ModulePatch
{
@ -36,45 +34,26 @@ namespace IcyClawz.ItemSellPrice
typeof(Item).GetConstructors()[0];
[PatchPostfix]
private static void PatchPostfix(ref Item __instance)
{
private static void PatchPostfix(ref Item __instance) =>
__instance.AddTraderOfferAttribute();
}
}
internal class AmmoPatch : ModulePatch
internal class AmmoItemPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() =>
typeof(BulletClass).GetConstructors()[0];
typeof(AmmoItemClass).GetConstructors()[0];
[PatchPostfix]
private static void PatchPostfix(ref BulletClass __instance)
{
private static void PatchPostfix(ref AmmoItemClass __instance) =>
__instance.AddTraderOfferAttribute();
}
}
internal class GrenadePatch : ModulePatch
internal class ThrowWeapItemPatch : ModulePatch
{
protected override MethodBase GetTargetMethod() =>
typeof(GrenadeClass).GetConstructors()[0];
typeof(ThrowWeapItemClass).GetConstructors()[0];
[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();
}
}
}

View File

@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{1528224F-F248-466A-95DF-C841EA46D89E}") = "MagazineInspector", "MagazineInspector\MagazineInspector.csproj", "{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F4F0DAB6-3A97-4656-A61F-945D63BAB4F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BEB55B48-4A92-4A7E-8B4E-58FFADC9FE2C}
EndGlobalSection
EndGlobal

View File

@ -1,123 +1,80 @@
using Aki.Reflection.Utils;
using Comfort.Common;
using EFT;
using EFT.InventoryLogic;
using SPT.Reflection.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using InGameStatus = GClass1819;
using InGameStatus = GClass2064;
namespace IcyClawz.MagazineInspector;
namespace IcyClawz.MagazineInspector
{
internal static class MagazineClassExtensions
{
private static readonly Dictionary<string, string> DisplayNames = new Dictionary<string, string>()
private static readonly Dictionary<string, string> DisplayNames = new()
{
{ "ch", "上膛弹药" },
{ "cz", "Nabité střelivo" },
{ "en", "Loaded ammo" },
{ "es", "Munición cargada" },
{ "es-mx", "Munición cargada" },
{ "fr", "Munitions chargées" },
{ "ge", "Geladene Munition" },
{ "hu", "Töltött lőszer" },
{ "it", "Munizioni caricate" },
{ "jp", "装填弾薬" },
{ "kr", "장전 탄약" },
{ "pl", "Załadowana amunicja" },
{ "po", "Munição carregada" },
{ "ru", "Заряженные боеприпасы" },
{ "sk", "Nabitá munícia" },
{ "tu", "Yüklü mühimmat" }
["ch"] = "上膛弹药",
["cz"] = "Nabité střelivo",
["en"] = "Loaded ammo",
["es"] = "Munición cargada",
["es-mx"] = "Munición cargada",
["fr"] = "Munitions chargées",
["ge"] = "Geladene Munition",
["hu"] = "Töltött lőszer",
["it"] = "Munizioni caricate",
["jp"] = "装填弾薬",
["kr"] = "장전 탄약",
["pl"] = "Załadowana amunicja",
["po"] = "Munição carregada",
["ru"] = "Заряженные боеприпасы",
["sk"] = "Nabitá munícia",
["tu"] = "Yüklü mühimmat",
};
private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession();
private static ISession _Session;
private static ISession Session => _Session ??= ClientAppUtils.GetMainApp().GetClientBackEndSession();
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);
if (attribute == null)
{
if (attribute is null)
return;
}
attribute.DisplayNameFunc = () =>
{
string language = Singleton<SharedGameSettingsClass>.Instance?.Game?.Settings?.Language?.GetValue();
if (language == null || !DisplayNames.ContainsKey(language))
{
if (language is null || !DisplayNames.ContainsKey(language))
language = "en";
}
return DisplayNames[language];
};
attribute.Base = () =>
{
if (GetAmmoCount(magazine, ActiveProfile, out _) is int ammoCount)
{
return ammoCount;
}
else
{
return 0f;
}
int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out _);
return ammoCount ?? 0f;
};
attribute.StringValue = () =>
{
string value;
if (GetAmmoCount(magazine, ActiveProfile, out _) is int ammoCount)
{
value = ammoCount.ToString();
}
else
{
value = "?";
}
return $"{value}/{magazine.MaxCount}";
int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out _);
return $"{ammoCount?.ToString() ?? "?"}/{magazine.MaxCount}";
};
attribute.FullStringValue = () =>
{
Profile profile = ActiveProfile;
int? ammoCount = GetAmmoCount(magazine, profile, out bool magChecked);
if (magChecked)
int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out bool magChecked);
return magChecked
? string.Join(Environment.NewLine, magazine.Cartridges.Items.Reverse().Select(cartridge =>
{
List<Item> cartridges = new List<Item>(magazine.Cartridges.Items);
string[] lines = new string[cartridges.Count];
int i = cartridges.Count - 1;
foreach (Item cartridge in cartridges)
{
string count;
if (ammoCount != null)
{
count = cartridge.StackObjectsCount.ToString();
}
else
{
count = "?";
}
string name;
if (profile.Examined(cartridge))
{
name = cartridge.LocalizedName();
}
else
{
name = "Unknown item".Localized();
}
lines[i--] = $"{count} × {name}";
}
return string.Join(Environment.NewLine, lines);
}
else
{
return string.Empty;
}
string count = ammoCount is not null ? cartridge.StackObjectsCount.ToString() : "?";
string name = ActiveProfile.Examined(cartridge) ? cartridge.LocalizedName() : "Unknown item".Localized();
return $"{count} × {name}";
}))
: "";
};
}
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)
{
@ -125,24 +82,12 @@ namespace IcyClawz.MagazineInspector
return magazine.Count;
}
magChecked = profile.CheckedMagazines.ContainsKey(magazine.Id);
if (magChecked)
{
if (!magChecked)
return null;
bool equipped = profile.Inventory.Equipment.GetAllSlots().Any(slot => ReferenceEquals(slot.ContainedItem, magazine));
if (magazine.Count >= (equipped ? magazine.MaxCount - 1 : magazine.MaxCount))
{
return magazine.Count;
}
int skill = Mathf.Max(
profile.MagDrillsMastering,
profile.CheckedMagazineSkillLevel(magazine.Id),
magazine.CheckOverride
);
if (skill > 1 || (skill == 1 && magazine.MaxCount <= 10))
{
return magazine.Count;
}
}
return null;
}
int skill = Mathf.Max(profile.MagDrillsMastering, profile.CheckedMagazineSkillLevel(magazine.Id), magazine.CheckOverride);
return skill > 1 || (skill == 1 && magazine.MaxCount <= 10) ? magazine.Count : null;
}
}

View File

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

View File

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

View File

@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.2.32616.157
MinimumVisualStudioVersion = 10.0.40219.1
Project("{12CC1CB2-4C8C-40A8-834F-237AB5B50B90}") = "MunitionsExpert", "MunitionsExpert\MunitionsExpert.csproj", "{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F1E2DE6A-7145-4ED7-8AB0-DE2FE9E33CCA}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BA86C600-4494-49AD-A227-017B8142B09C}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,112 @@
#pragma warning disable 0169, 0414, 0649
using BepInEx.Configuration;
/// <summary>
/// Class that specifies how a setting should be displayed inside the ConfigurationManager settings window.
///
/// Usage:
/// This class template has to be copied inside the plugin's project and referenced by its code directly.
/// make a new instance, assign any fields that you want to override, and pass it as a tag for your setting.
///
/// If a field is null (default), it will be ignored and won't change how the setting is displayed.
/// If a field is non-null (you assigned a value to it), it will override default behavior.
/// </summary>
///
/// <example>
/// Here's an example of overriding order of settings and marking one of the settings as advanced:
/// <code>
/// // Override IsAdvanced and Order
/// Config.Bind("X", "1", 1, new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true, Order = 3 }));
/// // Override only Order, IsAdvanced stays as the default value assigned by ConfigManager
/// Config.Bind("X", "2", 2, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 1 }));
/// Config.Bind("X", "3", 3, new ConfigDescription("", null, new ConfigurationManagerAttributes { Order = 2 }));
/// </code>
/// </example>
///
/// <remarks>
/// You can read more and see examples in the readme at https://github.com/BepInEx/BepInEx.ConfigurationManager
/// You can optionally remove fields that you won't use from this class, it's the same as leaving them null.
/// </remarks>
internal sealed class ConfigurationManagerAttributes
{
/// <summary>
/// Should the setting be shown as a percentage (only use with value range settings).
/// </summary>
public bool? ShowRangeAsPercent;
/// <summary>
/// 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.
/// </summary>
public System.Action<ConfigEntryBase> CustomDrawer;
/// <summary>
/// Show this setting in the settings screen at all? If false, don't show.
/// </summary>
public bool? Browsable;
/// <summary>
/// Category the setting is under. Null to be directly under the plugin.
/// </summary>
public string Category;
/// <summary>
/// If set, a "Default" button will be shown next to the setting to allow resetting to default.
/// </summary>
public object DefaultValue;
/// <summary>
/// Force the "Reset" button to not be displayed, even if a valid DefaultValue is available.
/// </summary>
public bool? HideDefaultButton;
/// <summary>
/// Force the setting name to not be displayed. Should only be used with a <see cref="CustomDrawer"/> to get more space.
/// Can be used together with <see cref="HideDefaultButton"/> to gain even more space.
/// </summary>
public bool? HideSettingName;
/// <summary>
/// Optional description shown when hovering over the setting.
/// Not recommended, provide the description when creating the setting instead.
/// </summary>
public string Description;
/// <summary>
/// Name of the setting.
/// </summary>
public string DispName;
/// <summary>
/// Order of the setting on the settings list relative to other settings in a category.
/// 0 by default, higher number is higher on the list.
/// </summary>
public int? Order;
/// <summary>
/// Only show the value, don't allow editing it.
/// </summary>
public bool? ReadOnly;
/// <summary>
/// If true, don't show the setting by default. User has to turn on showing advanced settings or search for it.
/// </summary>
public bool? IsAdvanced;
/// <summary>
/// Custom converter from setting type to string for the built-in editor textboxes.
/// </summary>
public System.Func<object, string> ObjToStr;
/// <summary>
/// Custom converter from string to setting type for the built-in editor textboxes.
/// </summary>
public System.Func<string, object> StrToObj;
}
internal static class ConfigFileExtensions
{
public static ConfigEntry<T> Bind<T>(this ConfigFile config, string section, string key, T defaultValue, ConfigurationManagerAttributes attributes) =>
config.Bind(new ConfigDefinition(section, key), defaultValue, new ConfigDescription("", null, attributes));
}

View File

@ -1,60 +1,82 @@
using Comfort.Common;
using EFT.HandBook;
using EFT.InventoryLogic;
using EFT.UI.DragAndDrop;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace IcyClawz.MunitionsExpert
namespace IcyClawz.MunitionsExpert;
internal enum ColorName
{
Blue, Brown, Cyan, Gray, Green, Magenta, Orange, Pink, Purple, Red, Silver, Tan, Violet, Yellow
}
internal static class ColorCache
{
private const byte ALPHA = 38;
private static readonly Dictionary<ColorName, Color> Cache = new()
{
[ColorName.Blue] = new Color32(0, 60, 170, ALPHA),
[ColorName.Brown] = new Color32(140, 85, 30, ALPHA),
[ColorName.Cyan] = new Color32(0, 150, 150, ALPHA),
[ColorName.Gray] = new Color32(70, 70, 70, ALPHA),
[ColorName.Green] = new Color32(70, 140, 0, ALPHA),
[ColorName.Magenta] = new Color32(215, 0, 100, ALPHA),
[ColorName.Orange] = new Color32(140, 70, 0, ALPHA),
[ColorName.Pink] = new Color32(215, 120, 150, ALPHA),
[ColorName.Purple] = new Color32(120, 40, 135, ALPHA),
[ColorName.Red] = new Color32(170, 20, 0, ALPHA),
[ColorName.Silver] = new Color32(150, 150, 150, ALPHA),
[ColorName.Tan] = new Color32(175, 145, 100, ALPHA),
[ColorName.Violet] = new Color32(80, 50, 180, ALPHA),
[ColorName.Yellow] = new Color32(170, 170, 0, ALPHA),
};
public static Color Get(ColorName name) => Cache.TryGetValue(name, out Color color) ? color : default;
}
internal enum EAmmoExtraAttributeId
{
Damage,
PenetrationPower,
ArmorDamage,
FragmentationChance,
RicochetChance
ArmorDamage, FragmentationChance, RicochetChance
}
internal static class ImageExtensions
{
public static Sprite ToSprite(this System.Drawing.Image instance)
{
byte[] data;
using (MemoryStream ms = new MemoryStream())
{
using MemoryStream ms = new();
instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
data = ms.ToArray();
}
Texture2D texture = new Texture2D(instance.Width, instance.Height);
ImageConversion.LoadImage(texture, data);
Texture2D texture = new(instance.Width, instance.Height, TextureFormat.RGBA32, false);
texture.LoadImage(ms.ToArray());
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
}
}
internal static class IconCache
{
private static readonly Dictionary<EAmmoExtraAttributeId, Sprite> Cache = new Dictionary<EAmmoExtraAttributeId, Sprite>(5);
public static void Initialize()
private static readonly Dictionary<Enum, Sprite> Cache = new()
{
Cache.Add(EAmmoExtraAttributeId.Damage, Properties.Resources.Damage.ToSprite());
Cache.Add(EAmmoExtraAttributeId.PenetrationPower, Properties.Resources.PenetrationPower.ToSprite());
Cache.Add(EAmmoExtraAttributeId.ArmorDamage, Properties.Resources.ArmorDamage.ToSprite());
Cache.Add(EAmmoExtraAttributeId.FragmentationChance, Properties.Resources.FragmentationChance.ToSprite());
Cache.Add(EAmmoExtraAttributeId.RicochetChance, Properties.Resources.RicochetChance.ToSprite());
}
[EAmmoExtraAttributeId.ArmorDamage] = Properties.Resources.ArmorDamage.ToSprite(),
[EAmmoExtraAttributeId.FragmentationChance] = Properties.Resources.FragmentationChance.ToSprite(),
[EAmmoExtraAttributeId.RicochetChance] = Properties.Resources.RicochetChance.ToSprite(),
};
public static Sprite Get(Enum id) =>
id is EAmmoExtraAttributeId extraId ? Cache[extraId] : null;
public static Sprite Get(Enum id) => Cache.TryGetValue(id, out Sprite sprite) ? sprite : default;
}
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 =
typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance);
@ -63,47 +85,33 @@ namespace IcyClawz.MunitionsExpert
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;
if (instance.ProjectileCount > 1)
{
return $"{instance.ProjectileCount} × {instance.Damage} = {totalDamage}";
}
else
{
return 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();
string armorClassStr = armorClass > 0 ? $"Class {armorClass}" : "Unarmored";
return $"{armorClassStr} ({instance.PenetrationPower})";
},
DisplayType = () => EItemAttributeDisplayType.Compact
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.ArmorDamage)
{
Name = EAmmoExtraAttributeId.ArmorDamage.ToString(),
DisplayNameFunc = () => "Armor damage",
Base = () => 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)
@ -111,8 +119,8 @@ namespace IcyClawz.MunitionsExpert
Name = EAmmoExtraAttributeId.FragmentationChance.ToString(),
DisplayNameFunc = () => "Fragmentation chance",
Base = () => instance.FragmentationChance,
StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%",
DisplayType = () => EItemAttributeDisplayType.Compact
StringValue = () => $"{instance.FragmentationChance * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance)
@ -120,8 +128,44 @@ namespace IcyClawz.MunitionsExpert
Name = EAmmoExtraAttributeId.RicochetChance.ToString(),
DisplayNameFunc = () => "Ricochet chance",
Base = () => instance.RicochetChance,
StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%",
DisplayType = () => EItemAttributeDisplayType.Compact
StringValue = () => $"{(instance.RicochetChance * 100f):F1}%",
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,
});
}
@ -129,67 +173,8 @@ namespace IcyClawz.MunitionsExpert
{
var armorClasses = Singleton<BackendConfigSettingsClass>.Instance.Armor.ArmorClass;
for (int i = armorClasses.Length - 1; i >= 0; i--)
{
if (armorClasses[i].Resistance <= instance.PenetrationPower)
{
return i;
}
}
return 0;
}
}
internal static class ColorUtils
{
private const byte ALPHA = 38;
private static readonly Color[] ArmorClassColors = new Color[]
{
new Color32(120, 40, 135, ALPHA), // Unarmored => Violet
new Color32(0, 60, 170, ALPHA), // Class 1 => Blue
new Color32(0, 150, 150, ALPHA), // Class 2 => Cyan
new Color32(70, 140, 0, ALPHA), // Class 3 => Green
new Color32(170, 170, 0, ALPHA), // Class 4 => Yellow
new Color32(140, 70, 0, ALPHA), // Class 5 => Orange
new Color32(170, 20, 0, ALPHA) // Class 6+ => Red
};
public static Color GetArmorClassColor(int armorClass) =>
ArmorClassColors[Mathf.Clamp(armorClass, 0, ArmorClassColors.Length - 1)];
}
internal static class ItemViewExtensions
{
private static readonly FieldInfo BackgroundColorField =
typeof(ItemView).GetField("BackgroundColor", BindingFlags.NonPublic | BindingFlags.Instance);
private static void SetBackgroundColor(this ItemView instance, Color color) =>
BackgroundColorField.SetValue(instance, color);
public static void OverrideColor(this ItemView instance, Item item)
{
if (item is BulletClass bullet && bullet.PenetrationPower > 0)
{
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass();
instance.SetBackgroundColor(ColorUtils.GetArmorClassColor(armorClass));
}
}
}
internal static class EntityIconExtensions
{
private static readonly FieldInfo ColorPanelField =
typeof(EntityIcon).GetField("_colorPanel", BindingFlags.NonPublic | BindingFlags.Instance);
private static Image GetColorPanel(this EntityIcon instance) =>
ColorPanelField.GetValue(instance) as Image;
public static void OverrideColor(this EntityIcon instance, Item item)
{
if (item is BulletClass bullet && bullet.PenetrationPower > 0)
{
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass();
instance.GetColorPanel().color = ColorUtils.GetArmorClassColor(armorClass);
}
}
}
}

View File

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

View File

@ -1,31 +1,50 @@
using Aki.Reflection.Patching;
using BepInEx;
using BepInEx.Configuration;
using EFT.HandBook;
using EFT.InventoryLogic;
using EFT.UI;
using EFT.UI.DragAndDrop;
using SPT.Reflection.Patching;
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
namespace IcyClawz.MunitionsExpert
{
[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.1.1")]
namespace IcyClawz.MunitionsExpert;
[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
internal static ConfigEntry<bool> ColorizeItemBackgrounds;
private static ConfigEntry<bool> ColorizeConfig { get; set; }
private static ConfigEntry<ColorName>[] ArmorClassColorConfigs { get; set; }
private void Awake()
{
ColorizeItemBackgrounds = Config.Bind("General", "Colorize Icon Backgrounds", true);
IconCache.Initialize();
const string SECTION = "Colorize Icon Backgrounds";
ColorizeConfig = Config.Bind(SECTION, "", true, new ConfigurationManagerAttributes { Order = 7 });
ArmorClassColorConfigs = [
Config.Bind(SECTION, "Unarmored", ColorName.Purple, new ConfigurationManagerAttributes { Order = 6 }),
Config.Bind(SECTION, "Class 1", ColorName.Blue, new ConfigurationManagerAttributes { Order = 5 }),
Config.Bind(SECTION, "Class 2", ColorName.Cyan, new ConfigurationManagerAttributes { Order = 4 }),
Config.Bind(SECTION, "Class 3", ColorName.Green, new ConfigurationManagerAttributes { Order = 3 }),
Config.Bind(SECTION, "Class 4", ColorName.Yellow, new ConfigurationManagerAttributes { Order = 2 }),
Config.Bind(SECTION, "Class 5", ColorName.Orange, new ConfigurationManagerAttributes { Order = 1 }),
Config.Bind(SECTION, "Class 6+", ColorName.Red, new ConfigurationManagerAttributes { Order = 0 }),
];
new StaticIconsPatch().Enable();
new AmmoTemplatePatch().Enable();
new ItemViewPatch().Enable();
new EntityIconPatch().Enable();
}
internal static bool Colorize => ColorizeConfig.Value;
internal static Color GetArmorClassColor(int index)
{
index = Mathf.Clamp(index, 0, ArmorClassColorConfigs.Length - 1);
return ColorCache.Get(ArmorClassColorConfigs[index].Value);
}
}
internal class StaticIconsPatch : ModulePatch
@ -34,16 +53,8 @@ namespace IcyClawz.MunitionsExpert
typeof(StaticIcons).GetMethod("GetAttributeIcon", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix]
private static bool PatchPrefix(ref Sprite __result, Enum id)
{
var icon = IconCache.Get(id);
if (icon != null)
{
__result = icon;
return false;
}
return true;
}
private static bool PatchPrefix(ref Sprite __result, Enum id) =>
(__result = IconCache.Get(id)) is null;
}
internal class AmmoTemplatePatch : ModulePatch
@ -52,55 +63,53 @@ namespace IcyClawz.MunitionsExpert
typeof(AmmoTemplate).GetMethod("GetCachedReadonlyQualities", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix]
private static bool PatchPrefix(ref List<ItemAttributeClass> __result, AmmoTemplate __instance)
{
if (__instance.GetCachedQualities() != null)
{
__result = null;
return false;
}
return true;
}
private static bool PatchPrefix(ref AmmoTemplate __instance) =>
__instance.GetCachedQualities() is null;
[PatchPostfix]
private static void PatchPostfix(ref List<ItemAttributeClass> __result, AmmoTemplate __instance)
{
if (__result == null)
private static void PatchPostfix(ref List<ItemAttributeClass> __result, ref AmmoTemplate __instance)
{
if (__result is null)
__result = __instance.GetCachedQualities();
return;
}
else
__instance.AddExtraAttributes();
}
}
internal class ItemViewPatch : ModulePatch
{
private static readonly FieldInfo BackgroundColorField =
typeof(ItemView).GetField("BackgroundColor", BindingFlags.NonPublic | BindingFlags.Instance);
protected override MethodBase GetTargetMethod() =>
typeof(ItemView).GetMethod("UpdateColor", BindingFlags.NonPublic | BindingFlags.Instance);
typeof(ItemView).GetMethod("UpdateColor", BindingFlags.Public | BindingFlags.Instance);
[PatchPrefix]
private static void PatchPrefix(ref ItemView __instance)
{
if (Plugin.ColorizeItemBackgrounds.Value)
{
__instance.OverrideColor(__instance.Item);
}
if (!Plugin.Colorize || __instance.Item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return;
int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
BackgroundColorField.SetValue(__instance, Plugin.GetArmorClassColor(armorClass));
}
}
internal class EntityIconPatch : ModulePatch
{
private static readonly FieldInfo ColorPanelField =
typeof(EntityIcon).GetField("_colorPanel", BindingFlags.NonPublic | BindingFlags.Instance);
protected override MethodBase GetTargetMethod() =>
typeof(EntityIcon).GetMethod("Show", BindingFlags.Public | BindingFlags.Instance);
[PatchPostfix]
private static void PatchPostfix(ref EntityIcon __instance, Item item)
{
if (Plugin.ColorizeItemBackgrounds.Value)
{
__instance.OverrideColor(item);
}
}
if (!Plugin.Colorize || item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return;
if (ColorPanelField.GetValue(__instance) is not Image image)
return;
int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
image.color = Plugin.GetArmorClassColor(armorClass);
}
}

View File

@ -8,7 +8,7 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace IcyClawz.MunitionsExpert.Properties {
namespace IcyClawz.MunitionsExpert.Properties;
using System;
@ -70,16 +70,6 @@ namespace IcyClawz.MunitionsExpert.Properties {
}
}
/// <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>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -90,16 +80,6 @@ namespace IcyClawz.MunitionsExpert.Properties {
}
}
/// <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>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -110,4 +90,3 @@ namespace IcyClawz.MunitionsExpert.Properties {
}
}
}
}

View File

@ -121,15 +121,9 @@
<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>
</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">
<value>..\Resources\FragmentationChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</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">
<value>..\Resources\RicochetChance.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 B