Fix bsg item panel bugs, mouse over after swap, compare deltas when mod swapping
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using Aki.Reflection.Patching;
|
||||
using Aki.Reflection.Utils;
|
||||
using EFT.InventoryLogic;
|
||||
using EFT.UI;
|
||||
using HarmonyLib;
|
||||
@@ -8,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text.RegularExpressions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
@@ -19,19 +19,16 @@ namespace UIFixes
|
||||
private static FieldInfo AttributeCompactPanelDictionaryField;
|
||||
private static FieldInfo AttributeCompactDropdownDictionaryField;
|
||||
|
||||
private static FieldInfo ItemComponentItemField;
|
||||
|
||||
private static FieldInfo CompactCharacteristicPanelItemAttributeField;
|
||||
private static FieldInfo CompactCharacteristicPanelCompareItemAttributeField;
|
||||
|
||||
public static void Enable()
|
||||
{
|
||||
AttributeCompactPanelDictionaryField = AccessTools.GetDeclaredFields(typeof(ItemSpecificationPanel)).First(f => typeof(IEnumerable<KeyValuePair<ItemAttributeClass, CompactCharacteristicPanel>>).IsAssignableFrom(f.FieldType));
|
||||
AttributeCompactDropdownDictionaryField = AccessTools.GetDeclaredFields(typeof(ItemSpecificationPanel)).First(f => typeof(IEnumerable<KeyValuePair<ItemAttributeClass, CompactCharacteristicDropdownPanel>>).IsAssignableFrom(f.FieldType));
|
||||
|
||||
Type itemComponentType = PatchConstants.EftTypes.First(t => typeof(IItemComponent).IsAssignableFrom(t) && t.GetField("Item") != null); // GClass2754
|
||||
ItemComponentItemField = AccessTools.Field(itemComponentType, "Item");
|
||||
|
||||
CompactCharacteristicPanelItemAttributeField = AccessTools.Field(typeof(CompactCharacteristicPanel), "ItemAttribute");
|
||||
CompactCharacteristicPanelCompareItemAttributeField = AccessTools.Field(typeof(CompactCharacteristicPanel), "CompareItemAttribute");
|
||||
|
||||
new InjectButtonPatch().Enable();
|
||||
new LoadModStatsPatch().Enable();
|
||||
@@ -260,8 +257,14 @@ namespace UIFixes
|
||||
|
||||
private class FormatFullValuesPatch : ModulePatch
|
||||
{
|
||||
private static MethodInfo RoundToIntMethod;
|
||||
private static MethodInfo ToStringMethod;
|
||||
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
RoundToIntMethod = AccessTools.Method(typeof(Mathf), "RoundToInt");
|
||||
ToStringMethod = AccessTools.Method(typeof(float), "ToString", [typeof(string)]);
|
||||
|
||||
return AccessTools.Method(typeof(CharacteristicPanel), "SetValues");
|
||||
}
|
||||
|
||||
@@ -270,19 +273,61 @@ namespace UIFixes
|
||||
{
|
||||
try
|
||||
{
|
||||
FormatText(__instance, ___ValueText);
|
||||
FormatText(__instance, ___ValueText, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
// This transpiler looks for where it rounds a float to an int, and skips that. Instead it calls ToString("0.0#") on it
|
||||
[PatchTranspiler]
|
||||
private static IEnumerable<CodeInstruction> Transpile(IEnumerable<CodeInstruction> instructions)
|
||||
{
|
||||
int skip = 0;
|
||||
CodeInstruction lastInstruction = null;
|
||||
CodeInstruction currentInstruction = null;
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
if (lastInstruction == null)
|
||||
{
|
||||
lastInstruction = instruction;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentInstruction = instruction;
|
||||
|
||||
if (skip > 0)
|
||||
{
|
||||
--skip;
|
||||
}
|
||||
else if (currentInstruction.Calls(RoundToIntMethod))
|
||||
{
|
||||
yield return new CodeInstruction(OpCodes.Ldloca_S, 17);
|
||||
yield return new CodeInstruction(OpCodes.Ldstr, "0.0#");
|
||||
yield return new CodeInstruction(OpCodes.Call, ToStringMethod);
|
||||
skip = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return lastInstruction;
|
||||
}
|
||||
|
||||
lastInstruction = instruction;
|
||||
}
|
||||
|
||||
if (currentInstruction != null)
|
||||
{
|
||||
yield return currentInstruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These fields are percents, but have been manually multipied by 100 already
|
||||
private static readonly EItemAttributeId[] NonPercentPercents = [EItemAttributeId.ChangeMovementSpeed, EItemAttributeId.ChangeTurningSpeed, EItemAttributeId.Ergonomics];
|
||||
|
||||
private static void FormatText(CompactCharacteristicPanel panel, TextMeshProUGUI textMesh)
|
||||
private static void FormatText(CompactCharacteristicPanel panel, TextMeshProUGUI textMesh, bool fullBar = false)
|
||||
{
|
||||
// Comparisons are shown as <value>(<changed>)
|
||||
// <value> is from each attribute type's StringValue() function, so is formatted *mostly* ok
|
||||
@@ -301,6 +346,40 @@ namespace UIFixes
|
||||
string text = textMesh.text;
|
||||
ItemAttributeClass attribute = CompactCharacteristicPanelItemAttributeField.GetValue(panel) as ItemAttributeClass;
|
||||
|
||||
// Holy shit did they mess up MOA. Half of the calculation is done in the StringValue() method, so calculating delta from Base() loses all that
|
||||
// Plus, they round the difference to the nearest integer (!?)
|
||||
// Completely redo it
|
||||
if ((EItemAttributeId)attribute.Id == EItemAttributeId.CenterOfImpact)
|
||||
{
|
||||
ItemAttributeClass compareAttribute = CompactCharacteristicPanelCompareItemAttributeField.GetValue(panel) as ItemAttributeClass;
|
||||
if (compareAttribute != null)
|
||||
{
|
||||
string currentStringValue = attribute.StringValue();
|
||||
var moaMatch = Regex.Match(currentStringValue, @"^(\S+)");
|
||||
float moa;
|
||||
if (float.TryParse(moaMatch.Groups[1].Value, out moa))
|
||||
{
|
||||
string compareStringValue = compareAttribute.StringValue();
|
||||
moaMatch = Regex.Match(compareStringValue, @"^(\S+)");
|
||||
float compareMoa;
|
||||
if (float.TryParse(moaMatch.Groups[1].Value, out compareMoa))
|
||||
{
|
||||
float delta = compareMoa - moa;
|
||||
string final = currentStringValue;
|
||||
if (Math.Abs(delta) > 0)
|
||||
{
|
||||
string sign = delta > 0 ? "+" : "";
|
||||
string color = (attribute.LessIsGood && delta < 0) || (!attribute.LessIsGood && delta > 0) ? IncreasingColorHex : DecreasingColorHex;
|
||||
final += " <color=" + color + ">(" + sign + delta.ToString("0.0#") + ")</color>";
|
||||
}
|
||||
|
||||
textMesh.text = final;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some percents are formatted with ToString("P1"), which puts a space before the %. These are percents from 0-1, so the <changed> value need to be converted
|
||||
var match = Regex.Match(text, @" %\(([+-].*)\)");
|
||||
if (match.Success)
|
||||
@@ -350,6 +429,11 @@ namespace UIFixes
|
||||
{
|
||||
string sign = value > 0 ? "+" : "";
|
||||
string color = (attribute.LessIsGood && value < 0) || (!attribute.LessIsGood && value > 0) ? IncreasingColorHex : DecreasingColorHex;
|
||||
if (fullBar && Math.Abs(value) >= 1)
|
||||
{
|
||||
// Fullbar rounds to nearest int, but I transpiled it not to. Restore the rounding, but only if the value won't just round to 0
|
||||
value = Mathf.RoundToInt(value);
|
||||
}
|
||||
text = Regex.Replace(text, @"\(([+-].*)\)", "<color=" + color + ">(" + sign + value + ")</color>");
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,14 @@ using Aki.Reflection.Utils;
|
||||
using Comfort.Common;
|
||||
using EFT;
|
||||
using EFT.InventoryLogic;
|
||||
using EFT.UI;
|
||||
using EFT.UI.DragAndDrop;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UIFixes
|
||||
@@ -64,8 +66,9 @@ namespace UIFixes
|
||||
|
||||
new ItemViewOnDragPatch().Enable();
|
||||
new GridViewCanAcceptPatch().Enable();
|
||||
new GetHightLightColorPatch().Enable();
|
||||
new SlotViewCanAcceptPatch().Enable();
|
||||
new GridGetHightLightColorPatch().Enable();
|
||||
new SlotGetHightLightColorPatch().Enable();
|
||||
new ItemContextClassCanAcceptPatch().Enable();
|
||||
new CheckItemFilterPatch().Enable();
|
||||
new SwapOperationRaiseEventsPatch().Enable();
|
||||
new GridItemViewOnPointerEnterPatch().Enable();
|
||||
@@ -115,7 +118,7 @@ namespace UIFixes
|
||||
}
|
||||
}
|
||||
|
||||
if (!error.EndsWith("not applicable") && !error.StartsWith("Cannot apply") && error != "InventoryError/NoPossibleActions")
|
||||
if (!error.EndsWith("not applicable") && !(error.StartsWith("Cannot apply") && !error.EndsWith("modified")) && error != "InventoryError/NoPossibleActions")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -182,7 +185,6 @@ namespace UIFixes
|
||||
return false;
|
||||
}
|
||||
|
||||
//if (itemAddressA is GClass2769 && itemAddressB is GClass2769)
|
||||
if (GridItemAddressType.IsInstanceOfType(itemAddressA) && GridItemAddressType.IsInstanceOfType(itemAddressB))
|
||||
{
|
||||
LocationInGrid locationA = GridItemAddressLocationInGridField.GetValue(itemAddressA) as LocationInGrid;
|
||||
@@ -277,7 +279,7 @@ namespace UIFixes
|
||||
// Try original rotations
|
||||
var result = InteractionsHandlerClass.Swap(item, itemToAddress, targetItem, targetToAddress, traderControllerClass, true);
|
||||
operation = SwapOperationToCanAcceptOperationOperator.Invoke(null, [result]);
|
||||
__result = (bool)CanAcceptOperationSucceededProperty.GetValue(operation);
|
||||
__result = result.Succeeded;
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return;
|
||||
@@ -294,6 +296,7 @@ namespace UIFixes
|
||||
var result = InteractionsHandlerClass.Swap(item, itemToAddress, targetItem, targetToAddress, traderControllerClass, true);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
// Only save this operation result if it succeeded, otherwise we return the non-rotated result from above
|
||||
operation = SwapOperationToCanAcceptOperationOperator.Invoke(null, [result]);
|
||||
__result = true;
|
||||
return;
|
||||
@@ -347,7 +350,7 @@ namespace UIFixes
|
||||
}
|
||||
}
|
||||
|
||||
if (LastHoveredGridItemView != null)
|
||||
if (LastHoveredGridItemView != null && LastHoveredGridItemView.ItemContext != null)
|
||||
{
|
||||
LastHoveredGridItemView.OnPointerEnter(new PointerEventData(EventSystem.current));
|
||||
}
|
||||
@@ -370,39 +373,50 @@ namespace UIFixes
|
||||
|
||||
// Called when dragging an item onto an equipment slot
|
||||
// Handles any kind of ItemAddress as the target destination (aka where the dragged item came from)
|
||||
public class SlotViewCanAcceptPatch : ModulePatch
|
||||
public class ItemContextClassCanAcceptPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
Type type = typeof(SlotView);
|
||||
Type type = typeof(ItemContextClass);
|
||||
return type.GetMethod("CanAccept");
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void Postfix(SlotView __instance, ItemContextClass itemContext, ItemContextAbstractClass targetItemContext, ref object operation, InventoryControllerClass ___InventoryController, ref bool __result)
|
||||
private static void Postfix(ItemContextClass __instance, Slot slot, ItemContextAbstractClass targetItemContext, ref object operation, TraderControllerClass itemController, bool simulate, ref bool __result)
|
||||
{
|
||||
if (!ValidPrerequisites(itemContext, targetItemContext, operation))
|
||||
// targetItemContext here is not the target item, it's the *parent* context, i.e. the owner of the slot
|
||||
// Do a few more checks
|
||||
if (slot.ContainedItem == null || __instance.Item == slot.ContainedItem || slot.ContainedItem.GetAllParentItems().Contains(__instance.Item))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = itemContext.Item;
|
||||
var targetItem = targetItemContext.Item;
|
||||
var itemToAddress = Activator.CreateInstance(SlotItemAddressType, [__instance.Slot]) as ItemAddress;
|
||||
if (!ValidPrerequisites(__instance, targetItemContext, operation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = __instance.Item;
|
||||
var targetItem = slot.ContainedItem;
|
||||
var itemToAddress = Activator.CreateInstance(SlotItemAddressType, [slot]) as ItemAddress;
|
||||
var targetToAddress = item.Parent;
|
||||
|
||||
var result = InteractionsHandlerClass.Swap(item, itemToAddress, targetItem, targetToAddress, ___InventoryController, true);
|
||||
if (result.Succeeded)
|
||||
// Repair kits again
|
||||
// Don't have access to ItemView to call CanInteract, but repair kits can't go into any slot I'm aware of, so...
|
||||
if (item.Template is RepairKitClass)
|
||||
{
|
||||
operation = SwapOperationToCanAcceptOperationOperator.Invoke(null, [result]);
|
||||
__result = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = InteractionsHandlerClass.Swap(item, itemToAddress, targetItem, targetToAddress, itemController, simulate);
|
||||
operation = SwapOperationToCanAcceptOperationOperator.Invoke(null, [result]);
|
||||
__result = result.Succeeded;
|
||||
}
|
||||
}
|
||||
|
||||
// The patched method here is called when iterating over all slots to highlight ones that the dragged item can interact with
|
||||
// Since swap has no special highlight, I just skip the patch here (minor perf savings, plus makes debugging a million times easier)
|
||||
public class GetHightLightColorPatch : ModulePatch
|
||||
public class GridGetHightLightColorPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
@@ -423,6 +437,29 @@ namespace UIFixes
|
||||
}
|
||||
}
|
||||
|
||||
// The patched method here is called when iterating over all slots to highlight ones that the dragged item can interact with
|
||||
// Since swap has no special highlight, I just skip the patch here (minor perf savings, plus makes debugging a million times easier)
|
||||
public class SlotGetHightLightColorPatch : ModulePatch
|
||||
{
|
||||
protected override MethodBase GetTargetMethod()
|
||||
{
|
||||
Type type = typeof(SlotView);
|
||||
return type.GetMethod("method_2");
|
||||
}
|
||||
|
||||
[PatchPrefix]
|
||||
private static void Prefix()
|
||||
{
|
||||
InHighlight = true;
|
||||
}
|
||||
|
||||
[PatchPostfix]
|
||||
private static void Postfix()
|
||||
{
|
||||
InHighlight = false;
|
||||
}
|
||||
}
|
||||
|
||||
// CanApply, when dealing with containers, eventually calls down into FindPlaceForItem, which calls CheckItemFilter. For reasons,
|
||||
// if an item fails the filters, it returns the error "no space", instead of "no action". Try to detect this, so we can swap.
|
||||
public class CheckItemFilterPatch : ModulePatch
|
||||
|
@@ -61,6 +61,6 @@
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="if $(ConfigurationName) == Debug (
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\$(TargetName).dll"
 xcopy /F /Y "$(ProjectDir)$(OutDir)$(TargetName).pdb" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\$(TargetName).pdb"
) 
if $(ConfigurationName) == Release (
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\$(TargetName).dll"
)
if $(Configurationname) == Dist (
 mkdir "$(ProjectDir)\dist\BepInDex\plugins"
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\dist\BepInEx\plugins\$(TargetName).dll"
 7z a -t7z Tyfon-UIFixes-$(Version).7z $(ProjectDir)\dist\BepInEx
 move /Y Tyfon-UIFixes-$(Version).7z dist\
)" />
|
||||
<Exec Command="if $(ConfigurationName) == Debug (
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\"
 xcopy /F /Y "$(ProjectDir)$(OutDir)$(TargetName).pdb" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\"
) 
if $(ConfigurationName) == Release (
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\$(PathToSPT)\BepInEx\plugins\"
)
if $(Configurationname) == Dist (
 mkdir "$(ProjectDir)\dist\BepInDex\plugins"
 xcopy /F /Y "$(TargetPath)" "$(ProjectDir)\dist\BepInEx\plugins\"
 7z a -t7z Tyfon-UIFixes-$(Version).7z $(ProjectDir)\dist\BepInEx
 move /Y Tyfon-UIFixes-$(Version).7z dist\
)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
Reference in New Issue
Block a user