Compare commits

..

5 Commits
3.9.0 ... main

Author SHA1 Message Date
438b0fdb9e Updated for client version 0.15.5.1 33420 2024-12-02 15:59:14 +02:00
ec50b42dc7 style 2024-10-25 20:55:26 +03:00
7183b28e8b Fix potential issue with max values 2024-10-23 17:07:49 +03:00
ccad3d23d0 Add missing ammo attributes 2024-10-23 00:49:39 +03:00
0f7ee0b1e3 VS junk 2024-07-13 03:51:11 +03:00
18 changed files with 426 additions and 210 deletions

View File

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

View File

@ -4,21 +4,17 @@ using EFT.UI;
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using UnityEngine;
using EmptyInteractionsAbstractClass = GClass3061;
using EmptyInteractionsAbstractClass = GClass3423;
namespace IcyClawz.CustomInteractions;
[EditorBrowsable(EditorBrowsableState.Never)]
public interface ICustomInteractionsProvider { } // Do not implement this directly
public interface IItemCustomInteractionsProvider : ICustomInteractionsProvider
public interface ICustomInteractionsProvider
{
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext uiContext, EItemViewType viewType, Item item);
IEnumerable<CustomInteraction> GetCustomInteractions(ItemUiContext context, EItemViewType viewType, Item item);
}
public static class CustomInteractionsManager
@ -32,52 +28,35 @@ public static class CustomInteractionsManager
}
}
public class CustomInteraction()
public class CustomInteraction(ItemUiContext context)
{
internal readonly CustomInteractionImpl Impl = new(UnityEngine.Random.Range(0, int.MaxValue).ToString("x4"));
internal readonly CustomInteractionImpl Impl = new(context, UnityEngine.Random.Range(0, int.MaxValue).ToString("x4"));
public Func<string> Caption { get => Impl.Caption; set => Impl.Caption = value; }
public Func<Sprite> Icon { get => Impl.Icon; set => Impl.Icon = value; }
public Action Action { get => Impl.Action; set => Impl.Action = value; }
public Func<CustomSubInteractions> SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; }
public Func<IEnumerable<CustomInteraction>> SubMenu { get => Impl.SubMenu; set => Impl.SubMenu = value; }
public Func<bool> Enabled { get => Impl.Enabled; set => Impl.Enabled = value; }
public Func<string> Error { get => Impl.Error; set => Impl.Error = value; }
}
internal sealed class CustomInteractionImpl(string id) : DynamicInteractionClass(id, id)
internal sealed class CustomInteractionImpl(ItemUiContext context, string id) : DynamicInteractionClass(id, id)
{
internal readonly ItemUiContext Context = context;
public Func<string> Caption { get; set; }
public new Func<Sprite> Icon { get; set; }
public Action Action { get => action_0; set => action_0 = value; }
public Func<CustomSubInteractions> SubMenu { get; set; }
public Func<IEnumerable<CustomInteraction>> SubMenu { get; set; }
public Func<bool> Enabled { get; set; }
public Func<string> Error { get; set; }
public bool IsInteractive() => Enabled?.Invoke() ?? true;
}
public 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);
}
internal sealed class CustomSubInteractionsImpl(ItemUiContext uiContext) : EmptyInteractionsAbstractClass(uiContext)
internal sealed class CustomInteractionsImpl(ItemUiContext context) : EmptyInteractionsAbstractClass(context)
{
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);
}
@ -87,11 +66,8 @@ internal static class AbstractInteractionsExtensions
typeof(ItemInfoInteractionsAbstractClass<T>).GetField("dictionary_0", BindingFlags.NonPublic | BindingFlags.Instance)
.GetValue(instance) as Dictionary<string, DynamicInteractionClass>;
public static void AddCustomInteraction<T>(this ItemInfoInteractionsAbstractClass<T> instance, CustomInteraction interaction) where T : struct, Enum =>
instance.GetDynamicInteractions()[interaction.Impl.Key] = interaction.Impl;
public static void RemoveCustomInteraction<T>(this ItemInfoInteractionsAbstractClass<T> instance, CustomInteraction interaction) where T : struct, Enum =>
instance.GetDynamicInteractions().Remove(interaction.Impl.Key);
public static void AddCustomInteraction<T>(this ItemInfoInteractionsAbstractClass<T> instance, CustomInteractionImpl impl) where T : struct, Enum =>
instance.GetDynamicInteractions()[impl.Key] = impl;
}
internal static class InteractionButtonsContainerExtensions
@ -140,6 +116,7 @@ internal static class InteractionButtonsContainerExtensions
public static void AddCustomButton(this InteractionButtonsContainer instance, CustomInteractionImpl impl)
{
bool isInteractive = impl.IsInteractive();
IEnumerable<CustomInteraction> subMenu = impl.SubMenu?.Invoke();
SimpleContextMenuButton button = null;
button = instance.CreateButton(
impl.Key,
@ -156,14 +133,15 @@ internal static class InteractionButtonsContainerExtensions
{
instance.SetCurrentButton(button);
instance.CloseSubMenu();
if (isInteractive)
if (isInteractive && subMenu != null)
{
CustomSubInteractions subMenu = impl.SubMenu?.Invoke();
if (subMenu is not null)
instance.SetSubInteractions(subMenu.Impl);
CustomInteractionsImpl subInteractions = new CustomInteractionsImpl(impl.Context);
foreach (CustomInteractionImpl subImpl in subMenu.Select(item => item.Impl))
subInteractions.AddCustomInteraction(subImpl);
instance.SetSubInteractions(subInteractions);
}
},
impl.SubMenu is not null,
subMenu?.Any() ?? false,
false
);
button.SetButtonInteraction(

View File

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

View File

@ -6,7 +6,7 @@ using System.Reflection;
namespace IcyClawz.CustomInteractions;
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.5.0")]
[BepInPlugin("com.IcyClawz.CustomInteractions", "IcyClawz.CustomInteractions", "1.6.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake()
@ -25,13 +25,13 @@ internal class ItemUiContextPatch : ModulePatch
private static void Postfix(ref ItemInfoInteractionsAbstractClass<EFT.InventoryLogic.EItemInfoButton> __result,
ref ItemUiContext __instance, ItemContextClass itemContext)
{
foreach (var provider in CustomInteractionsManager.Providers.OfType<IItemCustomInteractionsProvider>())
foreach (var provider in CustomInteractionsManager.Providers)
{
var interactions = provider.GetCustomInteractions(__instance, itemContext.ViewType, itemContext.Item);
if (interactions is null)
continue;
foreach (CustomInteraction interaction in interactions)
__result.AddCustomInteraction(interaction);
foreach (CustomInteractionImpl impl in interactions.Select(interaction => interaction.Impl))
__result.AddCustomInteraction(impl);
}
}
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemAttributeFix</AssemblyName>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<RootNamespace>IcyClawz.ItemAttributeFix</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

@ -5,7 +5,7 @@ using System.Reflection;
namespace IcyClawz.ItemAttributeFix;
[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.4.0")]
[BepInPlugin("com.IcyClawz.ItemAttributeFix", "IcyClawz.ItemAttributeFix", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
private void Awake() =>

View File

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

View File

@ -3,15 +3,11 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemContextMenuExt</AssemblyName>
<Version>1.5.0</Version>
<Version>1.6.0</Version>
<RootNamespace>IcyClawz.ItemContextMenuExt</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="ItemContextMenuExt.cs~RF18496b2.TMP" />
</ItemGroup>
<ItemGroup>
<Reference Include="Assembly-CSharp">
<HintPath>..\Shared\Assembly-CSharp.dll</HintPath>
@ -31,6 +27,9 @@
<Reference Include="ItemTemplate.Types">
<HintPath>..\Shared\ItemTemplate.Types.dll</HintPath>
</Reference>
<Reference Include="SPT.Reflection">
<HintPath>..\Shared\spt-reflection.dll</HintPath>
</Reference>
<Reference Include="UnityEngine">
<HintPath>..\Shared\UnityEngine.dll</HintPath>
</Reference>

View File

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

View File

@ -8,7 +8,7 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using CurrencyUtil = GClass2531;
using CurrencyUtil = GClass2867;
namespace IcyClawz.ItemSellPrice;
@ -55,7 +55,7 @@ internal static class ItemExtensions
["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"]
["tu"] = ["Satış fiyatı ({0})", "Tüccarlara satılamaz"],
};
private static ISession _Session;
@ -91,11 +91,11 @@ internal static class ItemExtensions
FullStringValue = () =>
{
IEnumerable<TraderOffer> offers = GetAllTraderOffers(item);
return offers is not null
return offers.Any()
? string.Join(Environment.NewLine, offers.Select(offer => $"{offer.Name}: {offer.Currency} {offer.Price}"))
: "";
},
DisplayType = () => EItemAttributeDisplayType.Compact
DisplayType = () => EItemAttributeDisplayType.Compact,
};
item.Attributes = [attribute, .. item.Attributes];
}
@ -111,38 +111,34 @@ internal static class ItemExtensions
private static TraderOffer GetTraderOffer(Item item, TraderClass trader)
{
var result = trader.GetUserItemPrice(item);
return result is null ? null : new(
var price = trader.GetUserItemPrice(item);
return price.HasValue ? new(
trader.LocalizedName,
result.Value.Amount,
CurrencyUtil.GetCurrencyCharById(result.Value.CurrencyId),
trader.GetSupplyData().CurrencyCourses[result.Value.CurrencyId],
price.Value.Amount,
CurrencyUtil.GetCurrencyCharById(price.Value.CurrencyId.Value),
trader.GetSupplyData().CurrencyCourses[price.Value.CurrencyId.Value],
item.StackObjectsCount
);
) : null;
}
private static IEnumerable<TraderOffer> GetAllTraderOffers(Item item)
{
if (!Session.Profile.Examined(item))
return null;
switch (item.Owner?.OwnerType)
{
case EOwnerType.RagFair:
case EOwnerType.Trader:
if (item.StackObjectsCount > 1 || item.UnlimitedCount)
return [];
if (item.Owner?.OwnerType is EOwnerType.RagFair or EOwnerType.Trader
&& (item.StackObjectsCount > 1 || item.UnlimitedCount))
{
item = item.CloneItem();
item.StackObjectsCount = 1;
item.UnlimitedCount = false;
}
break;
}
return Session.Traders
.Where(trader => !trader.Settings.AvailableInRaid)
.Select(trader => GetTraderOffer(item, trader))
.Where(offer => offer is not null)
.OrderByDescending(offer => offer.Price * offer.Course);
}
private static TraderOffer GetBestTraderOffer(Item item) =>
GetAllTraderOffers(item)?.FirstOrDefault() ?? null;
GetAllTraderOffers(item).FirstOrDefault();
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.ItemSellPrice</AssemblyName>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<RootNamespace>IcyClawz.ItemSellPrice</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

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

View File

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

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.MagazineInspector</AssemblyName>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<RootNamespace>IcyClawz.MagazineInspector</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

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

View File

@ -31,7 +31,7 @@ internal static class ColorCache
[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)
[ColorName.Yellow] = new Color32(170, 170, 0, ALPHA),
};
public static Color Get(ColorName name) => Cache.TryGetValue(name, out Color color) ? color : default;
@ -60,7 +60,7 @@ internal static class IconCache
{
[EAmmoExtraAttributeId.ArmorDamage] = Properties.Resources.ArmorDamage.ToSprite(),
[EAmmoExtraAttributeId.FragmentationChance] = Properties.Resources.FragmentationChance.ToSprite(),
[EAmmoExtraAttributeId.RicochetChance] = Properties.Resources.RicochetChance.ToSprite()
[EAmmoExtraAttributeId.RicochetChance] = Properties.Resources.RicochetChance.ToSprite(),
};
public static Sprite Get(Enum id) => Cache.TryGetValue(id, out Sprite sprite) ? sprite : default;
@ -68,6 +68,15 @@ internal static class IconCache
internal static class AmmoTemplateExtensions
{
private static readonly string[] MalfChancesKeys = (string[])
typeof(AmmoTemplate).GetField("MalfChancesKeys", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
private static readonly FieldInfo MaxMalfMisfireChance =
typeof(AmmoTemplate).GetField("MaxMalfMisfireChance", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly FieldInfo MaxMalfFeedChance =
typeof(AmmoTemplate).GetField("MaxMalfFeedChance", BindingFlags.NonPublic | BindingFlags.Static);
private static readonly FieldInfo CachedQualitiesField =
typeof(AmmoTemplate).GetField("_cachedQualities", BindingFlags.NonPublic | BindingFlags.Instance);
@ -82,7 +91,27 @@ internal static class AmmoTemplateExtensions
DisplayNameFunc = () => "Armor damage",
Base = () => instance.ArmorDamage,
StringValue = () => $"{instance.ArmorDamage}%",
DisplayType = () => EItemAttributeDisplayType.Compact
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.DurabilityBurn)
{
Name = EItemAttributeId.DurabilityBurn.GetName(),
Base = () => instance.DurabilityBurnModificator - 1f,
StringValue = () => $"{(instance.DurabilityBurnModificator - 1f) * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
LabelVariations = EItemAttributeLabelVariations.Colored,
LessIsGood = true,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.HeatFactor)
{
Name = EItemAttributeId.HeatFactor.GetName(),
Base = () => instance.HeatFactor - 1f,
StringValue = () => $"{(instance.HeatFactor - 1f) * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
LabelVariations = EItemAttributeLabelVariations.Colored,
LessIsGood = true,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.FragmentationChance)
@ -90,8 +119,8 @@ internal static class AmmoTemplateExtensions
Name = EAmmoExtraAttributeId.FragmentationChance.ToString(),
DisplayNameFunc = () => "Fragmentation chance",
Base = () => instance.FragmentationChance,
StringValue = () => $"{Math.Round(instance.FragmentationChance * 100, 1)}%",
DisplayType = () => EItemAttributeDisplayType.Compact
StringValue = () => $"{instance.FragmentationChance * 100f:F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EAmmoExtraAttributeId.RicochetChance)
@ -99,8 +128,44 @@ internal static class AmmoTemplateExtensions
Name = EAmmoExtraAttributeId.RicochetChance.ToString(),
DisplayNameFunc = () => "Ricochet chance",
Base = () => instance.RicochetChance,
StringValue = () => $"{Math.Round(instance.RicochetChance * 100, 1)}%",
DisplayType = () => EItemAttributeDisplayType.Compact
StringValue = () => $"{(instance.RicochetChance * 100f):F1}%",
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.MalfMisfireChance)
{
Name = EItemAttributeId.MalfMisfireChance.GetName(),
Base = () => instance.MalfMisfireChance,
StringValue = () =>
{
float maxMalfMisfireChance = (float)MaxMalfMisfireChance.GetValue(null);
int index = instance.MalfMisfireChance <= 0f ? 0
: instance.MalfMisfireChance < 3f * maxMalfMisfireChance / 7f ? 1
: instance.MalfMisfireChance < 4f * maxMalfMisfireChance / 7f ? 2
: instance.MalfMisfireChance < 5f * maxMalfMisfireChance / 7f ? 3
: instance.MalfMisfireChance < 6f * maxMalfMisfireChance / 7f ? 4
: 5;
return MalfChancesKeys[index].Localized();
},
DisplayType = () => EItemAttributeDisplayType.Compact,
});
instance.SafelyAddQualityToList(new ItemAttributeClass(EItemAttributeId.MalfFeedChance)
{
Name = EItemAttributeId.MalfFeedChance.GetName(),
Base = () => instance.MalfFeedChance,
StringValue = () =>
{
float maxMalfFeedChance = (float)MaxMalfFeedChance.GetValue(null);
int index = instance.MalfFeedChance <= 0f ? 0
: instance.MalfFeedChance < 1f * maxMalfFeedChance / 7f ? 1
: instance.MalfFeedChance < 3f * maxMalfFeedChance / 7f ? 2
: instance.MalfFeedChance < 5f * maxMalfFeedChance / 7f ? 3
: instance.MalfFeedChance < 6f * maxMalfFeedChance / 7f ? 4
: 5;
return MalfChancesKeys[index].Localized();
},
DisplayType = () => EItemAttributeDisplayType.Compact,
});
}

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<AssemblyName>IcyClawz.MunitionsExpert</AssemblyName>
<Version>1.4.0</Version>
<Version>1.5.0</Version>
<RootNamespace>IcyClawz.MunitionsExpert</RootNamespace>
<LangVersion>latest</LangVersion>
</PropertyGroup>

View File

@ -13,24 +13,24 @@ using UnityEngine.UI;
namespace IcyClawz.MunitionsExpert;
[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.4.0")]
[BepInPlugin("com.IcyClawz.MunitionsExpert", "IcyClawz.MunitionsExpert", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
private static ConfigEntry<bool> ColorizeSwitch { get; set; }
private static ConfigEntry<ColorName>[] ArmorClassColors { get; set; }
private static ConfigEntry<bool> ColorizeConfig { get; set; }
private static ConfigEntry<ColorName>[] ArmorClassColorConfigs { get; set; }
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 })
const string SECTION = "Colorize Icon Backgrounds";
ColorizeConfig = Config.Bind(SECTION, "", true, new ConfigurationManagerAttributes { Order = 7 });
ArmorClassColorConfigs = [
Config.Bind(SECTION, "Unarmored", ColorName.Purple, new ConfigurationManagerAttributes { Order = 6 }),
Config.Bind(SECTION, "Class 1", ColorName.Blue, new ConfigurationManagerAttributes { Order = 5 }),
Config.Bind(SECTION, "Class 2", ColorName.Cyan, new ConfigurationManagerAttributes { Order = 4 }),
Config.Bind(SECTION, "Class 3", ColorName.Green, new ConfigurationManagerAttributes { Order = 3 }),
Config.Bind(SECTION, "Class 4", ColorName.Yellow, new ConfigurationManagerAttributes { Order = 2 }),
Config.Bind(SECTION, "Class 5", ColorName.Orange, new ConfigurationManagerAttributes { Order = 1 }),
Config.Bind(SECTION, "Class 6+", ColorName.Red, new ConfigurationManagerAttributes { Order = 0 }),
];
new StaticIconsPatch().Enable();
new AmmoTemplatePatch().Enable();
@ -38,12 +38,12 @@ public class Plugin : BaseUnityPlugin
new EntityIconPatch().Enable();
}
internal static bool Colorize => ColorizeSwitch.Value;
internal static bool Colorize => ColorizeConfig.Value;
internal static Color GetArmorClassColor(int index)
{
index = Mathf.Clamp(index, 0, ArmorClassColors.Length - 1);
return ColorCache.Get(ArmorClassColors[index].Value);
index = Mathf.Clamp(index, 0, ArmorClassColorConfigs.Length - 1);
return ColorCache.Get(ArmorClassColorConfigs[index].Value);
}
}
@ -87,9 +87,9 @@ internal class ItemViewPatch : ModulePatch
[PatchPrefix]
private static void PatchPrefix(ref ItemView __instance)
{
if (!Plugin.Colorize || __instance.Item is not BulletClass bullet || bullet.PenetrationPower <= 0)
if (!Plugin.Colorize || __instance.Item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return;
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass();
int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
BackgroundColorField.SetValue(__instance, Plugin.GetArmorClassColor(armorClass));
}
}
@ -105,10 +105,11 @@ internal class EntityIconPatch : ModulePatch
[PatchPostfix]
private static void PatchPostfix(ref EntityIcon __instance, Item item)
{
if (!Plugin.Colorize || item is not BulletClass bullet || bullet.PenetrationPower <= 0)
if (!Plugin.Colorize || item is not AmmoItemClass ammo || ammo.PenetrationPower <= 0)
return;
int armorClass = bullet.AmmoTemplate.GetPenetrationArmorClass();
if (ColorPanelField.GetValue(__instance) is Image image)
if (ColorPanelField.GetValue(__instance) is not Image image)
return;
int armorClass = ammo.AmmoTemplate.GetPenetrationArmorClass();
image.color = Plugin.GetArmorClassColor(armorClass);
}
}