Update C# lang
This commit is contained in:
parent
cdd23f964c
commit
e4c0280a09
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.CustomInteractions.Prepatch</AssemblyName>
|
||||
<Version>1.3.1</Version>
|
||||
<RootNamespace>IcyClawz.CustomInteractions</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,37 +2,24 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace IcyClawz.CustomInteractions
|
||||
{
|
||||
public static class Prepatch
|
||||
{
|
||||
public static IEnumerable<string> 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<string> 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");
|
||||
}
|
||||
}
|
||||
|
@ -11,196 +11,170 @@ using UnityEngine;
|
||||
using DynamicInteraction = GClass2816;
|
||||
using EmptyInteractions = GClass2817<System.Enum>;
|
||||
|
||||
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<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item);
|
||||
}
|
||||
|
||||
public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider
|
||||
public static class CustomInteractionsManager
|
||||
{
|
||||
internal static readonly LinkedList<ICustomInteractionsProvider> Providers = [];
|
||||
|
||||
public static void Register(ICustomInteractionsProvider provider)
|
||||
{
|
||||
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item);
|
||||
}
|
||||
|
||||
public static class CustomInteractionsManager
|
||||
{
|
||||
internal static readonly List<ICustomInteractionsProvider> Providers = new List<ICustomInteractionsProvider>();
|
||||
|
||||
public static void Register(ICustomInteractionsProvider provider)
|
||||
{
|
||||
if (!Providers.Contains(provider))
|
||||
{
|
||||
Providers.Add(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomInteraction
|
||||
{
|
||||
internal readonly CustomInteractionImpl Impl;
|
||||
|
||||
public CustomInteraction()
|
||||
{
|
||||
Impl = new CustomInteractionImpl();
|
||||
}
|
||||
|
||||
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<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
|
||||
{
|
||||
public CustomInteractionImpl()
|
||||
: base(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4")) { }
|
||||
|
||||
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<bool> Enabled { get; set; }
|
||||
public Func<string> 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<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);
|
||||
}
|
||||
|
||||
internal static class AbstractInteractionsExtensions
|
||||
{
|
||||
private static Dictionary<string, DynamicInteraction> GetDynamicInteractions<T>(this GClass2817<T> instance) where T : Enum =>
|
||||
typeof(GClass2817<T>).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(instance) as Dictionary<string, DynamicInteraction>;
|
||||
|
||||
public static void AddCustomInteraction<T>(this GClass2817<T> instance, CustomInteraction customInteraction) where T : Enum =>
|
||||
instance.GetDynamicInteractions()[customInteraction.Impl.Key] = customInteraction.Impl;
|
||||
|
||||
public static void RemoveCustomInteraction<T>(this GClass2817<T> 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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
if (!Providers.Contains(provider))
|
||||
Providers.AddLast(provider);
|
||||
}
|
||||
}
|
||||
|
||||
public class CustomInteraction()
|
||||
{
|
||||
internal readonly CustomInteractionImpl Impl = new();
|
||||
|
||||
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<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(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4"))
|
||||
{
|
||||
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<bool> Enabled { get; set; }
|
||||
public Func<string> 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 void Add(CustomInteraction interaction) => Impl.AddCustomInteraction(interaction);
|
||||
public void AddRange(IEnumerable<CustomInteraction> interactions) => interactions.ExecuteForEach(Impl.AddCustomInteraction);
|
||||
public void Remove(CustomInteraction interaction) => Impl.RemoveCustomInteraction(interaction);
|
||||
public void RemoveRange(IEnumerable<CustomInteraction> interactions) => interactions.ExecuteForEach(Impl.RemoveCustomInteraction);
|
||||
public void CallRedraw() => Impl.CallRedraw();
|
||||
public void CallRedraw(string templateId) => Impl.CallRedraw(templateId);
|
||||
}
|
||||
|
||||
internal sealed class CustomSubInteractionsImpl(ItemUiContext uiContext)
|
||||
: EmptyInteractions(uiContext)
|
||||
{
|
||||
public IEnumerable<CustomInteractionImpl> CustomInteractions => DynamicInteractions.OfType<CustomInteractionImpl>();
|
||||
|
||||
public 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<string, DynamicInteraction> GetDynamicInteractions<T>(this GClass2817<T> instance) where T : Enum =>
|
||||
typeof(GClass2817<T>).GetField("dictionary_1", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(instance) as Dictionary<string, DynamicInteraction>;
|
||||
|
||||
public static void AddCustomInteraction<T>(this GClass2817<T> instance, CustomInteraction interaction) where T : Enum =>
|
||||
instance.GetDynamicInteractions()[interaction.Impl.Key] = interaction.Impl;
|
||||
|
||||
public static void RemoveCustomInteraction<T>(this GClass2817<T> instance, CustomInteraction interaction) where T : Enum =>
|
||||
instance.GetDynamicInteractions().Remove(interaction.Impl.Key);
|
||||
}
|
||||
|
||||
internal static class InteractionButtonsContainerExtensions
|
||||
{
|
||||
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(),
|
||||
() =>
|
||||
{
|
||||
if (isInteractive)
|
||||
impl.Execute();
|
||||
},
|
||||
() =>
|
||||
{
|
||||
instance.SetCurrentButton(button);
|
||||
instance.CloseSubMenu();
|
||||
if (isInteractive)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.CustomInteractions</AssemblyName>
|
||||
<Version>1.3.1</Version>
|
||||
<RootNamespace>IcyClawz.CustomInteractions</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -8,54 +8,50 @@ using DynamicInteraction = GClass2816;
|
||||
using ItemContext = GClass2623;
|
||||
using ItemInfoInteractions = GClass2817<EFT.InventoryLogic.EItemInfoButton>;
|
||||
|
||||
namespace IcyClawz.CustomInteractions
|
||||
namespace IcyClawz.CustomInteractions;
|
||||
|
||||
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.3.1")]
|
||||
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<IItemCustomInteractionsProvider>())
|
||||
{
|
||||
foreach (var provider in CustomInteractionsManager.Providers.OfType<IItemCustomInteractionsProvider>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.ItemAttributeFix</AssemblyName>
|
||||
<Version>1.2.0</Version>
|
||||
<RootNamespace>IcyClawz.ItemAttributeFix</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<CustomInteraction> 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<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item)
|
||||
FireModeComponent fireModeComponent = item.GetItemComponent<FireModeComponent>();
|
||||
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<LightComponent>();
|
||||
if (lightComponent is not null)
|
||||
{
|
||||
// Turn on/off
|
||||
yield return new()
|
||||
{
|
||||
var component = item.GetItemComponent<FireModeComponent>();
|
||||
if (component != null)
|
||||
Caption = () => (lightComponent.IsActive ? "TurnOff" : "TurnOn").Localized(),
|
||||
Icon = () => ResourceCache.Pop<Sprite>(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<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
|
||||
ComponentUtils.SetLightState(lightComponent, !lightComponent.IsActive, lightComponent.SelectedMode);
|
||||
uiContext.RedrawContextMenus([item.TemplateId]);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Switch mode
|
||||
yield return new()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
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<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
|
||||
ComponentUtils.SetFireMode(component, fireMode);
|
||||
},
|
||||
Enabled = () => fireMode != component.FireMode
|
||||
});
|
||||
}
|
||||
}
|
||||
Singleton<GUISounds>.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<GUISounds>.Instance.PlayUISound(EUISoundType.MenuContextMenu);
|
||||
ComponentUtils.SetLightState(component, component.IsActive, lightMode);
|
||||
},
|
||||
Enabled = () => lightMode != component.SelectedMode
|
||||
});
|
||||
}
|
||||
}
|
||||
Singleton<GUISounds>.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<TacticalComboVisualController>())
|
||||
{
|
||||
foreach (var tcvc in player.GetComponentsInChildren<TacticalComboVisualController>())
|
||||
if (ReferenceEquals(tcvc.LightMod, component))
|
||||
{
|
||||
if (ReferenceEquals(tcvc.LightMod, component))
|
||||
{
|
||||
tcvc.UpdateBeams();
|
||||
break;
|
||||
}
|
||||
tcvc.UpdateBeams();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.ItemContextMenuExt</AssemblyName>
|
||||
<Version>1.2.1</Version>
|
||||
<RootNamespace>IcyClawz.ItemContextMenuExt</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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<SupplyData> result = await Session.GetSupplyData(trader.Id);
|
||||
if (result.Failed)
|
||||
{
|
||||
Debug.LogError("Failed to download supply data");
|
||||
return;
|
||||
}
|
||||
Result<SupplyData> result = await Session.GetSupplyData(trader.Id);
|
||||
if (result.Succeed)
|
||||
trader.SetSupplyData(result.Value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ItemExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, string[]> DisplayNames = new Dictionary<string, string[]>()
|
||||
{
|
||||
{ "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" } }
|
||||
};
|
||||
|
||||
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<SharedGameSettingsClass>.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<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;
|
||||
}
|
||||
},
|
||||
DisplayType = () => EItemAttributeDisplayType.Compact
|
||||
};
|
||||
List<ItemAttributeClass> attributes = new List<ItemAttributeClass> { 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<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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private static TraderOffer GetBestTraderOffer(Item item)
|
||||
{
|
||||
if (GetAllTraderOffers(item) is List<TraderOffer> offers)
|
||||
{
|
||||
return offers.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
Debug.LogError("Failed to download supply data");
|
||||
}
|
||||
}
|
||||
|
||||
internal static class ItemExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, string[]> DisplayNames = new()
|
||||
{
|
||||
["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)
|
||||
{
|
||||
Name = EItemAttributeId.MoneySum.GetName(),
|
||||
DisplayNameFunc = () =>
|
||||
{
|
||||
string language = Singleton<SharedGameSettingsClass>.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<TraderOffer> offers = GetAllTraderOffers(item);
|
||||
return offers is not null
|
||||
? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}"))
|
||||
: "";
|
||||
},
|
||||
DisplayType = () => EItemAttributeDisplayType.Compact
|
||||
};
|
||||
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<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)
|
||||
{
|
||||
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;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.ItemSellPrice</AssemblyName>
|
||||
<Version>1.2.1</Version>
|
||||
<RootNamespace>IcyClawz.ItemSellPrice</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<string, string> DisplayNames = new()
|
||||
{
|
||||
private static readonly Dictionary<string, string> DisplayNames = new Dictionary<string, string>()
|
||||
["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<SharedGameSettingsClass>.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<SharedGameSettingsClass>.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<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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
<AssemblyName>IcyClawz.MagazineInspector</AssemblyName>
|
||||
<Version>1.2.1</Version>
|
||||
<RootNamespace>IcyClawz.MagazineInspector</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -2,26 +2,21 @@ using Aki.Reflection.Patching;
|
||||
using BepInEx;
|
||||
using System.Reflection;
|
||||
|
||||
namespace IcyClawz.MagazineInspector
|
||||
namespace IcyClawz.MagazineInspector;
|
||||
|
||||
[BepInPlugin("com.IcyClawz.MagazineInspector", "IcyClawz.MagazineInspector", "1.2.1")]
|
||||
public class Plugin : BaseUnityPlugin
|
||||
{
|
||||
[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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
112
MunitionsExpert/ConfigurationManagerAttributes.cs
Normal file
112
MunitionsExpert/ConfigurationManagerAttributes.cs
Normal 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<BepInEx.Configuration.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));
|
||||
}
|
@ -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<ColorName, Color> 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)
|
||||
};
|
||||
|
||||
internal static class ImageExtensions
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class IconCache
|
||||
{
|
||||
private static readonly Dictionary<EAmmoExtraAttributeId, Sprite> Cache = new Dictionary<EAmmoExtraAttributeId, Sprite>(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<ItemAttributeClass> GetCachedQualities(this AmmoTemplate instance) =>
|
||||
CachedQualitiesField.GetValue(instance) as List<ItemAttributeClass>;
|
||||
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
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<Enum, Sprite> 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<ItemAttributeClass> GetCachedQualities(this AmmoTemplate instance) =>
|
||||
CachedQualitiesField.GetValue(instance) as List<ItemAttributeClass>;
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
public static int GetPenetrationArmorClass(this AmmoTemplate instance)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<AssemblyName>IcyClawz.MunitionsExpert</AssemblyName>
|
||||
<Version>1.2.0</Version>
|
||||
<Version>1.2.1</Version>
|
||||
<RootNamespace>IcyClawz.MunitionsExpert</RootNamespace>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -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<bool> ColorizeItemBackgrounds;
|
||||
private static ConfigEntry<bool> ColorizeSwitch { get; set; }
|
||||
private static ConfigEntry<ColorName>[] 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<ItemAttributeClass> __result, AmmoTemplate __instance)
|
||||
{
|
||||
if (__instance.GetCachedQualities() != null)
|
||||
{
|
||||
__result = null;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void PatchPostfix(ref List<ItemAttributeClass> __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<ItemAttributeClass> __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);
|
||||
}
|
||||
}
|
||||
|
165
MunitionsExpert/Properties/Resources.Designer.cs
generated
165
MunitionsExpert/Properties/Resources.Designer.cs
generated
@ -8,106 +8,105 @@
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace IcyClawz.MunitionsExpert.Properties {
|
||||
using System;
|
||||
namespace IcyClawz.MunitionsExpert.Properties;
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// 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() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap ArmorDamage {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("ArmorDamage", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap ArmorDamage {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("ArmorDamage", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
internal static System.Drawing.Bitmap FragmentationChance {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("FragmentationChance", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap FragmentationChance {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("FragmentationChance", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
internal static System.Drawing.Bitmap RicochetChance {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("RicochetChance", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap RicochetChance {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("RicochetChance", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user