diff --git a/CustomInteractions.Prepatch/CustomInteractions.Prepatch.csproj b/CustomInteractions.Prepatch/CustomInteractions.Prepatch.csproj index d93930c..c987699 100644 --- a/CustomInteractions.Prepatch/CustomInteractions.Prepatch.csproj +++ b/CustomInteractions.Prepatch/CustomInteractions.Prepatch.csproj @@ -5,6 +5,7 @@ IcyClawz.CustomInteractions.Prepatch 1.3.1 IcyClawz.CustomInteractions + latest diff --git a/CustomInteractions.Prepatch/Prepatch.cs b/CustomInteractions.Prepatch/Prepatch.cs index 385a8f6..6adcb1b 100644 --- a/CustomInteractions.Prepatch/Prepatch.cs +++ b/CustomInteractions.Prepatch/Prepatch.cs @@ -2,37 +2,24 @@ using System.Collections.Generic; using System.Linq; using Mono.Cecil; -namespace IcyClawz.CustomInteractions -{ - public static class Prepatch - { - public static IEnumerable TargetDLLs => new[] { "Assembly-CSharp.dll" }; +namespace IcyClawz.CustomInteractions; - public static void Patch(ref AssemblyDefinition assembly) - { - var type = assembly.MainModule.GetType("GClass2816"); // DynamicInteraction - if (type != null) - { - type.IsSealed = false; - var field = type.Fields.SingleOrDefault(c => c.Name == "action_0"); - if (field != null) - { - 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) - { - param.IsOptional = true; - param.HasDefault = true; - param.Constant = null; - } - } - } - //assembly.Write("Assembly-CSharp-CustomInteractions.dll"); - } +public static class Prepatch +{ + public static IEnumerable TargetDLLs => ["Assembly-CSharp.dll"]; + + public static void Patch(AssemblyDefinition assembly) + { + TypeDefinition type = assembly.MainModule.GetType("GClass2816"); // DynamicInteraction + type.IsSealed = false; + FieldDefinition field = type.Fields.SingleOrDefault(c => c.Name is "action_0"); + field.IsFamily = true; + field.IsInitOnly = false; + 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"); } } diff --git a/CustomInteractions/CustomInteractions.cs b/CustomInteractions/CustomInteractions.cs index f2e26ca..d2a9463 100644 --- a/CustomInteractions/CustomInteractions.cs +++ b/CustomInteractions/CustomInteractions.cs @@ -11,196 +11,170 @@ using UnityEngine; using DynamicInteraction = GClass2816; using EmptyInteractions = GClass2817; -namespace IcyClawz.CustomInteractions +namespace IcyClawz.CustomInteractions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public interface ICustomInteractionsProvider { } // Do not implement this directly + +public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider { - [EditorBrowsable(EditorBrowsableState.Never)] - public interface ICustomInteractionsProvider { } // Do not implement this directly + IEnumerable GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item); +} - public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider +public static class CustomInteractionsManager +{ + internal static readonly LinkedList Providers = []; + + public static void Register(ICustomInteractionsProvider provider) { - IEnumerable GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item); + if (!Providers.Contains(provider)) + Providers.AddLast(provider); + } +} + +public class CustomInteraction() +{ + internal readonly CustomInteractionImpl Impl = new(); + + public Func Caption { get => Impl.Caption; set => Impl.Caption = value; } + public Func Icon { get => Impl.Icon; set => Impl.Icon = value; } + public Action Action { get => Impl.Action; set => Impl.Action = value; } + public Func SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; } + public Func Enabled { get => Impl.Enabled; set => Impl.Enabled = value; } + public Func Error { get => Impl.Error; set => Impl.Error = value; } +} + +internal sealed class CustomInteractionImpl() + : DynamicInteraction(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4")) +{ + public Func Caption { get; set; } + public new Func Icon { get; set; } + public Action Action { get => action_0; set => action_0 = value; } + public Func SubMenu { get; set; } + public Func Enabled { get; set; } + public Func Error { get; set; } + + public bool IsInteractive() => Enabled?.Invoke() ?? true; +} + +public abstract class CustomSubInteractions(ItemUiContext uiContext) +{ + internal readonly CustomSubInteractionsImpl Impl = new(uiContext); + + public bool ExaminationRequired + { + get => Impl.ExaminationRequiredInternal; + set => Impl.ExaminationRequiredInternal = value; } - public static class CustomInteractionsManager - { - internal static readonly List Providers = new List(); + public void Add(CustomInteraction interaction) => Impl.AddCustomInteraction(interaction); + public void AddRange(IEnumerable interactions) => interactions.ExecuteForEach(Impl.AddCustomInteraction); + public void Remove(CustomInteraction interaction) => Impl.RemoveCustomInteraction(interaction); + public void RemoveRange(IEnumerable interactions) => interactions.ExecuteForEach(Impl.RemoveCustomInteraction); + public void CallRedraw() => Impl.CallRedraw(); + public void CallRedraw(string templateId) => Impl.CallRedraw(templateId); +} - public static void Register(ICustomInteractionsProvider provider) - { - if (!Providers.Contains(provider)) +internal sealed class CustomSubInteractionsImpl(ItemUiContext uiContext) + : EmptyInteractions(uiContext) +{ + public IEnumerable CustomInteractions => DynamicInteractions.OfType(); + + public bool ExaminationRequiredInternal { get; set; } = true; + public override bool ExaminationRequired => ExaminationRequiredInternal; + public override bool HasIcons => CustomInteractions.Any(interaction => interaction.Icon is not null); + + public void CallRedraw() => itemUiContext_0.RedrawContextMenus(null); +} + +internal static class AbstractInteractionsExtensions +{ + private static Dictionary GetDynamicInteractions(this GClass2817 instance) where T : Enum => + typeof(GClass2817).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance) + .GetValue(instance) as Dictionary; + + public static void AddCustomInteraction(this GClass2817 instance, CustomInteraction interaction) where T : Enum => + instance.GetDynamicInteractions()[interaction.Impl.Key] = interaction.Impl; + + public static void RemoveCustomInteraction(this GClass2817 instance, CustomInteraction interaction) where T : Enum => + instance.GetDynamicInteractions().Remove(interaction.Impl.Key); +} + +internal static class InteractionButtonsContainerExtensions +{ + private static readonly FieldInfo ButtonsContainerField = + typeof(InteractionButtonsContainer).GetField("_buttonsContainer", BindingFlags.NonPublic | BindingFlags.Instance); + + private static RectTransform GetButtonsContainer(this InteractionButtonsContainer instance) => + ButtonsContainerField.GetValue(instance) as RectTransform; + + private static readonly FieldInfo ButtonTemplateField = + typeof(InteractionButtonsContainer).GetField("_buttonTemplate", BindingFlags.NonPublic | BindingFlags.Instance); + + private static SimpleContextMenuButton GetButtonTemplate(this InteractionButtonsContainer instance) => + ButtonTemplateField.GetValue(instance) as SimpleContextMenuButton; + + private static readonly FieldInfo CurrentButtonField = + typeof(InteractionButtonsContainer).GetField("simpleContextMenuButton_0", BindingFlags.NonPublic | BindingFlags.Instance); + + private static void SetCurrentButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) => + CurrentButtonField.SetValue(instance, button); + + private static readonly MethodInfo CreateButtonMethod = + typeof(InteractionButtonsContainer).GetMethod("method_1", BindingFlags.NonPublic | 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, [ + key, caption, template, container, sprite, onButtonClicked, onMouseHover, subMenu, autoClose + ]); + + private static readonly MethodInfo CloseSubMenuMethod = + typeof(InteractionButtonsContainer).GetMethod("method_4", BindingFlags.NonPublic | 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); + + private static void AddButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) => + AddButtonMethod.Invoke(instance, [button]); + + public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl impl) + { + bool isInteractive = impl.IsInteractive(); + SimpleContextMenuButton button = null; + button = instance.CreateButton( + impl.Key, + impl.Caption?.Invoke() ?? "", + instance.GetButtonTemplate(), + instance.GetButtonsContainer(), + impl.Icon?.Invoke(), + () => { - Providers.Add(provider); - } - } - } - - public class CustomInteraction - { - internal readonly CustomInteractionImpl Impl; - - public CustomInteraction() - { - Impl = new CustomInteractionImpl(); - } - - public Func Caption { get => Impl.Caption; set => Impl.Caption = value; } - public Func Icon { get => Impl.Icon; set => Impl.Icon = value; } - public Action Action { get => Impl.Action; set => Impl.Action = value; } - public Func SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; } - public Func Enabled { get => Impl.Enabled; set => Impl.Enabled = value; } - public Func Error { get => Impl.Error; set => Impl.Error = value; } - } - - internal sealed class CustomInteractionImpl : DynamicInteraction - { - public CustomInteractionImpl() - : base(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4")) { } - - public Func Caption { get; set; } - public new Func Icon { get; set; } - public Action Action { get => action_0; set => action_0 = value; } - public Func SubMenu { get; set; } - public Func Enabled { get; set; } - public Func Error { get; set; } - - public bool IsInteractive() => - Enabled?.Invoke() ?? true; - } - - public abstract class CustomSubInteractions - { - 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 CustomInteractions => DynamicInteractions.OfType(); - - 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); - } - - internal static class AbstractInteractionsExtensions - { - private static Dictionary GetDynamicInteractions(this GClass2817 instance) where T : Enum => - typeof(GClass2817).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance) - .GetValue(instance) as Dictionary; - - public static void AddCustomInteraction(this GClass2817 instance, CustomInteraction customInteraction) where T : Enum => - instance.GetDynamicInteractions()[customInteraction.Impl.Key] = customInteraction.Impl; - - public static void RemoveCustomInteraction(this GClass2817 instance, CustomInteraction customInteraction) where T : Enum => - instance.GetDynamicInteractions().Remove(customInteraction.Impl.Key); - } - - internal static class InteractionButtonsContainerExtensions - { - private static readonly FieldInfo ButtonsContainerField = - typeof(InteractionButtonsContainer).GetField("_buttonsContainer", BindingFlags.NonPublic | BindingFlags.Instance); - - private static RectTransform GetButtonsContainer(this InteractionButtonsContainer instance) => - ButtonsContainerField.GetValue(instance) as RectTransform; - - private static readonly FieldInfo ButtonTemplateField = - typeof(InteractionButtonsContainer).GetField("_buttonTemplate", BindingFlags.NonPublic | BindingFlags.Instance); - - private static SimpleContextMenuButton GetButtonTemplate(this InteractionButtonsContainer instance) => - ButtonTemplateField.GetValue(instance) as SimpleContextMenuButton; - - private static readonly FieldInfo CurrentButtonField = - typeof(InteractionButtonsContainer).GetField("simpleContextMenuButton_0", BindingFlags.NonPublic | BindingFlags.Instance); - - private static void SetCurrentButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) => - CurrentButtonField.SetValue(instance, button); - - private static readonly MethodInfo CreateButtonMethod = - typeof(InteractionButtonsContainer).GetMethod("method_1", BindingFlags.NonPublic | 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[] { - key, caption, template, container, sprite, onButtonClicked, onMouseHover, subMenu, autoClose - }); - - private static readonly MethodInfo CloseSubMenuMethod = - typeof(InteractionButtonsContainer).GetMethod("method_4", BindingFlags.NonPublic | 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); - - private static void AddButton(this InteractionButtonsContainer instance, SimpleContextMenuButton button) => - AddButtonMethod.Invoke(instance, new object[] { button }); - - public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl customInteractionImpl) - { - bool isInteractive = customInteractionImpl.IsInteractive(); - SimpleContextMenuButton button = null; - button = instance.CreateButton( - customInteractionImpl.Key, - customInteractionImpl.Caption?.Invoke() ?? string.Empty, - instance.GetButtonTemplate(), - instance.GetButtonsContainer(), - customInteractionImpl.Icon?.Invoke(), - () => + if (isInteractive) + impl.Execute(); + }, + () => + { + instance.SetCurrentButton(button); + instance.CloseSubMenu(); + if (isInteractive) { - if (isInteractive) - { - customInteractionImpl.Execute(); - } - }, - () => - { - instance.SetCurrentButton(button); - instance.CloseSubMenu(); - if (isInteractive) - { - var subMenu = customInteractionImpl.SubMenu?.Invoke(); - if (subMenu != null) - { - instance.SetSubInteractions(subMenu.Impl); - } - } - }, - customInteractionImpl.SubMenu != null, - false - ); - button.SetButtonInteraction( - (isInteractive, customInteractionImpl.Error?.Invoke() ?? string.Empty) - ); - instance.AddButton(button); - } + CustomSubInteractions subMenu = impl.SubMenu?.Invoke(); + if (subMenu is not null) + instance.SetSubInteractions(subMenu.Impl); + } + }, + impl.SubMenu is not null, + false + ); + button.SetButtonInteraction( + (isInteractive, impl.Error?.Invoke() ?? "") + ); + instance.AddButton(button); } } \ No newline at end of file diff --git a/CustomInteractions/CustomInteractions.csproj b/CustomInteractions/CustomInteractions.csproj index 8e0c81d..85a6ed9 100644 --- a/CustomInteractions/CustomInteractions.csproj +++ b/CustomInteractions/CustomInteractions.csproj @@ -5,6 +5,7 @@ IcyClawz.CustomInteractions 1.3.1 IcyClawz.CustomInteractions + latest diff --git a/CustomInteractions/Plugin.cs b/CustomInteractions/Plugin.cs index 1a8d95f..7c01750 100644 --- a/CustomInteractions/Plugin.cs +++ b/CustomInteractions/Plugin.cs @@ -8,54 +8,50 @@ using DynamicInteraction = GClass2816; using ItemContext = GClass2623; using ItemInfoInteractions = GClass2817; -namespace IcyClawz.CustomInteractions +namespace IcyClawz.CustomInteractions; + +[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.3.1")] +public class Plugin : BaseUnityPlugin { - [BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.3.1")] - public class Plugin : BaseUnityPlugin + private void Awake() { - private void Awake() - { - new ItemUiContextPatch().Enable(); - new InteractionButtonsContainerPatch().Enable(); - } + new ItemUiContextPatch().Enable(); + new InteractionButtonsContainerPatch().Enable(); } +} - internal class ItemUiContextPatch : ModulePatch +internal class ItemUiContextPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(ItemUiContext).GetMethod("GetItemContextInteractions", BindingFlags.Public | BindingFlags.Instance); + + [PatchPostfix] + private static void Postfix(ref ItemInfoInteractions __result, ref ItemUiContext __instance, ItemContext itemContext) { - protected override MethodBase GetTargetMethod() => - typeof(ItemUiContext).GetMethod("GetItemContextInteractions", BindingFlags.Public | BindingFlags.Instance); - - [PatchPostfix] - private static void Postfix(ref ItemInfoInteractions __result, ItemUiContext __instance, ItemContext itemContext) + foreach (var provider in CustomInteractionsManager.Providers.OfType()) { - foreach (var provider in CustomInteractionsManager.Providers.OfType()) - { - var customInteractions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item); - if (customInteractions != null) - { - foreach (CustomInteraction customInteraction in customInteractions) - { - __result.AddCustomInteraction(customInteraction); - } - } - } - } - } - - internal class InteractionButtonsContainerPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.NonPublic | BindingFlags.Instance); - - [PatchPrefix] - private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteraction interaction) - { - if (interaction is CustomInteractionImpl customInteractionImpl) - { - __instance.AddCustomButton(customInteractionImpl); - return false; - } - return true; + var interactions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item); + if (interactions is null) + continue; + foreach (CustomInteraction interaction in interactions) + __result.AddCustomInteraction(interaction); } } } + +internal class InteractionButtonsContainerPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(InteractionButtonsContainer).GetMethod("method_3", BindingFlags.NonPublic | BindingFlags.Instance); + + [PatchPrefix] + private static bool Prefix(ref InteractionButtonsContainer __instance, DynamicInteraction interaction) + { + if (interaction is CustomInteractionImpl impl) + { + __instance.AddCustomButton(impl); + return false; + } + return true; + } +} diff --git a/ItemAttributeFix/ItemAttributeFix.csproj b/ItemAttributeFix/ItemAttributeFix.csproj index f97cc9a..0c3e0c8 100644 --- a/ItemAttributeFix/ItemAttributeFix.csproj +++ b/ItemAttributeFix/ItemAttributeFix.csproj @@ -5,6 +5,7 @@ IcyClawz.ItemAttributeFix 1.2.0 IcyClawz.ItemAttributeFix + latest diff --git a/ItemAttributeFix/Plugin.cs b/ItemAttributeFix/Plugin.cs index 361f00e..6648a14 100644 --- a/ItemAttributeFix/Plugin.cs +++ b/ItemAttributeFix/Plugin.cs @@ -3,33 +3,30 @@ using BepInEx; using EFT.UI; using System.Reflection; -namespace IcyClawz.ItemAttributeFix +namespace IcyClawz.ItemAttributeFix; + +[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.2.0")] +public class Plugin : BaseUnityPlugin { - [BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.2.0")] - public class Plugin : BaseUnityPlugin + private void Awake() => + new CompactCharacteristicPanelPatch().Enable(); +} + +internal class CompactCharacteristicPanelPatch : ModulePatch +{ + private static readonly FieldInfo ItemAttributeField = + typeof(CompactCharacteristicPanel).GetField("ItemAttribute", BindingFlags.NonPublic | BindingFlags.Instance); + + private static readonly FieldInfo StringField = + typeof(CompactCharacteristicPanel).GetField("string_0", BindingFlags.NonPublic | BindingFlags.Instance); + + protected override MethodBase GetTargetMethod() => + typeof(CompactCharacteristicPanel).GetMethod("SetValues", BindingFlags.Public | BindingFlags.Instance); + + [PatchPostfix] + private static void PatchPostfix(ref CompactCharacteristicPanel __instance) { - private void Awake() - { - new CompactCharacteristicPanelPatch().Enable(); - } - } - - internal class CompactCharacteristicPanelPatch : ModulePatch - { - private static readonly FieldInfo ItemAttributeField = - typeof(CompactCharacteristicPanel).GetField("ItemAttribute", BindingFlags.NonPublic | BindingFlags.Instance); - - private static readonly FieldInfo StringField = - typeof(CompactCharacteristicPanel).GetField("string_0", BindingFlags.NonPublic | BindingFlags.Instance); - - protected override MethodBase GetTargetMethod() => - typeof(CompactCharacteristicPanel).GetMethod("SetValues", BindingFlags.Public | BindingFlags.Instance); - - [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()); - } } } diff --git a/ItemContextMenuExt/ItemContextMenuExt.cs b/ItemContextMenuExt/ItemContextMenuExt.cs index 5f9cd8f..54beadf 100644 --- a/ItemContextMenuExt/ItemContextMenuExt.cs +++ b/ItemContextMenuExt/ItemContextMenuExt.cs @@ -9,175 +9,158 @@ using System.Reflection; using UnityEngine; using ILightTemplate = GInterface246; -using LightsState = GStruct155; using ResourceCache = GClass1977; -namespace IcyClawz.ItemContextMenuExt +namespace IcyClawz.ItemContextMenuExt; + +internal static class PlayerExtensions { - internal static class PlayerExtensions + private static readonly FieldInfo InventoryControllerField = + typeof(Player).GetField("_inventoryController", BindingFlags.NonPublic | BindingFlags.Instance); + + public static InventoryControllerClass GetInventoryController(this Player player) => + InventoryControllerField.GetValue(player) as InventoryControllerClass; +} + +internal static class LightComponentExtensions +{ + public static int GetModesCount(this LightComponent component) => + ((ILightTemplate)component.Item.Template).ModesCount; +} + +internal sealed class CustomInteractionsProvider : IItemCustomInteractionsProvider +{ + internal const string IconsPrefix = "Characteristics/Icons/"; + internal static StaticIcons StaticIcons => EFTHardSettings.Instance.StaticIcons; + + public IEnumerable GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item) { - private static readonly FieldInfo InventoryControllerField = - typeof(Player).GetField("_inventoryController", BindingFlags.NonPublic | BindingFlags.Instance); + if (viewType is not EItemViewType.Inventory) + yield break; - public static InventoryControllerClass GetInventoryController(this Player player) => - InventoryControllerField.GetValue(player) as InventoryControllerClass; - } - - internal static class LightComponentExtensions - { - public static int GetModesCount(this LightComponent component) => - ((ILightTemplate)component.Item.Template).ModesCount; - } - - internal sealed class CustomInteractionsProvider : IItemCustomInteractionsProvider - { - internal const string IconsPrefix = "Characteristics/Icons/"; - internal static StaticIcons StaticIcons => EFTHardSettings.Instance.StaticIcons; - - public IEnumerable GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item) + FireModeComponent fireModeComponent = item.GetItemComponent(); + if (fireModeComponent is not null) { - if (viewType != EItemViewType.Inventory) + // Firing mode + yield return new() { - yield break; - } + Caption = () => "Firing mode", + 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(); + if (lightComponent is not null) + { + // Turn on/off + yield return new() { - var component = item.GetItemComponent(); - if (component != null) + Caption = () => (lightComponent.IsActive ? "TurnOff" : "TurnOn").Localized(), + Icon = () => ResourceCache.Pop(IconsPrefix + (lightComponent.IsActive ? "TurnOff" : "TurnOn")), + Action = () => { - // Firing mode - yield return new CustomInteraction() - { - 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" - }; - yield break; + Singleton.Instance.PlayUISound(EUISoundType.MenuContextMenu); + ComponentUtils.SetLightState(lightComponent, !lightComponent.IsActive, lightComponent.SelectedMode); + uiContext.RedrawContextMenus([item.TemplateId]); } - } + }; + // Switch mode + yield return new() { - var component = item.GetItemComponent(); - if (component != null) - { - // Turn on/off - yield return new CustomInteraction() - { - Caption = () => (component.IsActive ? "TurnOff" : "TurnOn").Localized(), - Icon = () => ResourceCache.Pop(IconsPrefix + (component.IsActive ? "TurnOff" : "TurnOn")), - Action = () => - { - Singleton.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; - } - } + 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; } } +} - internal class FireModeSubMenu : CustomSubInteractions +internal class FireModeSubMenu : CustomSubInteractions +{ + public FireModeSubMenu(ItemUiContext uiContext, FireModeComponent component) + : base(uiContext) { - public FireModeSubMenu(ItemUiContext uiContext, FireModeComponent component) - : base(uiContext) + AddRange(component.AvailableEFireModes.Select(fireMode => new CustomInteraction() { - foreach (var fireMode in component.AvailableEFireModes) + Caption = () => fireMode.ToString().Localized(), + Action = () => { - Add(new CustomInteraction() - { - Caption = () => fireMode.ToString().Localized(), - Action = () => - { - Singleton.Instance.PlayUISound(EUISoundType.MenuContextMenu); - ComponentUtils.SetFireMode(component, fireMode); - }, - Enabled = () => fireMode != component.FireMode - }); - } - } + Singleton.Instance.PlayUISound(EUISoundType.MenuContextMenu); + ComponentUtils.SetFireMode(component, fireMode); + }, + Enabled = () => fireMode != component.FireMode + })); } +} - internal class LightModeSubMenu : CustomSubInteractions +internal class LightModeSubMenu : CustomSubInteractions +{ + public LightModeSubMenu(ItemUiContext uiContext, LightComponent component) + : base(uiContext) { - public LightModeSubMenu(ItemUiContext uiContext, LightComponent component) - : base(uiContext) + AddRange(Enumerable.Range(0, component.GetModesCount()).Select(lightMode => new CustomInteraction() { - foreach (var lightMode in Enumerable.Range(0, component.GetModesCount())) + Caption = () => $"Mode {lightMode + 1}", + Action = () => { - Add(new CustomInteraction() - { - Caption = () => $"Mode {lightMode + 1}", - Action = () => - { - Singleton.Instance.PlayUISound(EUISoundType.MenuContextMenu); - ComponentUtils.SetLightState(component, component.IsActive, lightMode); - }, - Enabled = () => lightMode != component.SelectedMode - }); - } - } + Singleton.Instance.PlayUISound(EUISoundType.MenuContextMenu); + ComponentUtils.SetLightState(component, component.IsActive, lightMode); + }, + Enabled = () => lightMode != component.SelectedMode + })); } +} - internal static class ComponentUtils +internal static class ComponentUtils +{ + public static void SetFireMode(FireModeComponent component, Weapon.EFireMode fireMode) { - public static void SetFireMode(FireModeComponent component, Weapon.EFireMode fireMode) + Player player = GamePlayerOwner.MyPlayer; + + if (player is not null && player.HandsController is Player.FirearmController fc && component.Item == fc.Item) { - var player = GamePlayerOwner.MyPlayer; - if (player != null && player.HandsController is Player.FirearmController fc && component.Item == fc.Item) + if (fc.Item.MalfState.State is not Weapon.EMalfunctionState.None) { - if (fc.Item.MalfState.State == Weapon.EMalfunctionState.None) - { - fc.ChangeFireMode(fireMode); - } - else - { - fc.FirearmsAnimator.MisfireSlideUnknown(false); - player.GetInventoryController().ExamineMalfunction(fc.Item, false); - } + fc.FirearmsAnimator.MisfireSlideUnknown(false); + player.GetInventoryController().ExamineMalfunction(fc.Item, false); return; } - component.SetFireMode(fireMode); + fc.ChangeFireMode(fireMode); + return; } - public static void SetLightState(LightComponent component, bool isActive, int lightMode) + component.SetFireMode(fireMode); + } + + public static void SetLightState(LightComponent component, bool isActive, int lightMode) + { + Player player = GamePlayerOwner.MyPlayer; + + if (player is not null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item)) { - var player = GamePlayerOwner.MyPlayer; - if (player != null && player.HandsController is Player.FirearmController fc && component.Item.IsChildOf(fc.Item)) - { - var state = new LightsState - { - Id = component.Item.Id, - IsActive = isActive, - LightMode = lightMode - }; - fc.SetLightsState(new[] { state }); - return; - } + fc.SetLightsState([new() { Id = component.Item.Id, IsActive = isActive, LightMode = lightMode }]); + return; + } - component.IsActive = isActive; - component.SelectedMode = lightMode; + component.IsActive = isActive; + component.SelectedMode = lightMode; - if (player != null) + if (player is not null) + { + foreach (TacticalComboVisualController tcvc in player.GetComponentsInChildren()) { - foreach (var tcvc in player.GetComponentsInChildren()) + if (ReferenceEquals(tcvc.LightMod, component)) { - if (ReferenceEquals(tcvc.LightMod, component)) - { - tcvc.UpdateBeams(); - break; - } + tcvc.UpdateBeams(); + break; } } } diff --git a/ItemContextMenuExt/ItemContextMenuExt.csproj b/ItemContextMenuExt/ItemContextMenuExt.csproj index c11309d..99eb56c 100644 --- a/ItemContextMenuExt/ItemContextMenuExt.csproj +++ b/ItemContextMenuExt/ItemContextMenuExt.csproj @@ -5,6 +5,7 @@ IcyClawz.ItemContextMenuExt 1.2.1 IcyClawz.ItemContextMenuExt + latest diff --git a/ItemContextMenuExt/Plugin.cs b/ItemContextMenuExt/Plugin.cs index 8aef43d..11bf788 100644 --- a/ItemContextMenuExt/Plugin.cs +++ b/ItemContextMenuExt/Plugin.cs @@ -1,14 +1,12 @@ using BepInEx; using IcyClawz.CustomInteractions; -namespace IcyClawz.ItemContextMenuExt +namespace IcyClawz.ItemContextMenuExt; + +[BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.2.1")] +[BepInDependency("com.IcyClawz.CustomInteractions")] +public class Plugin : BaseUnityPlugin { - [BepInPlugin("com.IcyClawz.ItemContextMenuExt", "IcyClawz.ItemContextMenuExt", "1.2.1")] - public class Plugin : BaseUnityPlugin - { - private void Awake() - { - CustomInteractionsManager.Register(new CustomInteractionsProvider()); - } - } + private void Awake() => + CustomInteractionsManager.Register(new CustomInteractionsProvider()); } diff --git a/ItemSellPrice/ItemSellPrice.cs b/ItemSellPrice/ItemSellPrice.cs index 674497e..88630ae 100644 --- a/ItemSellPrice/ItemSellPrice.cs +++ b/ItemSellPrice/ItemSellPrice.cs @@ -10,203 +10,139 @@ using UnityEngine; using CurrencyUtil = GClass2334; -namespace IcyClawz.ItemSellPrice +namespace IcyClawz.ItemSellPrice; + +internal static class TraderClassExtensions { - internal static class TraderClassExtensions + 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); + + public static SupplyData GetSupplyData(this TraderClass trader) => + SupplyDataField.GetValue(trader) as SupplyData; + + public static void SetSupplyData(this TraderClass trader, SupplyData supplyData) => + SupplyDataField.SetValue(trader, supplyData); + + public static async void UpdateSupplyData(this TraderClass trader) { - private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession(); - - private static readonly FieldInfo SupplyDataField = - typeof(TraderClass).GetField("supplyData_0", BindingFlags.NonPublic | BindingFlags.Instance); - - public static SupplyData GetSupplyData(this TraderClass trader) => - SupplyDataField.GetValue(trader) as SupplyData; - - public static void SetSupplyData(this TraderClass trader, SupplyData supplyData) => - SupplyDataField.SetValue(trader, supplyData); - - public static async void UpdateSupplyData(this TraderClass trader) - { - Result result = await Session.GetSupplyData(trader.Id); - if (result.Failed) - { - Debug.LogError("Failed to download supply data"); - return; - } + Result result = await Session.GetSupplyData(trader.Id); + if (result.Succeed) trader.SetSupplyData(result.Value); - } + else + Debug.LogError("Failed to download supply data"); } +} - internal static class ItemExtensions +internal static class ItemExtensions +{ + private static readonly Dictionary DisplayNames = new() { - private static readonly Dictionary DisplayNames = new Dictionary() + ["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; + private static ISession Session => _Session ??= ClientAppUtils.GetMainApp().GetClientBackEndSession(); + + public static void AddTraderOfferAttribute(this Item item) + { + ItemAttributeClass attribute = new(EItemAttributeId.MoneySum) { - { "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" } } + Name = EItemAttributeId.MoneySum.GetName(), + DisplayNameFunc = () => + { + string language = Singleton.Instance?.Game?.Settings?.Language?.GetValue(); + if (language is null || !DisplayNames.ContainsKey(language)) + language = "en"; + TraderOffer offer = GetBestTraderOffer(item); + return offer is not null + ? string.Format(DisplayNames[language][0], offer.Name) + : DisplayNames[language][1]; + }, + Base = () => + { + TraderOffer offer = GetBestTraderOffer(item); + return offer is not null ? offer.Price : 0.01f; + }, + StringValue = () => + { + TraderOffer offer = GetBestTraderOffer(item); + return offer is not null + ? $"{offer.Currency} {offer.Price}" + (offer.Count > 1 ? $" ({offer.Count})" : "") + : ""; + }, + FullStringValue = () => + { + IEnumerable offers = GetAllTraderOffers(item); + return offers is not null + ? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}")) + : ""; + }, + DisplayType = () => EItemAttributeDisplayType.Compact }; - - private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession(); - - public static void AddTraderOfferAttribute(this Item item) - { - ItemAttributeClass attribute = new ItemAttributeClass(EItemAttributeId.MoneySum) - { - Name = EItemAttributeId.MoneySum.GetName(), - DisplayNameFunc = () => - { - string language = Singleton.Instance?.Game?.Settings?.Language?.GetValue(); - if (language == 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]; - } - }, - Base = () => - { - if (GetBestTraderOffer(item) is TraderOffer offer) - { - return offer.Price; - } - else - { - return 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; - } - }, - FullStringValue = () => - { - if (GetAllTraderOffers(item) is List 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; - } - }, - DisplayType = () => EItemAttributeDisplayType.Compact - }; - List attributes = new List { attribute }; - attributes.AddRange(item.Attributes); - item.Attributes = attributes; - } - - private sealed class TraderOffer - { - 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; - } - } - - private static TraderOffer GetTraderOffer(Item item, TraderClass trader) - { - var result = trader.GetUserItemPrice(item); - if (result == null) - { - return null; - } - return new TraderOffer( - trader.LocalizedName, - result.Value.Amount, - CurrencyUtil.GetCurrencyCharById(result.Value.CurrencyId), - trader.GetSupplyData().CurrencyCourses[result.Value.CurrencyId], - item.StackObjectsCount - ); - } - - private static List 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) - { - item = item.CloneItem(); - item.StackObjectsCount = 1; - item.UnlimitedCount = false; - } - break; - } - List offers = new List(); - 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; - } - - private static TraderOffer GetBestTraderOffer(Item item) - { - if (GetAllTraderOffers(item) is List offers) - { - return offers.FirstOrDefault(); - } - else - { - return null; - } - } + item.Attributes = [attribute, .. item.Attributes]; } + + private sealed class TraderOffer(string name, int price, string currency, double course, int 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); + return result is null ? null : new( + trader.LocalizedName, + result.Value.Amount, + CurrencyUtil.GetCurrencyCharById(result.Value.CurrencyId), + trader.GetSupplyData().CurrencyCourses[result.Value.CurrencyId], + item.StackObjectsCount + ); + } + + private static IEnumerable 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) + { + item = item.CloneItem(); + item.StackObjectsCount = 1; + item.UnlimitedCount = false; + } + break; + } + return Session.Traders + .Select(trader => GetTraderOffer(item, trader)) + .Where(offer => offer is not null) + .OrderByDescending(offer => offer.Price * offer.Course); + } + + private static TraderOffer GetBestTraderOffer(Item item) => + GetAllTraderOffers(item)?.FirstOrDefault() ?? null; } \ No newline at end of file diff --git a/ItemSellPrice/ItemSellPrice.csproj b/ItemSellPrice/ItemSellPrice.csproj index 4d8fd8f..52060ab 100644 --- a/ItemSellPrice/ItemSellPrice.csproj +++ b/ItemSellPrice/ItemSellPrice.csproj @@ -5,6 +5,7 @@ IcyClawz.ItemSellPrice 1.2.1 IcyClawz.ItemSellPrice + latest diff --git a/ItemSellPrice/Plugin.cs b/ItemSellPrice/Plugin.cs index 157285c..e4d9461 100644 --- a/ItemSellPrice/Plugin.cs +++ b/ItemSellPrice/Plugin.cs @@ -3,78 +3,67 @@ using BepInEx; using EFT.InventoryLogic; using System.Reflection; -namespace IcyClawz.ItemSellPrice +namespace IcyClawz.ItemSellPrice; + +[BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.2.1")] +public class Plugin : BaseUnityPlugin { - [BepInPlugin("com.IcyClawz.ItemSellPrice", "IcyClawz.ItemSellPrice", "1.2.1")] - public class Plugin : BaseUnityPlugin + private void Awake() { - private void Awake() - { - new TraderPatch().Enable(); - new ItemPatch().Enable(); - new AmmoPatch().Enable(); - new GrenadePatch().Enable(); - new SecureContainerPatch().Enable(); - } - } - - internal class TraderPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(TraderClass).GetConstructors()[0]; - - [PatchPostfix] - private static void PatchPostfix(ref TraderClass __instance) - { - __instance.UpdateSupplyData(); - } - } - - internal class ItemPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(Item).GetConstructors()[0]; - - [PatchPostfix] - private static void PatchPostfix(ref Item __instance) - { - __instance.AddTraderOfferAttribute(); - } - } - - internal class AmmoPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(BulletClass).GetConstructors()[0]; - - [PatchPostfix] - private static void PatchPostfix(ref BulletClass __instance) - { - __instance.AddTraderOfferAttribute(); - } - } - - internal class GrenadePatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(GrenadeClass).GetConstructors()[0]; - - [PatchPostfix] - private static void PatchPostfix(ref GrenadeClass __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(); - } + new TraderPatch().Enable(); + new ItemPatch().Enable(); + new AmmoPatch().Enable(); + new GrenadePatch().Enable(); + new SecureContainerPatch().Enable(); } } + +internal class TraderPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(TraderClass).GetConstructors()[0]; + + [PatchPostfix] + private static void PatchPostfix(ref TraderClass __instance) => + __instance.UpdateSupplyData(); +} + +internal class ItemPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(Item).GetConstructors()[0]; + + [PatchPostfix] + private static void PatchPostfix(ref Item __instance) => + __instance.AddTraderOfferAttribute(); +} + +internal class AmmoPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(BulletClass).GetConstructors()[0]; + + [PatchPostfix] + private static void PatchPostfix(ref BulletClass __instance) => + __instance.AddTraderOfferAttribute(); +} + +internal class GrenadePatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(GrenadeClass).GetConstructors()[0]; + + [PatchPostfix] + private static void PatchPostfix(ref GrenadeClass __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(); +} diff --git a/MagazineInspector/MagazineInspector.cs b/MagazineInspector/MagazineInspector.cs index cb8c9d0..2816864 100644 --- a/MagazineInspector/MagazineInspector.cs +++ b/MagazineInspector/MagazineInspector.cs @@ -9,140 +9,85 @@ using UnityEngine; using InGameStatus = GClass1716; -namespace IcyClawz.MagazineInspector +namespace IcyClawz.MagazineInspector; + +internal static class MagazineClassExtensions { - internal static class MagazineClassExtensions + private static readonly Dictionary DisplayNames = new() { - private static readonly Dictionary DisplayNames = new Dictionary() + ["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; + 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) + { + ItemAttributeClass attribute = magazine.Attributes.Find(attr => attr.Id is EItemAttributeId.MaxCount); + if (attribute is null) + return; + attribute.DisplayNameFunc = () => { - { "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" } + string language = Singleton.Instance?.Game?.Settings?.Language?.GetValue(); + if (language is null || !DisplayNames.ContainsKey(language)) + language = "en"; + return DisplayNames[language]; }; - - private static ISession Session => ClientAppUtils.GetMainApp().GetClientBackEndSession(); - - private static Profile ActiveProfile => InGameStatus.InRaid ? GamePlayerOwner.MyPlayer.Profile : Session.Profile; - - public static void AddAmmoCountAttribute(this MagazineClass magazine) + attribute.Base = () => { - ItemAttributeClass attribute = magazine.Attributes.Find(attr => attr.Id is EItemAttributeId.MaxCount); - if (attribute == null) - { - return; - } - attribute.DisplayNameFunc = () => - { - string language = Singleton.Instance?.Game?.Settings?.Language?.GetValue(); - if (language == null || !DisplayNames.ContainsKey(language)) + int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out _); + return ammoCount ?? 0f; + }; + attribute.StringValue = () => + { + int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out _); + return $"{ammoCount?.ToString() ?? "?"}/{magazine.MaxCount}"; + }; + attribute.FullStringValue = () => + { + int? ammoCount = GetAmmoCount(magazine, ActiveProfile, out bool magChecked); + return magChecked + ? string.Join(Environment.NewLine, magazine.Cartridges.Items.Reverse().Select(cartridge => { - language = "en"; - } - return DisplayNames[language]; - }; - attribute.Base = () => - { - if (GetAmmoCount(magazine, ActiveProfile, out _) is int ammoCount) - { - return ammoCount; - } - else - { - return 0f; - } - }; - attribute.StringValue = () => - { - string value; - if (GetAmmoCount(magazine, ActiveProfile, out _) is int ammoCount) - { - value = ammoCount.ToString(); - } - else - { - value = "?"; - } - return $"{value}/{magazine.MaxCount}"; - }; - attribute.FullStringValue = () => - { - Profile profile = ActiveProfile; - int? ammoCount = GetAmmoCount(magazine, profile, out bool magChecked); - if (magChecked) - { - List cartridges = new List(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) + { + if (!InGameStatus.InRaid || magazine.Count == 0) + { + magChecked = true; + return magazine.Count; } - - private static int? GetAmmoCount(MagazineClass magazine, Profile profile, out bool magChecked) - { - if (!InGameStatus.InRaid || magazine.Count == 0) - { - magChecked = true; - return magazine.Count; - } - magChecked = profile.CheckedMagazines.ContainsKey(magazine.Id); - if (magChecked) - { - 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; - } - } + magChecked = profile.CheckedMagazines.ContainsKey(magazine.Id); + 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); + return skill > 1 || (skill == 1 && magazine.MaxCount <= 10) ? magazine.Count : null; } } \ No newline at end of file diff --git a/MagazineInspector/MagazineInspector.csproj b/MagazineInspector/MagazineInspector.csproj index 483f493..d64e652 100644 --- a/MagazineInspector/MagazineInspector.csproj +++ b/MagazineInspector/MagazineInspector.csproj @@ -5,6 +5,7 @@ IcyClawz.MagazineInspector 1.2.1 IcyClawz.MagazineInspector + latest diff --git a/MagazineInspector/Plugin.cs b/MagazineInspector/Plugin.cs index 09fa24f..60eba78 100644 --- a/MagazineInspector/Plugin.cs +++ b/MagazineInspector/Plugin.cs @@ -2,26 +2,21 @@ using Aki.Reflection.Patching; using BepInEx; using System.Reflection; -namespace IcyClawz.MagazineInspector -{ - [BepInPlugin("com.IcyClawz.MagazineInspector", "IcyClawz.MagazineInspector", "1.2.1")] - public class Plugin : BaseUnityPlugin - { - private void Awake() - { - new MagazinePatch().Enable(); - } - } +namespace IcyClawz.MagazineInspector; - internal class MagazinePatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(MagazineClass).GetConstructors()[0]; - - [PatchPostfix] - private static void PatchPostfix(ref MagazineClass __instance) - { - __instance.AddAmmoCountAttribute(); - } - } +[BepInPlugin("com.IcyClawz.MagazineInspector", "IcyClawz.MagazineInspector", "1.2.1")] +public class Plugin : BaseUnityPlugin +{ + private void Awake() => + new MagazinePatch().Enable(); +} + +internal class MagazinePatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(MagazineClass).GetConstructors()[0]; + + [PatchPostfix] + private static void PatchPostfix(ref MagazineClass __instance) => + __instance.AddAmmoCountAttribute(); } diff --git a/MunitionsExpert/ConfigurationManagerAttributes.cs b/MunitionsExpert/ConfigurationManagerAttributes.cs new file mode 100644 index 0000000..6dc8950 --- /dev/null +++ b/MunitionsExpert/ConfigurationManagerAttributes.cs @@ -0,0 +1,112 @@ +#pragma warning disable 0169, 0414, 0649 +using BepInEx.Configuration; + + +/// +/// 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. +/// +/// +/// +/// Here's an example of overriding order of settings and marking one of the settings as advanced: +/// +/// // 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 })); +/// +/// +/// +/// +/// 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. +/// +internal sealed class ConfigurationManagerAttributes +{ + /// + /// Should the setting be shown as a percentage (only use with value range settings). + /// + public bool? ShowRangeAsPercent; + + /// + /// 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. + /// + public System.Action CustomDrawer; + + /// + /// Show this setting in the settings screen at all? If false, don't show. + /// + public bool? Browsable; + + /// + /// Category the setting is under. Null to be directly under the plugin. + /// + public string Category; + + /// + /// If set, a "Default" button will be shown next to the setting to allow resetting to default. + /// + public object DefaultValue; + + /// + /// Force the "Reset" button to not be displayed, even if a valid DefaultValue is available. + /// + public bool? HideDefaultButton; + + /// + /// Force the setting name to not be displayed. Should only be used with a to get more space. + /// Can be used together with to gain even more space. + /// + public bool? HideSettingName; + + /// + /// Optional description shown when hovering over the setting. + /// Not recommended, provide the description when creating the setting instead. + /// + public string Description; + + /// + /// Name of the setting. + /// + public string DispName; + + /// + /// 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. + /// + public int? Order; + + /// + /// Only show the value, don't allow editing it. + /// + public bool? ReadOnly; + + /// + /// If true, don't show the setting by default. User has to turn on showing advanced settings or search for it. + /// + public bool? IsAdvanced; + + /// + /// Custom converter from setting type to string for the built-in editor textboxes. + /// + public System.Func ObjToStr; + + /// + /// Custom converter from string to setting type for the built-in editor textboxes. + /// + public System.Func StrToObj; +} + +internal static class ConfigFileExtensions +{ + public static ConfigEntry Bind(this ConfigFile config, string section, string key, T defaultValue, ConfigurationManagerAttributes attributes) => + config.Bind(new ConfigDefinition(section, key), defaultValue, new ConfigDescription("", null, attributes)); +} diff --git a/MunitionsExpert/MunitionsExpert.cs b/MunitionsExpert/MunitionsExpert.cs index 850ced0..64b606d 100644 --- a/MunitionsExpert/MunitionsExpert.cs +++ b/MunitionsExpert/MunitionsExpert.cs @@ -1,195 +1,145 @@ 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 { - internal enum EAmmoExtraAttributeId + 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 Cache = new() { - Damage, - PenetrationPower, - ArmorDamage, - FragmentationChance, - RicochetChance + [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 +} + +internal static class ImageExtensions +{ + public static Sprite ToSprite(this System.Drawing.Image instance) + { + using MemoryStream ms = new(); + instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + Texture2D texture = new(instance.Width, instance.Height); + ImageConversion.LoadImage(texture, ms.ToArray()); + return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); + } +} + +internal static class IconCache +{ + private static readonly Dictionary Cache = new() + { + [EAmmoExtraAttributeId.Damage] = Properties.Resources.Damage.ToSprite(), + [EAmmoExtraAttributeId.PenetrationPower] = Properties.Resources.PenetrationPower.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) => Cache.TryGetValue(id, out Sprite sprite) ? sprite : default; +} + +internal static class AmmoTemplateExtensions +{ + private static readonly FieldInfo CachedQualitiesField = + typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance); + + public static List GetCachedQualities(this AmmoTemplate instance) => + CachedQualitiesField.GetValue(instance) as List; + + 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) + { + Name = EAmmoExtraAttributeId.ArmorDamage.ToString(), + DisplayNameFunc = () => "Armor damage", + Base = () => instance.ArmorDamage, + StringValue = () => $"{instance.ArmorDamage}%", + DisplayType = () => EItemAttributeDisplayType.Compact + }); + + instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance) + { + Name = EAmmoExtraAttributeId.FragmentationChance.ToString(), + DisplayNameFunc = () => "Fragmentation chance", + Base = () => instance.FragmentationChance, + StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%", + DisplayType = () => EItemAttributeDisplayType.Compact + }); + + instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance) + { + Name = EAmmoExtraAttributeId.RicochetChance.ToString(), + DisplayNameFunc = () => "Ricochet chance", + Base = () => instance.RicochetChance, + StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%", + DisplayType = () => EItemAttributeDisplayType.Compact + }); } - internal static class ImageExtensions + public static int GetPenetrationArmorClass(this AmmoTemplate instance) { - public static Sprite ToSprite(this System.Drawing.Image instance) - { - byte[] data; - using (MemoryStream ms = new MemoryStream()) - { - instance.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - data = ms.ToArray(); - } - Texture2D texture = new Texture2D(instance.Width, instance.Height); - ImageConversion.LoadImage(texture, data); - return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); - } + var armorClasses = Singleton.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 IconCache - { - private static readonly Dictionary Cache = new Dictionary(5); - - public static void Initialize() - { - 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()); - } - - public static Sprite Get(Enum id) => - id is EAmmoExtraAttributeId extraId ? Cache[extraId] : null; - } - - internal static class AmmoTemplateExtensions - { - private static readonly FieldInfo CachedQualitiesField = - typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance); - - public static List GetCachedQualities(this AmmoTemplate instance) => - CachedQualitiesField.GetValue(instance) as List; - - 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 - }); - - instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance) - { - Name = EAmmoExtraAttributeId.FragmentationChance.ToString(), - DisplayNameFunc = () => "Fragmentation chance", - Base = () => instance.FragmentationChance, - StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%", - DisplayType = () => EItemAttributeDisplayType.Compact - }); - - instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance) - { - Name = EAmmoExtraAttributeId.RicochetChance.ToString(), - DisplayNameFunc = () => "Ricochet chance", - Base = () => instance.RicochetChance, - StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%", - DisplayType = () => EItemAttributeDisplayType.Compact - }); - } - - public static int GetPenetrationArmorClass(this AmmoTemplate instance) - { - var armorClasses = Singleton.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); - } - } - } -} \ No newline at end of file +} diff --git a/MunitionsExpert/MunitionsExpert.csproj b/MunitionsExpert/MunitionsExpert.csproj index 6fa9a5f..ee754f4 100644 --- a/MunitionsExpert/MunitionsExpert.csproj +++ b/MunitionsExpert/MunitionsExpert.csproj @@ -3,8 +3,9 @@ net472 IcyClawz.MunitionsExpert - 1.2.0 + 1.2.1 IcyClawz.MunitionsExpert + latest diff --git a/MunitionsExpert/Plugin.cs b/MunitionsExpert/Plugin.cs index 55c4102..5db318e 100644 --- a/MunitionsExpert/Plugin.cs +++ b/MunitionsExpert/Plugin.cs @@ -9,98 +9,106 @@ using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; +using UnityEngine.UI; -namespace IcyClawz.MunitionsExpert +namespace IcyClawz.MunitionsExpert; + +[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.2.1")] +public class Plugin : BaseUnityPlugin { - [BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.2.0")] - public class Plugin : BaseUnityPlugin - { - internal static ConfigEntry ColorizeItemBackgrounds; + private static ConfigEntry ColorizeSwitch { get; set; } + private static ConfigEntry[] ArmorClassColors { get; set; } - private void Awake() - { - ColorizeItemBackgrounds = Config.Bind("General", "Colorize Icon Backgrounds", true); - IconCache.Initialize(); - new StaticIconsPatch().Enable(); - new AmmoTemplatePatch().Enable(); - new ItemViewPatch().Enable(); - new EntityIconPatch().Enable(); - } + private void Awake() + { + const string COLORIZE = "Colorize Icon Backgrounds"; + ColorizeSwitch = Config.Bind(COLORIZE, "", true, new ConfigurationManagerAttributes { Order = 7 }); + ArmorClassColors = [ + Config.Bind(COLORIZE, "Unarmored", ColorName.Purple, new ConfigurationManagerAttributes { Order = 6 }), + Config.Bind(COLORIZE, "Class 1", ColorName.Blue, new ConfigurationManagerAttributes { Order = 5 }), + Config.Bind(COLORIZE, "Class 2", ColorName.Cyan, new ConfigurationManagerAttributes { Order = 4 }), + Config.Bind(COLORIZE, "Class 3", ColorName.Green, new ConfigurationManagerAttributes { Order = 3 }), + Config.Bind(COLORIZE, "Class 4", ColorName.Yellow, new ConfigurationManagerAttributes { Order = 2 }), + Config.Bind(COLORIZE, "Class 5", ColorName.Orange, new ConfigurationManagerAttributes { Order = 1 }), + Config.Bind(COLORIZE, "Class 6+", ColorName.Red, new ConfigurationManagerAttributes { Order = 0 }) + ]; + new StaticIconsPatch().Enable(); + new AmmoTemplatePatch().Enable(); + new ItemViewPatch().Enable(); + new EntityIconPatch().Enable(); } - internal class StaticIconsPatch : ModulePatch + internal static bool Colorize => ColorizeSwitch.Value; + + internal static Color GetArmorClassColor(int index) { - protected override MethodBase GetTargetMethod() => - 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; - } - } - - internal class AmmoTemplatePatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(AmmoTemplate).GetMethod("GetCachedReadonlyQualities", BindingFlags.Public | BindingFlags.Instance); - - [PatchPrefix] - private static bool PatchPrefix(ref List __result, AmmoTemplate __instance) - { - if (__instance.GetCachedQualities() != null) - { - __result = null; - return false; - } - return true; - } - - [PatchPostfix] - private static void PatchPostfix(ref List __result, AmmoTemplate __instance) - { - if (__result == null) - { - __result = __instance.GetCachedQualities(); - return; - } - __instance.AddExtraAttributes(); - } - } - - internal class ItemViewPatch : ModulePatch - { - protected override MethodBase GetTargetMethod() => - typeof(ItemView).GetMethod("UpdateColor", BindingFlags.NonPublic | BindingFlags.Instance); - - [PatchPrefix] - private static void PatchPrefix(ref ItemView __instance) - { - if (Plugin.ColorizeItemBackgrounds.Value) - { - __instance.OverrideColor(__instance.Item); - } - } - } - - internal class EntityIconPatch : ModulePatch - { - 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); - } - } + index = Mathf.Clamp(index, 0, ArmorClassColors.Length - 1); + return ColorCache.Get(ArmorClassColors[index].Value); + } +} + +internal class StaticIconsPatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(StaticIcons).GetMethod("GetAttributeIcon", BindingFlags.Public | BindingFlags.Instance); + + [PatchPrefix] + private static bool PatchPrefix(ref Sprite __result, Enum id) => + (__result = IconCache.Get(id)) is null; +} + +internal class AmmoTemplatePatch : ModulePatch +{ + protected override MethodBase GetTargetMethod() => + typeof(AmmoTemplate).GetMethod("GetCachedReadonlyQualities", BindingFlags.Public | BindingFlags.Instance); + + [PatchPrefix] + private static bool PatchPrefix(ref AmmoTemplate __instance) => + __instance.GetCachedQualities() is null; + + [PatchPostfix] + private static void PatchPostfix(ref List __result, ref AmmoTemplate __instance) + { + if (__result is null) + __result = __instance.GetCachedQualities(); + 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); + + [PatchPrefix] + private static void PatchPrefix(ref ItemView __instance) + { + if (!Plugin.Colorize || __instance.Item is not BulletClass bullet || bullet.PenetrationPower <= 0) + return; + int armorClass = bullet.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.Colorize || item is not BulletClass bullet || bullet.PenetrationPower <= 0) + return; + int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass(); + if (ColorPanelField.GetValue(__instance) is Image image) + image.color = Plugin.GetArmorClassColor(armorClass); } } diff --git a/MunitionsExpert/Properties/Resources.Designer.cs b/MunitionsExpert/Properties/Resources.Designer.cs index 01b5d04..0c0ac3d 100644 --- a/MunitionsExpert/Properties/Resources.Designer.cs +++ b/MunitionsExpert/Properties/Resources.Designer.cs @@ -8,106 +8,105 @@ // //------------------------------------------------------------------------------ -namespace IcyClawz.MunitionsExpert.Properties { - using System; +namespace IcyClawz.MunitionsExpert.Properties; +using System; + + +/// +/// A strongly-typed resource class, for looking up localized strings, etc. +/// +// This class was auto-generated by the StronglyTypedResourceBuilder +// class via a tool like ResGen or Visual Studio. +// To add or remove a member, edit your .ResX file then rerun ResGen +// with the /str option, or rebuild your VS project. +[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] +[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] +[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] +internal class Resources { + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } /// - /// A strongly-typed resource class, for looking up localized strings, etc. + /// Returns the cached ResourceManager instance used by this class. /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IcyClawz.MunitionsExpert.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("IcyClawz.MunitionsExpert.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } + set { + resourceCulture = value; } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap ArmorDamage { - get { - object obj = ResourceManager.GetObject("ArmorDamage", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap ArmorDamage { + get { + object obj = ResourceManager.GetObject("ArmorDamage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap Damage { - get { - object obj = ResourceManager.GetObject("Damage", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Damage { + get { + object obj = ResourceManager.GetObject("Damage", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap FragmentationChance { - get { - object obj = ResourceManager.GetObject("FragmentationChance", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap FragmentationChance { + get { + object obj = ResourceManager.GetObject("FragmentationChance", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap PenetrationPower { - get { - object obj = ResourceManager.GetObject("PenetrationPower", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap PenetrationPower { + get { + object obj = ResourceManager.GetObject("PenetrationPower", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } - - /// - /// Looks up a localized resource of type System.Drawing.Bitmap. - /// - internal static System.Drawing.Bitmap RicochetChance { - get { - object obj = ResourceManager.GetObject("RicochetChance", resourceCulture); - return ((System.Drawing.Bitmap)(obj)); - } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap RicochetChance { + get { + object obj = ResourceManager.GetObject("RicochetChance", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); } } }