using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using FairyDust.Tooltip.Configuration; using FairyDust.Tooltip.Features.InventoryTooltip; using FairyDust.Tooltip.Host; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; using madeinfairyland.fairyengine; using madeinfairyland.fairyengine.actor; using madeinfairyland.fairyengine.actor.player; using madeinfairyland.fairyengine.ui; using madeinfairyland.forsakenfrontiers; using madeinfairyland.forsakenfrontiers.actor.player; using madeinfairyland.forsakenfrontiers.actor.player.datadeck; using madeinfairyland.forsakenfrontiers.actor.player.equipment; using madeinfairyland.forsakenfrontiers.train; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("FairyDust.Tooltip")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("FairyDust.Tooltip")] [assembly: AssemblyTitle("FairyDust.Tooltip")] [assembly: AssemblyVersion("1.0.0.0")] namespace FairyDust.Tooltip { public sealed class FairyDustTooltipMod { private readonly IFairyDustHost host; private InventoryTooltipModule inventoryTooltipModule; private bool disposed; private bool loggedConfigReloadFailure; private long nextConfigReloadAt; public FairyDustTooltipMod(IFairyDustHost host) { this.host = host; } public void Initialize() { disposed = false; Config.Initialize(host); host.Log.Info($"{"FairyDust.Tooltip"} v{"2.0.0"} loaded under {host.LoaderName}."); host.Log.Info("Game root: " + host.GameRootDirectory); host.Log.Info("Config file: " + Config.ConfigFilePath); inventoryTooltipModule = new InventoryTooltipModule(host); inventoryTooltipModule.Initialize(); } public void Shutdown() { if (!disposed) { disposed = true; inventoryTooltipModule?.Dispose(); inventoryTooltipModule = null; host.Log.Info("FairyDust.Tooltip shut down."); } } public void OnUpdate() { ReloadConfigIfDue(); inventoryTooltipModule?.OnUpdate(); } public void OnSceneWasLoaded(int buildIndex, string sceneName) { ForsakenScene.WorldLoaded(sceneName); } private void ReloadConfigIfDue() { long timestamp = Stopwatch.GetTimestamp(); if (timestamp < nextConfigReloadAt) { return; } nextConfigReloadAt = timestamp + Stopwatch.Frequency; try { if (Config.ReloadIfChanged()) { loggedConfigReloadFailure = false; host.Log.Info("Tooltip config reloaded."); } } catch (Exception ex) { nextConfigReloadAt = timestamp + Stopwatch.Frequency * 5; if (!loggedConfigReloadFailure) { loggedConfigReloadFailure = true; host.Log.Warning("Tooltip config reload failed: " + ex); } } } } public static class Metadata { public const string Name = "FairyDust.Tooltip"; public const string Version = "2.0.0"; public const string Author = "MadeInPG13"; } } namespace FairyDust.Tooltip.Host { public interface IFairyDustConfig where T : new() { string FilePath { get; } T Values { get; } void Initialize(); void Save(); bool ReloadIfChanged(); } public interface IFairyDustHost { string LoaderName { get; } string GameRootDirectory { get; } string ConfigDirectory { get; } IFairyDustLogger Log { get; } IFairyDustConfig CreateConfig(string categoryName) where T : new(); void PatchAll(Assembly assembly); void UnpatchSelf(); } public interface IFairyDustLogger { void Info(string message); void Warning(string message); void Error(string message); void Error(string message, Exception exception); } } namespace FairyDust.Tooltip.Features.InventoryTooltip { internal sealed class ActiveDataDeckContext { private FlavorData localFlavor; public FFDataDeck Deck { get; set; } public Canvas DeckCanvas { get; set; } public bool TryGetLocalFlavor(out FlavorData flavor) { flavor = localFlavor; return flavor != null; } public void RememberLocalFlavor(FFDataDeck deck, FlavorData flavor) { if (flavor != null && LocalPlayerDeck.IsLocalPlayerDeck(deck)) { localFlavor = flavor; } } public void ClearFlavor() { localFlavor = null; } } internal sealed class DataDeckToggleBinding { private Action handler; private FFDataDeck bound; public void Subscribe(FFDataDeck deck, Action onToggled) { if (!Il2CppGameInterop.SameInstance(bound, deck)) { Clear(); bound = deck; handler = DelegateSupport.ConvertDelegate>((Delegate)onToggled); deck.EvtOnToggledDataDeck += handler; } } public void Clear() { if ((Object)(object)bound != (Object)null && (Delegate)(object)handler != (Delegate)null) { try { bound.EvtOnToggledDataDeck -= handler; } catch { } } bound = null; handler = null; } } internal static class LocalPlayerDeck { public static bool IsOpen(FFDataDeck deck) { if ((Object)(object)deck != (Object)null) { return deck.DataDeckOpen; } return false; } public static bool IsLocalPlayerDeck(FFDataDeck candidate) { FFPlayer current = FairyLocalPlayer.Current; FFDataDeck val = ((current != null) ? current.DataDeck : null); if ((Object)(object)val != (Object)null) { return Il2CppGameInterop.SameInstance(val, candidate); } return false; } public static bool CanAdopt(FFDataDeck deck) { if ((Object)(object)deck == (Object)null) { return false; } if (!IsLocalPlayerDeck(deck)) { return false; } return ForsakenScene.IsValid(deck); } } internal static class EquipmentTitleResolver { public static string Resolve(BoundSlot slot) { FFEquipment val = slot?.Equipment; if ((Object)(object)val == (Object)null) { return null; } string fairyName = TryGetFairyName(val); foreach (string item in EnumerateCandidateNames(val, fairyName)) { string configuredTitle = ItemDisplayNames.GetConfiguredTitle(item); if (configuredTitle != null) { return configuredTitle; } } string text = TryResolveShopTitle(val, fairyName); if (text != null) { return text; } string text2 = TryResolveTypeTitle(val); if (text2 != null) { return text2; } string text3 = TryResolveItemTitle(val, fairyName); if (text3 != null) { return text3; } return "[NEW ITEM]"; } private static IEnumerable EnumerateCandidateNames(FFEquipment equipment, string fairyName) { yield return fairyName; yield return SafeName(() => ((Object)equipment).name); yield return SafeName(delegate { GameObject gameObject = ((Component)equipment).gameObject; return (gameObject == null) ? null : ((Object)gameObject).name; }); yield return GetTypeName(equipment); yield return ItemDisplayNames.CleanSourceName(GetTypeName(equipment)); } private static string TryResolveShopTitle(FFEquipment equipment, string fairyName) { try { FFTrader current = FairyTrader.Current; Il2CppReferenceArray val = ((current != null) ? current.ShopItems : null); if (val == null) { return null; } for (int i = 0; i < ((Il2CppArrayBase)(object)val).Length; i++) { ShopItem val2 = ((Il2CppArrayBase)(object)val)[i]; if (val2 != null && MatchesShopPrefab(val2.prefab, equipment, fairyName)) { string stableShopItemName = GetStableShopItemName(val2); string text = ItemDisplayNames.GetConfiguredTitle(stableShopItemName) ?? ItemDisplayNames.GetConfiguredTitle(val2.itemName); if (text != null) { return text; } return CacheAndReturn(equipment, fairyName, ItemDisplayNames.GetFallbackTitle(stableShopItemName), stableShopItemName, val2.itemName); } } } catch { return null; } return null; } private static string GetStableShopItemName(ShopItem item) { string text = SafeName(() => item.OriginalItemName); if (string.IsNullOrWhiteSpace(text)) { return ItemDisplayNames.CleanDisplayTitle(SafeName(() => item.itemName)); } return text; } private static string TryResolveTypeTitle(FFEquipment equipment) { string typeName = GetTypeName(equipment); return CacheAndReturn(equipment, TryGetFairyName(equipment), ItemDisplayNames.GetFallbackTitle(typeName), typeName); } private static string TryResolveItemTitle(FFEquipment equipment, string fairyName) { foreach (string item in EnumerateItemNames(equipment, fairyName)) { string fallbackTitle = ItemDisplayNames.GetFallbackTitle(item); if (fallbackTitle != null) { return CacheAndReturn(equipment, fairyName, fallbackTitle, item); } } return null; } private static string CacheAndReturn(FFEquipment equipment, string fairyName, string title, params string[] extraNames) { if (title != null) { Config.CacheDisplayName(EnumerateCacheNames(equipment, fairyName, extraNames), title); } return title; } private static IEnumerable EnumerateCacheNames(FFEquipment equipment, string fairyName, IEnumerable extraNames) { foreach (string item in EnumerateCandidateNames(equipment, fairyName)) { yield return item; } foreach (string extraName in extraNames) { yield return extraName; } } private static IEnumerable EnumerateItemNames(FFEquipment equipment, string fairyName) { yield return SafeName(() => ((Object)equipment).name); yield return SafeName(delegate { GameObject gameObject = ((Component)equipment).gameObject; return (gameObject == null) ? null : ((Object)gameObject).name; }); yield return fairyName; } private static bool MatchesShopPrefab(GameObject prefab, FFEquipment equipment, string fairyName) { if ((Object)(object)prefab == (Object)null || (Object)(object)equipment == (Object)null) { return false; } if (SameName(TryGetFairyName(prefab), fairyName)) { return true; } string left = SafeName(() => ((Object)prefab).name); if (SameName(left, SafeName(() => ((Object)equipment).name)) || SameName(left, SafeName(delegate { GameObject gameObject = ((Component)equipment).gameObject; return (gameObject == null) ? null : ((Object)gameObject).name; }))) { return true; } return SameName(GetTypeName(SafeGetComponent(prefab)), GetTypeName(equipment)); } private static string TryGetFairyName(FFEquipment equipment) { try { FairyObject obj = ((equipment != null) ? ((Il2CppObjectBase)equipment).TryCast() : null); return (obj != null) ? obj.FairyName : null; } catch { return null; } } private static string TryGetFairyName(GameObject gameObject) { try { FairyObject obj = ((gameObject != null) ? gameObject.GetComponent() : null); return (obj != null) ? obj.FairyName : null; } catch { return null; } } private static T SafeGetComponent(GameObject gameObject) where T : Component { try { return ((Object)(object)gameObject != (Object)null) ? gameObject.GetComponent() : default(T); } catch { return default(T); } } private static string SafeName(Func read) { try { return read(); } catch { return null; } } private static string GetTypeName(object instance) { return instance?.GetType().Name; } private static bool SameName(string left, string right) { string text = Config.NormalizeKey(ItemDisplayNames.CleanSourceName(left)); string b = Config.NormalizeKey(ItemDisplayNames.CleanSourceName(right)); if (text.Length > 0) { return string.Equals(text, b, StringComparison.Ordinal); } return false; } } internal static class FairyItemEquipmentResolver { private static MethodInfo[] concreteTryCastMethods; private static readonly Dictionary equipmentByItem = new Dictionary(); private static readonly HashSet failedMethods = new HashSet(); public static FFEquipment Resolve(FairyItem linked) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown if ((Object)(object)linked == (Object)null) { return null; } IntPtr intPtr = NativePointer(linked); if (intPtr != IntPtr.Zero && equipmentByItem.TryGetValue(intPtr, out var value)) { return value; } Ensure(); MethodInfo[] array = concreteTryCastMethods; foreach (MethodInfo methodInfo in array) { if (!failedMethods.Contains(methodInfo)) { object obj; try { obj = methodInfo.Invoke(linked, null); } catch { failedMethods.Add(methodInfo); continue; } if (obj != null) { return Cache(intPtr, (FFEquipment)obj); } } } return Cache(intPtr, ((Il2CppObjectBase)linked).TryCast()); } public static void Clear() { equipmentByItem.Clear(); failedMethods.Clear(); } private static void Ensure() { if (concreteTryCastMethods != null) { return; } try { MethodInfo tryCastMethod = typeof(FairyItem).GetRuntimeMethods().FirstOrDefault((MethodInfo m) => m.Name == "TryCast" && m.IsGenericMethodDefinition); if (tryCastMethod == null) { concreteTryCastMethods = Array.Empty(); return; } concreteTryCastMethods = (from t in (from t in typeof(FFEquipment).Assembly.GetTypes() where t.IsSubclassOf(typeof(FFEquipment)) && !t.IsAbstract select t).OrderByDescending(DepthUnderEquipment) select tryCastMethod.MakeGenericMethod(t)).ToArray(); } catch { concreteTryCastMethods = Array.Empty(); } } private static FFEquipment Cache(IntPtr pointer, FFEquipment equipment) { if (pointer != IntPtr.Zero && (Object)(object)equipment != (Object)null) { equipmentByItem[pointer] = equipment; } return equipment; } private static IntPtr NativePointer(object instance) { Il2CppObjectBase val = (Il2CppObjectBase)((instance is Il2CppObjectBase) ? instance : null); if (val == null) { return IntPtr.Zero; } return val.Pointer; } private static int DepthUnderEquipment(Type t) { int num = 0; Type type = t; while (type != null && type != typeof(FFEquipment)) { num++; type = type.BaseType; } return num; } } internal static class FairyLocalPlayer { public static FFPlayer Current { get { FairyPlayer localPlayer = FairyEngine.LocalPlayer; if (localPlayer == null) { return null; } return ((Il2CppObjectBase)localPlayer).TryCast(); } } public static FFWorld World { get { FFPlayer current = Current; if (current == null) { return null; } return current._ffWorld; } } public static FFTrain Train { get { FFWorld world = World; object obj = ((world != null) ? world._train : null); if (obj == null) { FFPlayer current = Current; if (current == null) { return null; } obj = current.Train; } return (FFTrain)obj; } } } internal static class FairyTrader { public static FFTrader Current { get { FFTrain train = FairyLocalPlayer.Train; if (train == null) { return null; } return train.Trader; } } } internal static class Il2CppGameInterop { private static IntPtr PointerOf(object instance) { Il2CppObjectBase val = (Il2CppObjectBase)((instance is Il2CppObjectBase) ? instance : null); if (val != null) { return val.Pointer; } return IntPtr.Zero; } public static bool SameInstance(object a, object b) { return NativeObjectIdentity.SameObject(a, b, PointerOf); } } internal static class NativeObjectIdentity { public static bool SameObject(object left, object right, Func pointerOf) { if (left == right) { return left != null; } if (left == null || right == null) { return false; } return SamePointer(pointerOf(left), pointerOf(right)); } public static bool SamePointer(IntPtr left, IntPtr right) { if (left != IntPtr.Zero) { return left == right; } return false; } } internal sealed class InventoryTooltipModule : IDisposable { private readonly IFairyDustHost host; private readonly EquipSlotBinder binder; private readonly DataDeckToggleBinding deckToggleBinding = new DataDeckToggleBinding(); private readonly ActiveDataDeckContext context = new ActiveDataDeckContext(); private readonly EquipSlotHoverTooltips tooltips; private Object lastInventoryUIGroupForCanvas; private bool disposed; private bool inventoryTooltipsEnabled; internal static InventoryTooltipModule Instance { get; private set; } public InventoryTooltipModule(IFairyDustHost host) { this.host = host ?? throw new ArgumentNullException("host"); binder = new EquipSlotBinder(new EquipSlotLocator(), new EquipSlotHoverBindingRegistry()); tooltips = new EquipSlotHoverTooltips(binder, context); } public void Initialize() { disposed = false; Instance = this; inventoryTooltipsEnabled = Config.Item.EnableInventoryTooltips; ForsakenScene.OnWorldLoaded += ResetSceneState; host.PatchAll(typeof(InventoryTooltipModule).Assembly); host.Log.Info("Inventory tooltip module initialized."); } public void OnUpdate() { if (!disposed) { ApplyInventoryTooltipEnabledState(); if (inventoryTooltipsEnabled) { tooltips.OnUpdate(); } } } public void Dispose() { if (!disposed) { disposed = true; ForsakenScene.OnWorldLoaded -= ResetSceneState; deckToggleBinding.Clear(); CleanupBindings(); tooltips.Dispose(); host.UnpatchSelf(); if (Instance == this) { Instance = null; } } } internal void OnHarmonyDeckRefreshData(FFDataDeck instance) { if (!LocalPlayerDeck.IsLocalPlayerDeck(instance) || !LocalPlayerDeck.IsOpen(instance)) { return; } if ((Object)(object)context.Deck == (Object)null) { TryAdoptLocalDeck(instance); } if (!((Object)(object)context.Deck == (Object)null) && Il2CppGameInterop.SameInstance(instance, context.Deck)) { RebindEquipSlotsIfDeckReady(); if (inventoryTooltipsEnabled) { tooltips.RefreshHover(); } } } internal void RememberLocalFlavor(FFDataDeck dataDeck, FlavorData flavor) { context.RememberLocalFlavor(dataDeck, flavor); } internal void TryAdoptLocalDeck(FFDataDeck dataDeck) { if (LocalPlayerDeck.CanAdopt(dataDeck) && !AlreadyWiredTo(dataDeck)) { deckToggleBinding.Clear(); CleanupBindings(); context.Deck = dataDeck; context.DeckCanvas = null; lastInventoryUIGroupForCanvas = null; deckToggleBinding.Subscribe(dataDeck, OnDeckToggled); if (inventoryTooltipsEnabled && dataDeck.DataDeckOpen) { RebindEquipSlotsIfDeckReady(); } } } private void ResetSceneState() { deckToggleBinding.Clear(); CleanupBindings(); context.Deck = null; context.DeckCanvas = null; context.ClearFlavor(); FairyItemEquipmentResolver.Clear(); lastInventoryUIGroupForCanvas = null; } private void OnDeckToggled(bool open) { if (!open) { tooltips.ClearHover(); } else if (inventoryTooltipsEnabled) { RebindEquipSlotsIfDeckReady(); } } private void RebindEquipSlotsIfDeckReady() { if (!inventoryTooltipsEnabled) { CleanupBindings(); return; } FFDataDeck deck = context.Deck; if (!((Object)(object)deck == (Object)null)) { if (!LocalPlayerDeck.IsOpen(deck)) { tooltips.ClearHover(); return; } binder.TryRebind(deck, tooltips.OnPointerEnter, tooltips.OnPointerExit); RefreshDeckCanvas(deck); } } private void RefreshDeckCanvas(FFDataDeck deck) { if ((Object)(object)deck.inventoryUIGroup != (Object)null) { if (lastInventoryUIGroupForCanvas != deck.inventoryUIGroup) { lastInventoryUIGroupForCanvas = (Object)(object)deck.inventoryUIGroup; context.DeckCanvas = ((Component)deck.inventoryUIGroup).GetComponentInParent(true); } } else { lastInventoryUIGroupForCanvas = null; context.DeckCanvas = ((Component)deck).GetComponentInParent(true); } } private void ApplyInventoryTooltipEnabledState() { bool enableInventoryTooltips = Config.Item.EnableInventoryTooltips; if (enableInventoryTooltips != inventoryTooltipsEnabled) { inventoryTooltipsEnabled = enableInventoryTooltips; if (!enableInventoryTooltips) { CleanupBindings(); } else { RebindEquipSlotsIfDeckReady(); } } } private void CleanupBindings() { binder.UnbindAll(); tooltips.ClearHover(); FairyItemEquipmentResolver.Clear(); } private bool AlreadyWiredTo(FFDataDeck dataDeck) { return Il2CppGameInterop.SameInstance(dataDeck, context.Deck); } } internal static class ForsakenScene { public const string WorldName = "Forsaken Frontiers"; internal static event Action OnWorldLoaded; public static bool IsWorldScene(string sceneName) { return string.Equals(sceneName, "Forsaken Frontiers", StringComparison.Ordinal); } internal static void WorldLoaded(string sceneName) { if (IsWorldScene(sceneName)) { ForsakenScene.OnWorldLoaded?.Invoke(); } } public static bool IsValid(FFDataDeck deck) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) Scene scene = (((Object)(object)deck.inventoryUIGroup != (Object)null) ? ((Component)deck.inventoryUIGroup).gameObject : ((Component)deck).gameObject).scene; return string.Equals(((Scene)(ref scene)).name, "Forsaken Frontiers", StringComparison.Ordinal); } } internal sealed class BoundSlot { public int SlotIndex { get; } public RectTransform HoverRect { get; } public RectTransform TooltipAnchor { get; } public GameObject InspectRoot { get; } public FairyUIItemBase UiItem { get; private set; } public FairyItem LinkedItem { get; private set; } public FFEquipment Equipment { get; private set; } public BoundSlot(int slotIndex, RectTransform hoverRect, RectTransform tooltipAnchor, GameObject inspectRoot) { SlotIndex = slotIndex; HoverRect = hoverRect; TooltipAnchor = tooltipAnchor; InspectRoot = inspectRoot; RefreshItemCache(); } public bool RefreshItemCache() { FairyItem linkedItem = LinkedItem; UiItem = null; LinkedItem = null; Equipment = null; if ((Object)(object)InspectRoot == (Object)null) { return (Object)(object)linkedItem != (Object)null; } FairyUIItemBase[] array = Il2CppArrayBase.op_Implicit(InspectRoot.GetComponentsInChildren(true)); foreach (FairyUIItemBase val in array) { FairyItem linkedToItem = val.LinkedToItem; if (!((Object)(object)linkedToItem == (Object)null)) { UiItem = val; LinkedItem = linkedToItem; Equipment = FairyItemEquipmentResolver.Resolve(linkedToItem); return !Il2CppGameInterop.SameInstance(linkedItem, LinkedItem); } } return (Object)(object)linkedItem != (Object)null; } } internal sealed class EquipSlotBinder { private readonly EquipSlotLocator locator; private readonly EquipSlotHoverBindingRegistry hoverBindingRegistry; private readonly Dictionary slotsByIndex = new Dictionary(); private Transform lastBoundSlotsRoot; private Transform lastSearchRoot; public TextMeshProUGUI StyleTemplate { get; private set; } public int BoundSlotCount => slotsByIndex.Count; public EquipSlotBinder(EquipSlotLocator locator, EquipSlotHoverBindingRegistry hoverBindingRegistry) { this.locator = locator; this.hoverBindingRegistry = hoverBindingRegistry; } public bool TryRebind(FFDataDeck deck, Action onPointerEnter, Action onPointerExit) { if ((Object)(object)deck == (Object)null) { UnbindAll(); return false; } Transform primarySearchRoot = EquipSlotLocator.GetPrimarySearchRoot(deck); if ((Object)(object)lastSearchRoot == (Object)(object)primarySearchRoot && slotsByIndex.Count == 4 && CurrentBindingsAreValid()) { return true; } if (!locator.TryFindSlots(deck, out var slots, out var slotsRootUsed)) { UnbindAll(); return false; } if ((Object)(object)lastBoundSlotsRoot == (Object)(object)slotsRootUsed && slotsByIndex.Count == 4 && CurrentBindingsAreValid()) { return true; } UnbindAll(); lastBoundSlotsRoot = slotsRootUsed; lastSearchRoot = primarySearchRoot; foreach (BoundSlot item in slots) { slotsByIndex[item.SlotIndex] = item; hoverBindingRegistry.Bind(item, onPointerEnter, onPointerExit); } StyleTemplate = locator.FindStyleTemplate(deck, slotsRootUsed); return true; } public void RefreshSlotItemCaches() { foreach (BoundSlot value in slotsByIndex.Values) { value.RefreshItemCache(); } } public void UnbindAll() { hoverBindingRegistry.UnbindAll(); slotsByIndex.Clear(); lastBoundSlotsRoot = null; lastSearchRoot = null; StyleTemplate = null; } private bool CurrentBindingsAreValid() { for (int i = 1; i <= 4; i++) { if (!slotsByIndex.TryGetValue(i, out var value) || (Object)(object)value.HoverRect == (Object)null || (Object)(object)value.TooltipAnchor == (Object)null) { return false; } } return true; } } internal sealed class EquipSlotHoverBindingRegistry { private sealed class PointerHoverRelay { public BoundSlot Slot; public Action OnEnter; public Action OnExit; public void HandleEnter(BaseEventData _) { OnEnter?.Invoke(Slot); } public void HandleExit(BaseEventData _) { OnExit?.Invoke(Slot); } } private sealed class SlotEventBinding { public BoundSlot Slot; public EventTrigger Trigger; public Entry EnterEntry; public Entry ExitEntry; public PointerHoverRelay Relay; public bool CreatedTrigger; public Graphic RaycastGraphic; public bool OriginalRaycastTarget; public bool ChangedRaycastTarget; public bool CreatedRaycastGraphic; } private static readonly Color NearlyInvisibleRaycast = new Color(0f, 0f, 0f, 0.01f); private readonly List eventBindings = new List(); public void Bind(BoundSlot slot, Action onPointerEnter, Action onPointerExit) { SlotEventBinding slotEventBinding = EnsureRaycastTarget(slot.HoverRect); slotEventBinding.Slot = slot; GameObject gameObject = ((Component)slot.HoverRect).gameObject; EventTrigger val = gameObject.GetComponent(); if ((Object)(object)val == (Object)null) { val = gameObject.AddComponent(); slotEventBinding.CreatedTrigger = true; } PointerHoverRelay pointerHoverRelay = new PointerHoverRelay { Slot = slot, OnEnter = onPointerEnter, OnExit = onPointerExit }; slotEventBinding.Trigger = val; slotEventBinding.Relay = pointerHoverRelay; slotEventBinding.EnterEntry = AddEventTriggerEntry(val, (EventTriggerType)0, pointerHoverRelay.HandleEnter); slotEventBinding.ExitEntry = AddEventTriggerEntry(val, (EventTriggerType)1, pointerHoverRelay.HandleExit); eventBindings.Add(slotEventBinding); } public void UnbindAll() { for (int num = eventBindings.Count - 1; num >= 0; num--) { Unbind(eventBindings[num]); } eventBindings.Clear(); } private static void Unbind(SlotEventBinding binding) { if ((Object)(object)binding.Trigger != (Object)null) { if (binding.EnterEntry != null) { binding.Trigger.triggers.Remove(binding.EnterEntry); } if (binding.ExitEntry != null) { binding.Trigger.triggers.Remove(binding.ExitEntry); } if (binding.CreatedTrigger && binding.Trigger.triggers.Count == 0) { Object.Destroy((Object)(object)binding.Trigger); } } if (binding.CreatedRaycastGraphic && (Object)(object)binding.RaycastGraphic != (Object)null) { Object.Destroy((Object)(object)binding.RaycastGraphic); } else if (binding.ChangedRaycastTarget && (Object)(object)binding.RaycastGraphic != (Object)null) { binding.RaycastGraphic.raycastTarget = binding.OriginalRaycastTarget; } } private static Entry AddEventTriggerEntry(EventTrigger trigger, EventTriggerType type, Action handler) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown Entry val = new Entry { eventID = type }; ((UnityEvent)(object)val.callback).AddListener(DelegateSupport.ConvertDelegate>((Delegate)handler)); trigger.triggers.Add(val); return val; } private static SlotEventBinding EnsureRaycastTarget(RectTransform rect) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) SlotEventBinding slotEventBinding = new SlotEventBinding(); if ((Object)(object)rect == (Object)null) { return slotEventBinding; } Graphic component = ((Component)rect).GetComponent(); if ((Object)(object)component == (Object)null) { Image val = ((Component)rect).gameObject.AddComponent(); ((Graphic)val).color = NearlyInvisibleRaycast; ((Graphic)val).raycastTarget = true; slotEventBinding.RaycastGraphic = (Graphic)(object)val; slotEventBinding.CreatedRaycastGraphic = true; } else if (!component.raycastTarget) { slotEventBinding.RaycastGraphic = component; slotEventBinding.OriginalRaycastTarget = component.raycastTarget; slotEventBinding.ChangedRaycastTarget = true; component.raycastTarget = true; } return slotEventBinding; } } internal sealed class EquipSlotLocator { public const int EquipSlotCount = 4; public bool TryFindSlots(FFDataDeck deck, out IReadOnlyList slots, out Transform slotsRootUsed) { slots = Array.Empty(); slotsRootUsed = null; foreach (Transform deckSearchRoot in GetDeckSearchRoots(deck)) { foreach (Transform item in EnumerateNamedSlotsTransforms(deckSearchRoot)) { if (TryBuildSlotsFromSlotsRoot(item, out var result)) { slots = result; slotsRootUsed = item; return true; } } } return false; } public TextMeshProUGUI FindStyleTemplate(FFDataDeck deck, Transform slotsRoot) { if ((Object)(object)deck == (Object)null) { return null; } TextMeshProUGUI[] array = Il2CppArrayBase.op_Implicit(((Component)GetPrimarySearchRoot(deck)).GetComponentsInChildren(true)); TextMeshProUGUI val = null; float num = -1f; TextMeshProUGUI[] array2 = array; foreach (TextMeshProUGUI val2 in array2) { if (!((Object)(object)val2 == (Object)null) && ((Component)val2).gameObject.activeInHierarchy && (!((Object)(object)slotsRoot != (Object)null) || !((TMP_Text)val2).transform.IsChildOf(slotsRoot)) && ((TMP_Text)val2).fontSize > num) { num = ((TMP_Text)val2).fontSize; val = val2; } } if ((Object)(object)val != (Object)null) { return val; } for (int j = 0; j < array.Length; j++) { if ((Object)(object)array[j] != (Object)null) { return array[j]; } } return null; } public static Transform GetPrimarySearchRoot(FFDataDeck deck) { if (!((Object)(object)deck.inventoryUIGroup != (Object)null)) { return ((Component)deck).transform; } return ((Component)deck.inventoryUIGroup).transform; } private static IEnumerable GetDeckSearchRoots(FFDataDeck deck) { if ((Object)(object)deck.inventoryUIGroup != (Object)null) { yield return ((Component)deck.inventoryUIGroup).transform; } yield return ((Component)deck).transform; } private static IEnumerable EnumerateNamedSlotsTransforms(Transform searchRoot) { foreach (Transform componentsInChild in ((Component)searchRoot).GetComponentsInChildren(true)) { if (((Object)componentsInChild).name.Equals("slots", StringComparison.OrdinalIgnoreCase)) { yield return componentsInChild; } } } private static bool TryBuildSlotsFromSlotsRoot(Transform slotsRoot, out IReadOnlyList result) { List list = new List(4); for (int i = 1; i <= 4; i++) { Transform val = FindSlotTransform(slotsRoot, i); if ((Object)(object)val == (Object)null) { result = Array.Empty(); return false; } RectTransform component = ((Component)val).GetComponent(); RectTransform val2 = ResolveTooltipAnchor(val); if ((Object)(object)component == (Object)null || (Object)(object)val2 == (Object)null) { result = Array.Empty(); return false; } list.Add(new BoundSlot(i, component, val2, ((Component)val).gameObject)); } result = list; return true; } private static Transform FindSlotTransform(Transform slotsRoot, int index) { string text = "slot" + index; Transform val = FindDirectChild(slotsRoot, text, "Slot" + index); if ((Object)(object)val != (Object)null) { return val; } return FindDescendantByName(slotsRoot, text, skipRoot: true); } private static RectTransform ResolveTooltipAnchor(Transform slotRoot) { RectTransform val = TryRectTransform(FindDirectChild(slotRoot, "ui_item", "UI_Item")); if ((Object)(object)val != (Object)null) { return val; } val = TryRectTransform(FindDescendantByName(slotRoot, "ui_item", skipRoot: false)); return val ?? ((Component)slotRoot).GetComponent(); } private static RectTransform TryRectTransform(Transform transform) { if (!((Object)(object)transform != (Object)null)) { return null; } return ((Component)transform).GetComponent(); } private static Transform FindDirectChild(Transform parent, params string[] names) { foreach (string text in names) { Transform val = parent.Find(text); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Transform FindDescendantByName(Transform root, string matchName, bool skipRoot) { foreach (Transform componentsInChild in ((Component)root).GetComponentsInChildren(true)) { if ((!skipRoot || !((Object)(object)componentsInChild == (Object)(object)root)) && ((Object)componentsInChild).name.Equals(matchName, StringComparison.OrdinalIgnoreCase)) { return componentsInChild; } } return null; } } internal sealed class EquipSlotHoverTooltips { private readonly EquipSlotBinder binder; private readonly TooltipPresenter presenter = new TooltipPresenter(); private readonly TooltipContentBuilder contentBuilder = new TooltipContentBuilder(); private readonly ActiveDataDeckContext context; private BoundSlot hoveredSlot; private bool lastReveal; public EquipSlotHoverTooltips(EquipSlotBinder binder, ActiveDataDeckContext context) { this.binder = binder; this.context = context; } public void OnPointerEnter(BoundSlot slot) { hoveredSlot = slot; lastReveal = IsRevealKeyHeld(); Refresh(); } public void OnUpdate() { if (hoveredSlot != null) { bool flag = IsRevealKeyHeld(); if (flag != lastReveal) { lastReveal = flag; Refresh(); } } } public void OnPointerExit(BoundSlot slot) { if (hoveredSlot != null && hoveredSlot.SlotIndex == slot.SlotIndex) { hoveredSlot = null; presenter.Hide(); } } public void ClearHover() { hoveredSlot = null; presenter.Hide(); } public void Dispose() { presenter.Destroy(); } public void RefreshHover() { if (hoveredSlot != null) { Refresh(); } } private static bool IsRevealKeyHeld() { if (!Input.GetKey((KeyCode)304)) { return Input.GetKey((KeyCode)303); } return true; } private void Refresh() { if ((Object)(object)context.Deck == (Object)null || (Object)(object)context.DeckCanvas == (Object)null || hoveredSlot == null) { presenter.Hide(); return; } hoveredSlot.RefreshItemCache(); if (!contentBuilder.TryBuild(hoveredSlot, lastReveal, out var data)) { presenter.Hide(); return; } FlavorData flavor = null; if (!context.TryGetLocalFlavor(out flavor)) { FFPlayer current = FairyLocalPlayer.Current; flavor = ((current != null) ? current.Flavor : null); } presenter.Show(context.DeckCanvas, hoveredSlot.HoverRect, hoveredSlot.TooltipAnchor, data, 8f, 0f, binder.StyleTemplate, flavor); } } internal static class ShadowPropertySetter { private static readonly PropertyInfo EffectColorProperty = GetShadowProperty("effectColor"); private static readonly PropertyInfo EffectDistanceProperty = GetShadowProperty("effectDistance"); private static readonly PropertyInfo UseGraphicAlphaProperty = GetShadowProperty("useGraphicAlpha"); public static void Set(Outline outline, string propertyName, object value) { if (!((Object)(object)outline == (Object)null)) { Shadow val = ((Il2CppObjectBase)outline).TryCast(); if (!((Object)(object)val == (Object)null)) { ResolveProperty(propertyName)?.SetValue(val, value); } } } private static PropertyInfo ResolveProperty(string propertyName) { return propertyName switch { "effectColor" => EffectColorProperty, "effectDistance" => EffectDistanceProperty, "useGraphicAlpha" => UseGraphicAlphaProperty, _ => null, }; } private static PropertyInfo GetShadowProperty(string propertyName) { return typeof(Shadow).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public); } } internal sealed class TooltipContentBuilder { private readonly List lines = new List(); public bool TryBuild(BoundSlot slot, bool reveal, out TooltipData data) { data = null; FFEquipment equipment = slot.Equipment; string text = EquipmentTitleResolver.Resolve(slot); if (string.IsNullOrWhiteSpace(text)) { return false; } lines.Clear(); if (string.Equals(text, "[NEW ITEM]", StringComparison.Ordinal)) { lines.Add("Check for an update to FairyDust.Tooltip"); } if ((Object)(object)equipment != (Object)null) { AppendEquipmentBodyLines(equipment, reveal); } data = new TooltipData(text, lines); return true; } private void AppendEquipmentBodyLines(FFEquipment equipment, bool reveal) { bool num = Config.Item.ShowDurability || reveal; bool flag = Config.Item.ShowMetal || reveal; bool flag2 = Config.Item.ShowWaterproof || reveal; bool flag3 = Config.Item.ShowEmp || reveal; if (num) { AppendDurability(equipment); } if (flag) { AppendMaterial(equipment); } if (flag2) { AppendWater(equipment); } if (flag3) { AppendEmp(equipment); } } private void AppendDurability(FFEquipment equipment) { if (TryGetDurability(equipment, out var remaining, out var max)) { lines.Add((max > 0) ? $"Durability {remaining} / {max}" : $"Durability {remaining}"); } } private void AppendMaterial(FFEquipment equipment) { if (equipment.IsMetal) { lines.Add("Metal"); } } private void AppendWater(FFEquipment equipment) { lines.Add(equipment.DisabledWhenSubmerged ? "Not waterproof" : "Waterproof"); } private void AppendEmp(FFEquipment equipment) { lines.Add(equipment.IsDisabledByEmp ? "Vulnerable to EMP" : "EMP safe"); } private static bool TryGetDurability(FFEquipment equipment, out int remaining, out int max) { FFSprayTool val = ((Il2CppObjectBase)equipment).TryCast(); if ((Object)(object)val != (Object)null) { remaining = val.currentPaint; max = val.paint; if (max <= 0) { return remaining > 0; } return true; } if (equipment.useDurability && equipment.maxDurability > 0) { remaining = equipment.durability; max = equipment.maxDurability; return true; } remaining = 0; max = 0; return false; } } internal sealed class TooltipData { public string Title { get; } public IReadOnlyList Lines { get; } public TooltipData(string title, IEnumerable lines) { Title = title; Lines = lines?.ToArray() ?? Array.Empty(); } } internal sealed class TooltipPresenter { private const string RootName = "FairyDust_EquipTooltip"; private const float TitleFontScaleFromTemplate = 0.65f; private const float MinTitleFontSize = 12f; private const float MaxTitleFontSize = 21f; private const float FallbackTitleFontSize = 14f; private const float DetailFontScale = 0.76f; private const int MaxMeasuredTextCacheEntries = 32; private readonly StringBuilder textBuilder = new StringBuilder(); private readonly Dictionary measuredSizeByText = new Dictionary(StringComparer.Ordinal); private GameObject rootObject; private Canvas canvasRef; private RectTransform canvasRectCached; private RectTransform panelRect; private RectTransform accentRect; private CanvasGroup canvasGroup; private TextMeshProUGUI text; private Image panelBackground; private Outline panelOutline; private Image accentBar; private string lastRenderedText; public void Hide() { if ((Object)(object)rootObject != (Object)null && (Object)(object)canvasGroup != (Object)null) { canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; } } public void Show(Canvas canvas, RectTransform slotRect, RectTransform iconAlignRect, TooltipData data, float offsetX, float offsetY, TextMeshProUGUI template, FlavorData flavor) { if ((Object)(object)canvas == (Object)null || (Object)(object)slotRect == (Object)null || data == null) { Hide(); return; } EnsureCreated(canvas, template); ApplyFlavorStyling(flavor); bool needMeshRebuild = !rootObject.activeSelf; rootObject.SetActive(true); canvasGroup.alpha = 1f; UpdateText(data, needMeshRebuild); Position(slotRect, iconAlignRect, offsetX, offsetY); } private void EnsureCreated(Canvas canvas, TextMeshProUGUI template) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Expected O, but got Unknown //IL_02a6: Unknown result type (might be due to invalid IL or missing references) //IL_02b1: Unknown result type (might be due to invalid IL or missing references) //IL_02c6: Unknown result type (might be due to invalid IL or missing references) //IL_02da: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)rootObject != (Object)null) { if ((Object)(object)rootObject.transform.parent != (Object)(object)((Component)canvas).transform) { rootObject.transform.SetParent(((Component)canvas).transform, false); } if (canvasRef != canvas) { canvasRef = canvas; canvasRectCached = ((Component)canvas).GetComponent(); } return; } canvasRef = canvas; canvasRectCached = ((Component)canvas).GetComponent(); rootObject = new GameObject("FairyDust_EquipTooltip"); rootObject.transform.SetParent(((Component)canvas).transform, false); rootObject.SetActive(false); panelRect = rootObject.AddComponent(); canvasGroup = rootObject.AddComponent(); panelBackground = rootObject.AddComponent(); panelOutline = rootObject.AddComponent(); panelRect.anchorMin = new Vector2(0.5f, 0.5f); panelRect.anchorMax = new Vector2(0.5f, 0.5f); panelRect.pivot = new Vector2(0f, 0.5f); panelRect.sizeDelta = new Vector2(180f, 64f); canvasGroup.alpha = 0f; canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; ((Graphic)panelBackground).raycastTarget = false; ShadowPropertySetter.Set(panelOutline, "effectDistance", (object)new Vector2(1f, -1f)); ShadowPropertySetter.Set(panelOutline, "useGraphicAlpha", true); GameObject val = new GameObject("Accent"); val.transform.SetParent(rootObject.transform, false); accentRect = val.AddComponent(); accentRect.anchorMin = new Vector2(0f, 0f); accentRect.anchorMax = new Vector2(0f, 1f); accentRect.pivot = new Vector2(0f, 0.5f); accentRect.anchoredPosition = Vector2.zero; accentRect.sizeDelta = new Vector2(4f, 0f); accentBar = val.AddComponent(); ((Graphic)accentBar).raycastTarget = false; GameObject val2 = new GameObject("Text"); val2.transform.SetParent(rootObject.transform, false); RectTransform obj = val2.AddComponent(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.offsetMin = new Vector2(14f, 6f); obj.offsetMax = new Vector2(-12f, -6f); text = val2.AddComponent(); ((Graphic)text).raycastTarget = false; ((TMP_Text)text).enableWordWrapping = false; ((TMP_Text)text).overflowMode = (TextOverflowModes)0; ((TMP_Text)text).alignment = (TextAlignmentOptions)257; ((TMP_Text)text).richText = true; ((TMP_Text)text).lineSpacing = 0f; ((TMP_Text)text).paragraphSpacing = 0f; ((TMP_Text)text).fontStyle = (FontStyles)33; if ((Object)(object)template != (Object)null) { ((TMP_Text)text).font = ((TMP_Text)template).font; ((TMP_Text)text).fontSharedMaterial = ((TMP_Text)template).fontSharedMaterial; ((TMP_Text)text).fontSize = Mathf.Clamp(((TMP_Text)template).fontSize * 0.65f, 12f, 21f); } else { ((TMP_Text)text).fontSize = 14f; } } public void Destroy() { if ((Object)(object)rootObject != (Object)null) { Object.Destroy((Object)(object)rootObject); } rootObject = null; canvasRef = null; canvasRectCached = null; panelRect = null; accentRect = null; canvasGroup = null; text = null; panelBackground = null; panelOutline = null; accentBar = null; lastRenderedText = null; measuredSizeByText.Clear(); } private void ApplyFlavorStyling(FlavorData flavor) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Color primary; Color panelFill; if (flavor != null) { primary = flavor.color; primary.a = 1f; panelFill = PanelFillFromSource(flavor.DarkAccentColor, 0.42f); } else { primary = Color.white; panelFill = PanelFillFromSource(Color.white, 0.38f); } ApplyThemedChrome(primary, panelFill); } private static Color PanelFillFromSource(Color source, float mix) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) return new Color(Mathf.Lerp(0.02f, source.r, mix), Mathf.Lerp(0.02f, source.g, mix), Mathf.Lerp(0.02f, source.b, mix), 0.96f); } private void ApplyThemedChrome(Color primary, Color panelFill) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) ((Graphic)panelBackground).color = panelFill; ShadowPropertySetter.Set(panelOutline, "effectColor", (object)new Color(primary.r, primary.g, primary.b, 0.92f)); ((Graphic)accentBar).color = new Color(primary.r, primary.g, primary.b, 1f); ((Graphic)text).color = new Color(primary.r, primary.g, primary.b, 1f); } private void UpdateText(TooltipData data, bool needMeshRebuild) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) string value = Mathf.Max(8f, ((TMP_Text)this.text).fontSize * 0.76f).ToString(CultureInfo.InvariantCulture); textBuilder.Clear(); textBuilder.Append(data.Title); foreach (string line in data.Lines) { textBuilder.Append('\n'); textBuilder.Append("'); textBuilder.Append(line); textBuilder.Append(""); } string text = textBuilder.ToString(); bool flag = !string.Equals(lastRenderedText, text, StringComparison.Ordinal); if (flag) { ((TMP_Text)this.text).text = text; lastRenderedText = text; } if (!needMeshRebuild && measuredSizeByText.TryGetValue(text, out var value2)) { panelRect.sizeDelta = value2; return; } if (needMeshRebuild || flag) { ((TMP_Text)this.text).ForceMeshUpdate(true, true); } Vector2 preferredValues = ((TMP_Text)this.text).GetPreferredValues(text); float num = Mathf.Clamp(preferredValues.x + 28f, 120f, 320f); float num2 = Mathf.Clamp(preferredValues.y + 12f, 30f, 180f); Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(num, num2); panelRect.sizeDelta = val; if (measuredSizeByText.Count < 32) { measuredSizeByText[text] = val; } } private void Position(RectTransform slotRect, RectTransform iconAlignRect, float offsetX, float offsetY) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) Canvas val = canvasRef; RectTransform obj = canvasRectCached; Camera val2 = (((int)val.renderMode == 0) ? null : val.worldCamera); RectTransform val3 = (((Object)(object)iconAlignRect != (Object)null) ? iconAlignRect : slotRect); Rect rect = slotRect.rect; float xMax = ((Rect)(ref rect)).xMax; rect = slotRect.rect; Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(xMax, ((Rect)(ref rect)).center.y, 0f); rect = val3.rect; float x = ((Rect)(ref rect)).center.x; rect = val3.rect; Vector3 val5 = default(Vector3); ((Vector3)(ref val5))..ctor(x, ((Rect)(ref rect)).center.y, 0f); Vector2 val6 = RectTransformUtility.WorldToScreenPoint(val2, ((Transform)slotRect).TransformPoint(val4)); Vector2 val7 = RectTransformUtility.WorldToScreenPoint(val2, ((Transform)val3).TransformPoint(val5)); Vector2 val8 = default(Vector2); ((Vector2)(ref val8))..ctor(val6.x, val7.y); Vector2 val9 = default(Vector2); RectTransformUtility.ScreenPointToLocalPointInRectangle(obj, val8, val2, ref val9); Vector2 sizeDelta = panelRect.sizeDelta; val9.x += offsetX; val9.y += offsetY; Rect rect2 = obj.rect; val9.x = Mathf.Clamp(val9.x, ((Rect)(ref rect2)).xMin + 6f, ((Rect)(ref rect2)).xMax - sizeDelta.x - 6f); val9.y = Mathf.Clamp(val9.y, ((Rect)(ref rect2)).yMin + sizeDelta.y * 0.5f + 6f, ((Rect)(ref rect2)).yMax - sizeDelta.y * 0.5f - 6f); panelRect.anchoredPosition = val9; } } } namespace FairyDust.Tooltip.Features.InventoryTooltip.Patches { internal static class FFDataDeckPatches { [HarmonyPatch(typeof(FFDataDeck), "OpenDataDeck")] internal static class OpenDataDeckPatch { [HarmonyPostfix] public static void Postfix(FFDataDeck __instance) { InventoryTooltipModule.Instance?.TryAdoptLocalDeck(__instance); } } [HarmonyPatch(typeof(FFDataDeck), "RefreshData")] internal static class RefreshDataPatch { [HarmonyPostfix] public static void Postfix(FFDataDeck __instance) { InventoryTooltipModule.Instance?.OnHarmonyDeckRefreshData(__instance); } } [HarmonyPatch(typeof(FFDataDeck), "ApplyFlavor")] internal static class ApplyFlavorPatch { [HarmonyPostfix] public static void Postfix(FFDataDeck __instance, FlavorData flavor) { InventoryTooltipModule.Instance?.RememberLocalFlavor(__instance, flavor); } } } } namespace FairyDust.Tooltip.Configuration { public static class Config { private readonly struct DisplayNameReader { public Func Read { get; } public DisplayNameReader(Func read) { Read = read; } } private static readonly ItemPreferences DefaultItem = new ItemPreferences(); private static readonly Dictionary DisplayNameReaders = BuildDisplayNameReaders(); private static IFairyDustConfig config; public static string ConfigFilePath { get; private set; } = string.Empty; public static ItemPreferences Item { get; private set; } = new ItemPreferences(); public static void Initialize(IFairyDustHost host) { if (host == null) { throw new ArgumentNullException("host"); } config = host.CreateConfig("Item"); config.Initialize(); Item = config.Values; ConfigFilePath = config.FilePath; } public static void Save() { GetConfig().Save(); Item = GetConfig().Values; } public static bool ReloadIfChanged() { bool num = GetConfig().ReloadIfChanged(); if (num) { Item = GetConfig().Values; } return num; } public static string GetDisplayName(string itemName) { string text = TryGetName(Item, itemName); if (!string.IsNullOrWhiteSpace(text)) { string text2 = TryGetName(DefaultItem, itemName); string text3 = text.Trim(); if (!string.Equals(text3, text2?.Trim(), StringComparison.Ordinal)) { return ItemDisplayNames.CleanDisplayTitle(text3); } } return TryGetCachedDisplayName(itemName); } public static void CacheDisplayName(IEnumerable itemNames, string displayName) { if (string.IsNullOrWhiteSpace(displayName)) { return; } string text = ItemDisplayNames.CleanDisplayTitle(displayName); if (string.IsNullOrWhiteSpace(text)) { return; } string[] array = (from name in itemNames select NormalizeKey(ItemDisplayNames.CleanSourceName(name)) into key where key.Length > 0 select key).Distinct(StringComparer.Ordinal).ToArray(); if (array.Length == 0) { return; } Dictionary dictionary = DisplayNameCacheCodec.Parse(Item.DisplayNameCache); bool flag = false; string[] array2 = array; foreach (string key2 in array2) { if (!dictionary.TryGetValue(key2, out var value) || !string.Equals(value, text, StringComparison.Ordinal)) { dictionary[key2] = text; flag = true; } } string text2 = DisplayNameCacheCodec.Serialize(dictionary); if (!string.Equals(Item.DisplayNameCache, text2, StringComparison.Ordinal)) { Item.DisplayNameCache = text2; flag = true; } if (flag) { Save(); } } private static string TryGetName(ItemPreferences p, string name) { if (name == null || !DisplayNameReaders.TryGetValue(NormalizeKey(name), out var value)) { return null; } return value.Read(p); } private static Dictionary BuildDisplayNameReaders() { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); FieldInfo[] fields = typeof(ItemPreferences).GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.FieldType != typeof(string)) && !(fieldInfo.Name == "DisplayNameCache")) { AddField(dictionary, fieldInfo); } } AddAlias(dictionary, "adrenaline", "Adrenaline_Shot"); AddAlias(dictionary, "environment marker", "Spraymark"); AddAlias(dictionary, "spray tool", "Spraymark"); AddAlias(dictionary, "soda", "Noisemaker"); AddAlias(dictionary, "soda can", "Noisemaker"); AddAlias(dictionary, "gun", "Shotgun"); AddAlias(dictionary, "uv", "UVLight"); AddAlias(dictionary, "ultraviolet light", "UVLight"); return dictionary; } private static void AddField(Dictionary readers, FieldInfo field) { Add(readers, field.Name, new DisplayNameReader((ItemPreferences p) => (string)field.GetValue(p))); } private static void AddAlias(Dictionary readers, string alias, string fieldName) { if (readers.TryGetValue(NormalizeKey(fieldName), out var value)) { Add(readers, alias, value); } } private static void Add(Dictionary readers, string name, DisplayNameReader reader) { string text = NormalizeKey(name); if (text.Length > 0) { readers[text] = reader; } } internal static string NormalizeKey(string name) { if (string.IsNullOrWhiteSpace(name)) { return string.Empty; } return new string(name.Where(char.IsLetterOrDigit).Select(char.ToLowerInvariant).ToArray()); } private static IFairyDustConfig GetConfig() { return config ?? throw new InvalidOperationException("Config.Initialize() must be called before using preferences."); } private static string TryGetCachedDisplayName(string itemName) { string text = NormalizeKey(ItemDisplayNames.CleanSourceName(itemName)); if (text.Length == 0) { return null; } if (!DisplayNameCacheCodec.Parse(Item.DisplayNameCache).TryGetValue(text, out var value) || string.IsNullOrWhiteSpace(value)) { return null; } return ItemDisplayNames.CleanDisplayTitle(value); } } internal static class DisplayNameCacheCodec { public static Dictionary Parse(string serialized) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (string.IsNullOrWhiteSpace(serialized)) { return dictionary; } string[] array = serialized.Split(new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { int num = text.IndexOf('='); if (num > 0) { string text2 = Config.NormalizeKey(SafeUnescape(text.Substring(0, num))); string text3 = SafeUnescape(text.Substring(num + 1)).Trim(); if (text2.Length > 0 && text3.Length > 0) { dictionary[text2] = text3; } } } return dictionary; } public static string Serialize(IReadOnlyDictionary cache) { if (cache == null || cache.Count == 0) { return string.Empty; } return string.Join(";", from entry in cache.Where((KeyValuePair entry) => entry.Key.Length > 0 && !string.IsNullOrWhiteSpace(entry.Value)).OrderBy, string>((KeyValuePair entry) => entry.Key, StringComparer.Ordinal) select Uri.EscapeDataString(entry.Key) + "=" + Uri.EscapeDataString(entry.Value.Trim())); } private static string SafeUnescape(string value) { try { return Uri.UnescapeDataString(value); } catch (UriFormatException) { return value; } } } public static class ItemDisplayNames { public const string NewItemTitle = "[NEW ITEM]"; public const string NewItemDescription = "Check for an update to FairyDust.Tooltip"; public static string GetTitle(string fairyName) { if (string.IsNullOrEmpty(fairyName)) { return "[NEW ITEM]"; } return GetConfiguredTitle(fairyName) ?? GetFallbackTitle(fairyName) ?? "[NEW ITEM]"; } internal static string GetConfiguredTitle(string itemName) { return Config.GetDisplayName(itemName); } internal static string GetFallbackTitle(string itemName) { string text = CleanSourceName(itemName); if (string.IsNullOrWhiteSpace(text)) { return null; } return ToDisplayTitle(text); } internal static string CleanDisplayTitle(string displayTitle) { string text = CleanSourceName(displayTitle); if (string.IsNullOrWhiteSpace(text)) { return null; } text = Regex.Replace(text, "\\s*[\\(\\[\\{][^\\)\\]\\}]*\\d+\\s*%[^\\)\\]\\}]*[\\)\\]\\}]\\s*", " "); text = Regex.Replace(text, "^\\s*(sale|discount)\\s*[-:]?\\s*\\d+\\s*%\\s*", string.Empty, RegexOptions.IgnoreCase); text = Regex.Replace(text, "\\s*[-:]*\\s*\\d+\\s*%\\s*(off|discount)?\\s*$", string.Empty, RegexOptions.IgnoreCase); return Regex.Replace(text, "\\s+", " ").Trim(); } internal static string CleanSourceName(string itemName) { if (string.IsNullOrWhiteSpace(itemName)) { return null; } return TrimPrefix(TrimSuffix(TrimSuffix(itemName.Trim(), "(Clone)"), "Clone"), "FF").Trim(); } private static string ToDisplayTitle(string source) { string[] array = SeparateWords(source).Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return null; } StringBuilder stringBuilder = new StringBuilder(); TextInfo textInfo = CultureInfo.InvariantCulture.TextInfo; for (int i = 0; i < array.Length; i++) { if (i > 0) { stringBuilder.Append(' '); } stringBuilder.Append(FormatWord(array[i], textInfo)); } return stringBuilder.ToString(); } private static string SeparateWords(string source) { StringBuilder stringBuilder = new StringBuilder(source.Length + 8); for (int i = 0; i < source.Length; i++) { char c = source[i]; char c2 = ((c == '_' || c == '-') ? ' ' : c); if (c2 == ' ') { AppendSingleSpace(stringBuilder); continue; } char previous = ((i > 0) ? source[i - 1] : '\0'); char next = ((i + 1 < source.Length) ? source[i + 1] : '\0'); if (stringBuilder.Length > 0 && ShouldStartNewWord(previous, c2, next)) { AppendSingleSpace(stringBuilder); } stringBuilder.Append(c2); } return stringBuilder.ToString(); } private static bool ShouldStartNewWord(char previous, char current, char next) { if ((!char.IsLower(previous) || !char.IsUpper(current)) && (!char.IsLetter(previous) || !char.IsDigit(current)) && (!char.IsDigit(previous) || !char.IsLetter(current))) { if (char.IsUpper(previous) && char.IsUpper(current)) { return char.IsLower(next); } return false; } return true; } private static string FormatWord(string word, TextInfo textInfo) { string text = word.Trim(); string text2 = Config.NormalizeKey(text); if (text2 == "uv" || text2 == "emp") { return text2.ToUpperInvariant(); } if (text.Length <= 1) { return text.ToUpperInvariant(); } return textInfo.ToTitleCase(text.ToLowerInvariant()); } private static void AppendSingleSpace(StringBuilder builder) { if (builder.Length > 0 && builder[builder.Length - 1] != ' ') { builder.Append(' '); } } private static string TrimPrefix(string value, string prefix) { if (!value.StartsWith(prefix, StringComparison.Ordinal)) { return value; } return value.Substring(prefix.Length); } private static string TrimSuffix(string value, string suffix) { if (!value.EndsWith(suffix, StringComparison.Ordinal)) { return value; } return value.Substring(0, value.Length - suffix.Length); } } public sealed class ItemPreferences { public bool ShowMetal = true; public bool ShowWaterproof; public bool ShowEmp; public bool ShowDurability = true; public bool EnableInventoryTooltips = true; public string DisplayNameCache = ""; public string Pickaxe = "Pickaxe"; public string Flashlight = "Flashlight"; public string Medkit = "Medkit"; public string Lantern = "Lantern"; public string Glowstick = "Glowstick"; public string Adrenaline_Shot = "Adrenaline Shot"; public string Walkietalkie = "Walkie-Talkie"; public string Sledgehammer = "Sledgehammer"; public string Boltcutters = "Boltcutters"; public string Spraymark = "Environment Marker"; public string Gasmask = "Gasmask"; public string Stunlight = "Stun Light"; public string Firecrackers = "Firecrackers"; public string Rebreather = "Rebreather"; public string Dynamite = "Dynamite"; public string Noisemaker = "Soda Can"; public string Shotgun = "Shotgun"; public string Defib = "Defibrillator"; public string Flare = "Flare"; public string Pager = "Pager"; public string UVLight = "UV Light"; } } namespace FairyDust.Tooltip.BepInEx { internal sealed class ConfigAdapter : IFairyDustConfig where T : new() { private readonly ConfigFile file; private readonly string sectionName; private readonly Dictionary entries = new Dictionary(); private DateTime lastWriteUtc = DateTime.MinValue; public string FilePath => file.ConfigFilePath; public T Values { get; private set; } = new T(); public ConfigAdapter(ConfigFile file, string sectionName) { this.file = file; this.sectionName = sectionName; } public void Initialize() { Directory.CreateDirectory(Path.GetDirectoryName(FilePath) ?? string.Empty); BindFields(); ReadValues(); file.Save(); lastWriteUtc = GetWriteUtc(); } public void Save() { foreach (KeyValuePair entry in entries) { entry.Value.BoxedValue = entry.Key.GetValue(Values); } file.Save(); lastWriteUtc = GetWriteUtc(); } public bool ReloadIfChanged() { DateTime writeUtc = GetWriteUtc(); if (writeUtc == DateTime.MinValue || writeUtc <= lastWriteUtc) { return false; } T values; bool num = TryReadValuesFromFile(out values); lastWriteUtc = writeUtc; if (!num) { return false; } Values = values; return true; } private void BindFields() { FieldInfo[] fields = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { object value = fieldInfo.GetValue(Values); entries[fieldInfo] = BindField(fieldInfo, value); } } private ConfigEntryBase BindField(FieldInfo field, object defaultValue) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown return (ConfigEntryBase)typeof(ConfigFile).GetMethods().First((MethodInfo method) => method.Name == "Bind" && method.IsGenericMethodDefinition && method.GetParameters().Length == 4).MakeGenericMethod(field.FieldType) .Invoke(file, new object[4] { sectionName, field.Name, defaultValue, "FairyDust.Tooltip option." }); } private void ReadValues() { T val = new T(); foreach (KeyValuePair entry in entries) { entry.Key.SetValue(val, entry.Value.BoxedValue); } Values = val; } private bool TryReadValuesFromFile(out T values) { values = new T(); if (!File.Exists(FilePath)) { return false; } Dictionary dictionary = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public).ToDictionary((FieldInfo field) => field.Name, StringComparer.Ordinal); bool flag = false; string[] array = File.ReadAllLines(FilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0 || text.StartsWith("#", StringComparison.Ordinal)) { continue; } if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) { flag = string.Equals(text.Substring(1, text.Length - 2).Trim(), sectionName, StringComparison.Ordinal); } else { if (!flag) { continue; } int num = text.IndexOf('='); if (num > 0) { string key = text.Substring(0, num).Trim(); if (dictionary.TryGetValue(key, out var value) && TryConvert(text.Substring(num + 1).Trim(), value.FieldType, out var value2)) { value.SetValue(values, value2); } } } } return true; } private static bool TryConvert(string text, Type targetType, out object value) { if (targetType == typeof(string)) { value = text; return true; } if (targetType == typeof(bool) && bool.TryParse(text, out var result)) { value = result; return true; } if (targetType == typeof(int) && int.TryParse(text, out var result2)) { value = result2; return true; } value = null; return false; } private DateTime GetWriteUtc() { if (!File.Exists(FilePath)) { return DateTime.MinValue; } return File.GetLastWriteTimeUtc(FilePath); } } internal sealed class HostAdapter : IFairyDustHost { private readonly BasePlugin plugin; private readonly Harmony harmony; public string LoaderName => "BepInEx"; public string GameRootDirectory => Paths.GameRootPath; public string ConfigDirectory => Paths.ConfigPath; public IFairyDustLogger Log { get; } public HostAdapter(BasePlugin plugin) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected O, but got Unknown this.plugin = plugin; harmony = new Harmony("com.fairydust.tooltip"); Log = new LoggerAdapter(plugin.Log); } public IFairyDustConfig CreateConfig(string categoryName) where T : new() { return new ConfigAdapter(plugin.Config, categoryName); } public void PatchAll(Assembly assembly) { harmony.PatchAll(assembly); } public void UnpatchSelf() { harmony.UnpatchSelf(); } } internal sealed class LoggerAdapter : IFairyDustLogger { private readonly ManualLogSource logger; public LoggerAdapter(ManualLogSource logger) { this.logger = logger; } public void Info(string message) { logger.LogInfo((object)message); } public void Warning(string message) { logger.LogWarning((object)message); } public void Error(string message) { logger.LogError((object)message); } public void Error(string message, Exception exception) { logger.LogError((object)(message + Environment.NewLine + exception)); } } [BepInPlugin("com.fairydust.tooltip", "FairyDust.Tooltip", "2.0.0")] [BepInProcess("Forsaken Frontiers.exe")] public sealed class Plugin : BasePlugin { private FairyDustTooltipMod mod; private UpdateBridge updateBridge; public override void Load() { mod = new FairyDustTooltipMod(new HostAdapter((BasePlugin)(object)this)); mod.Initialize(); RuntimeState.Mod = mod; updateBridge = ((BasePlugin)this).AddComponent(); } public override bool Unload() { if ((Object)(object)updateBridge != (Object)null) { updateBridge.DetachRuntime(); Object.Destroy((Object)(object)updateBridge); updateBridge = null; } mod?.Shutdown(); if (RuntimeState.Mod == mod) { RuntimeState.Mod = null; } mod = null; return true; } } internal static class RuntimeState { public static FairyDustTooltipMod Mod { get; set; } } public sealed class UpdateBridge : MonoBehaviour { private FairyDustTooltipMod mod; private int lastSceneBuildIndex = int.MinValue; private string lastSceneName = string.Empty; private void Awake() { mod = RuntimeState.Mod; ForwardActiveSceneIfChanged(); } private void Update() { ForwardActiveSceneIfChanged(); mod?.OnUpdate(); } public void DetachRuntime() { if (RuntimeState.Mod == mod) { RuntimeState.Mod = null; } mod = null; } private void OnDestroy() { DetachRuntime(); } private void ForwardActiveSceneIfChanged() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).buildIndex != lastSceneBuildIndex || !(((Scene)(ref activeScene)).name == lastSceneName)) { lastSceneBuildIndex = ((Scene)(ref activeScene)).buildIndex; lastSceneName = ((Scene)(ref activeScene)).name; mod?.OnSceneWasLoaded(((Scene)(ref activeScene)).buildIndex, ((Scene)(ref activeScene)).name); } } } }