using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using GiftingAssistant.Config; using GiftingAssistant.Data; using GiftingAssistant.Game; using GiftingAssistant.Integration; using GiftingAssistant.Patches; using GiftingAssistant.UI; using HarmonyLib; using I2.Loc; using Microsoft.CodeAnalysis; using SunhavenMods.Shared; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using Wish; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("GiftingAssistant")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+dbad047d5f07d948a50327da218c7718fca0f543")] [assembly: AssemblyProduct("GiftingAssistant")] [assembly: AssemblyTitle("GiftingAssistant")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SunhavenMods.Shared { public static class ConfigFileHelper { public static ConfigFile CreateNamedConfig(string pluginGuid, string configFileName, Action logWarning = null) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, configFileName); string text2 = Path.Combine(Paths.ConfigPath, pluginGuid + ".cfg"); try { if (!File.Exists(text) && File.Exists(text2)) { File.Copy(text2, text); } } catch (Exception ex) { logWarning?.Invoke("[Config] Migration to " + configFileName + " failed: " + ex.Message); } return new ConfigFile(text, true); } public static bool ReplacePluginConfig(BaseUnityPlugin plugin, ConfigFile newConfig, Action logWarning = null) { if ((Object)(object)plugin == (Object)null || newConfig == null) { return false; } try { Type typeFromHandle = typeof(BaseUnityPlugin); PropertyInfo property = typeFromHandle.GetProperty("Config", BindingFlags.Instance | BindingFlags.Public); if (property != null && property.CanWrite) { property.SetValue(plugin, newConfig, null); return true; } FieldInfo field = typeFromHandle.GetField("k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); if (field != null) { field.SetValue(plugin, newConfig); return true; } FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(ConfigFile)) { fieldInfo.SetValue(plugin, newConfig); return true; } } } catch (Exception ex) { logWarning?.Invoke("[Config] ReplacePluginConfig failed: " + ex.Message); } return false; } } public abstract class PersistentRunnerBase : MonoBehaviour { private bool _wasInGame; private float _lastHeartbeat; private string _lastSceneName = ""; private float _lastSceneCheckTime; private const float SceneCheckInterval = 0.5f; protected virtual float HeartbeatInterval => 0f; protected virtual string RunnerName => ((object)this).GetType().Name; protected virtual void OnUpdate() { } protected virtual void OnMenuTransition() { } protected virtual void OnGameTransition() { } protected virtual void Log(string message) { } protected virtual void LogWarning(string message) { } public static T CreateRunner() where T : PersistentRunnerBase { //IL_001e: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Expected O, but got Unknown GameObject val = new GameObject("[" + typeof(T).Name + "]") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val); SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(val); return val.AddComponent(); } protected virtual void Awake() { //IL_0001: 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) Scene activeScene = SceneManager.GetActiveScene(); _lastSceneName = ((Scene)(ref activeScene)).name; _wasInGame = !SceneHelpers.IsMenuScene(_lastSceneName); Log("[" + RunnerName + "] Initialized in scene: " + _lastSceneName); } protected virtual void Update() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (HeartbeatInterval > 0f) { _lastHeartbeat += Time.deltaTime; if (_lastHeartbeat >= HeartbeatInterval) { _lastHeartbeat = 0f; Log("[" + RunnerName + "] Heartbeat - still alive"); } } float unscaledTime = Time.unscaledTime; if (unscaledTime - _lastSceneCheckTime >= 0.5f) { _lastSceneCheckTime = unscaledTime; Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; if (name != _lastSceneName) { _lastSceneName = name; HandleSceneChange(name); } } try { OnUpdate(); } catch (Exception ex) { LogWarning("[" + RunnerName + "] Error in OnUpdate: " + ex.Message); } } private void HandleSceneChange(string sceneName) { bool flag = SceneHelpers.IsMenuScene(sceneName); if (_wasInGame && flag) { Log("[" + RunnerName + "] Menu transition detected"); try { OnMenuTransition(); } catch (Exception ex) { LogWarning("[" + RunnerName + "] Error in OnMenuTransition: " + ex.Message); } } else if (!_wasInGame && !flag) { Log("[" + RunnerName + "] Game transition detected"); try { OnGameTransition(); } catch (Exception ex2) { LogWarning("[" + RunnerName + "] Error in OnGameTransition: " + ex2.Message); } } _wasInGame = !flag; } protected virtual void OnDestroy() { //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(); string text = (((Scene)(ref activeScene)).name ?? string.Empty).ToLowerInvariant(); if (!Application.isPlaying || text.Contains("menu") || text.Contains("title")) { Log("[" + RunnerName + "] OnDestroy during app quit/menu unload (expected)."); } else { LogWarning("[" + RunnerName + "] OnDestroy outside quit/menu (unexpected)."); } } } public static class VersionChecker { public class VersionCheckResult { public bool Success { get; set; } public bool UpdateAvailable { get; set; } public string CurrentVersion { get; set; } public string LatestVersion { get; set; } public string ModName { get; set; } public string NexusUrl { get; set; } public string Changelog { get; set; } public string ErrorMessage { get; set; } } public class ModHealthSnapshot { public string PluginGuid { get; set; } public DateTime LastCheckUtc { get; set; } public int ExceptionCount { get; set; } public string LastError { get; set; } } private class VersionCheckRunner : MonoBehaviour { private ManualLogSource _pluginLog; public void StartCheck(string pluginGuid, string currentVersion, ManualLogSource pluginLog, Action onComplete) { _pluginLog = pluginLog; ((MonoBehaviour)this).StartCoroutine(CheckVersionCoroutine(pluginGuid, currentVersion, onComplete)); } private void LogInfo(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogInfo((object)("[VersionChecker] " + message)); } } private void LogWarningMsg(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogWarning((object)("[VersionChecker] " + message)); } } private void LogErrorMsg(string message) { ManualLogSource pluginLog = _pluginLog; if (pluginLog != null) { pluginLog.LogError((object)("[VersionChecker] " + message)); } } private IEnumerator CheckVersionCoroutine(string pluginGuid, string currentVersion, Action onComplete) { VersionCheckResult result = new VersionCheckResult { CurrentVersion = currentVersion }; UnityWebRequest www = UnityWebRequest.Get("https://azraelgodking.github.io/SunhavenMod/versions.json"); try { www.timeout = 10; yield return www.SendWebRequest(); if ((int)www.result == 2 || (int)www.result == 3) { result.Success = false; result.ErrorMessage = "Network error: " + www.error; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } try { string text = www.downloadHandler.text; Match match = GetModPattern(pluginGuid).Match(text); if (!match.Success) { result.Success = false; result.ErrorMessage = "Mod '" + pluginGuid + "' not found in versions.json"; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } string value = match.Groups[1].Value; result.LatestVersion = ExtractJsonString(value, "version"); result.ModName = ExtractJsonString(value, "name"); result.NexusUrl = ExtractJsonString(value, "nexus"); result.Changelog = ExtractJsonString(value, "changelog"); if (string.IsNullOrEmpty(result.LatestVersion)) { result.Success = false; result.ErrorMessage = "Could not parse version from response"; RecordHealthError(pluginGuid, result.ErrorMessage); LogWarningMsg(result.ErrorMessage); onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); yield break; } result.Success = true; result.UpdateAvailable = CompareVersions(currentVersion, result.LatestVersion) < 0; if (result.UpdateAvailable) { LogInfo("Update available for " + result.ModName + ": " + currentVersion + " -> " + result.LatestVersion); } else { LogInfo(result.ModName + " is up to date (v" + currentVersion + ")"); } } catch (Exception ex) { result.Success = false; result.ErrorMessage = "Parse error: " + ex.Message; RecordHealthError(pluginGuid, result.ErrorMessage); LogErrorMsg(result.ErrorMessage); } } finally { ((IDisposable)www)?.Dispose(); } onComplete?.Invoke(result); Object.Destroy((Object)(object)((Component)this).gameObject); } private string ExtractJsonString(string json, string key) { Match match = ExtractFieldRegex.Match(json); while (match.Success) { if (string.Equals(match.Groups["key"].Value, key, StringComparison.Ordinal)) { return match.Groups["value"].Value; } match = match.NextMatch(); } return null; } } private const string VersionsUrl = "https://azraelgodking.github.io/SunhavenMod/versions.json"; private static readonly Dictionary HealthByPluginGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly object HealthLock = new object(); private static readonly Dictionary ModPatternCache = new Dictionary(StringComparer.Ordinal); private static readonly object ModPatternCacheLock = new object(); private static readonly Regex ExtractFieldRegex = new Regex("\"(?[^\"]+)\"\\s*:\\s*(?:\"(?[^\"]*)\"|null)", RegexOptions.Compiled); public static void CheckForUpdate(string pluginGuid, string currentVersion, ManualLogSource logger = null, Action onComplete = null) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) TouchHealth(pluginGuid); VersionCheckRunner versionCheckRunner = new GameObject("VersionChecker").AddComponent(); Object.DontDestroyOnLoad((Object)(object)((Component)versionCheckRunner).gameObject); SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(((Component)versionCheckRunner).gameObject); versionCheckRunner.StartCheck(pluginGuid, currentVersion, logger, onComplete); } public static ModHealthSnapshot GetHealthSnapshot(string pluginGuid) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return null; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { return null; } return new ModHealthSnapshot { PluginGuid = value.PluginGuid, LastCheckUtc = value.LastCheckUtc, ExceptionCount = value.ExceptionCount, LastError = value.LastError }; } } public static int CompareVersions(string v1, string v2) { if (string.IsNullOrEmpty(v1) || string.IsNullOrEmpty(v2)) { return 0; } v1 = v1.TrimStart('v', 'V'); v2 = v2.TrimStart('v', 'V'); int num = v1.IndexOfAny(new char[2] { '-', '+' }); if (num >= 0) { v1 = v1.Substring(0, num); } int num2 = v2.IndexOfAny(new char[2] { '-', '+' }); if (num2 >= 0) { v2 = v2.Substring(0, num2); } string[] array = v1.Split(new char[1] { '.' }); string[] array2 = v2.Split(new char[1] { '.' }); int num3 = Math.Max(array.Length, array2.Length); for (int i = 0; i < num3; i++) { int result; int num4 = ((i < array.Length && int.TryParse(array[i], out result)) ? result : 0); int result2; int num5 = ((i < array2.Length && int.TryParse(array2[i], out result2)) ? result2 : 0); if (num4 < num5) { return -1; } if (num4 > num5) { return 1; } } return 0; } private static void TouchHealth(string pluginGuid) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { value = new ModHealthSnapshot { PluginGuid = pluginGuid }; HealthByPluginGuid[pluginGuid] = value; } value.LastCheckUtc = DateTime.UtcNow; } } private static void RecordHealthError(string pluginGuid, string errorMessage) { if (string.IsNullOrWhiteSpace(pluginGuid)) { return; } lock (HealthLock) { if (!HealthByPluginGuid.TryGetValue(pluginGuid, out ModHealthSnapshot value)) { value = new ModHealthSnapshot { PluginGuid = pluginGuid }; HealthByPluginGuid[pluginGuid] = value; } value.LastCheckUtc = DateTime.UtcNow; value.ExceptionCount++; value.LastError = errorMessage; } } private static Regex GetModPattern(string pluginGuid) { lock (ModPatternCacheLock) { if (!ModPatternCache.TryGetValue(pluginGuid, out Regex value)) { value = new Regex("\"" + Regex.Escape(pluginGuid) + "\"\\s*:\\s*\\{([^}]+)\\}", RegexOptions.Compiled | RegexOptions.Singleline); ModPatternCache[pluginGuid] = value; } return value; } } } public static class VersionCheckerExtensions { public static void NotifyUpdateAvailable(this VersionChecker.VersionCheckResult result, ManualLogSource logger = null) { if (!result.UpdateAvailable) { return; } string text = result.ModName + " update available: v" + result.LatestVersion; try { Type type = ReflectionHelper.FindWishType("NotificationStack"); if (type != null) { Type type2 = ReflectionHelper.FindType("SingletonBehaviour`1", "Wish"); if (type2 != null) { object obj = type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null); if (obj != null) { MethodInfo method = type.GetMethod("SendNotification", new Type[5] { typeof(string), typeof(int), typeof(int), typeof(bool), typeof(bool) }); if (method != null) { method.Invoke(obj, new object[5] { text, 0, 1, false, true }); return; } } } } } catch (Exception ex) { if (logger != null) { logger.LogWarning((object)("Failed to send native notification: " + ex.Message)); } } if (logger != null) { logger.LogWarning((object)("[UPDATE AVAILABLE] " + text)); } if (!string.IsNullOrEmpty(result.NexusUrl) && logger != null) { logger.LogWarning((object)("Download at: " + result.NexusUrl)); } } } public static class SceneHelpers { private static readonly string[] MenuScenePatterns = new string[3] { "menu", "title", "bootstrap" }; private static readonly string[] ExactMenuScenes = new string[2] { "MainMenu", "Bootstrap" }; public static bool IsMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return true; } string[] exactMenuScenes = ExactMenuScenes; foreach (string text in exactMenuScenes) { if (sceneName == text) { return true; } } string text2 = sceneName.ToLowerInvariant(); exactMenuScenes = MenuScenePatterns; foreach (string value in exactMenuScenes) { if (text2.Contains(value)) { return true; } } return false; } public static bool IsCurrentSceneMenu() { //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(); return IsMenuScene(((Scene)(ref activeScene)).name); } public static bool IsInGame() { return !IsCurrentSceneMenu(); } public static string GetCurrentSceneName() { //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(); return ((Scene)(ref activeScene)).name; } public static bool IsMainMenu() { //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(); return ((Scene)(ref activeScene)).name == "MainMenu"; } } public static class SceneRootSurvivor { private static readonly object Lock = new object(); private static readonly List NoKillSubstrings = new List(); private static Harmony _harmony; public static void TryRegisterPersistentRunnerGameObject(GameObject go) { if (!((Object)(object)go == (Object)null)) { TryAddNoKillListSubstring(((Object)go).name); } } public static void TryAddNoKillListSubstring(string nameSubstring) { if (string.IsNullOrEmpty(nameSubstring)) { return; } lock (Lock) { bool flag = false; for (int i = 0; i < NoKillSubstrings.Count; i++) { if (string.Equals(NoKillSubstrings[i], nameSubstring, StringComparison.OrdinalIgnoreCase)) { flag = true; break; } } if (!flag) { NoKillSubstrings.Add(nameSubstring); } } EnsurePatched(); } private static void EnsurePatched() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_00a3: Expected O, but got Unknown if (_harmony != null) { return; } lock (Lock) { if (_harmony == null) { MethodInfo methodInfo = AccessTools.Method(typeof(Scene), "GetRootGameObjects", Type.EmptyTypes, (Type[])null); if (!(methodInfo == null)) { string text = typeof(SceneRootSurvivor).Assembly.GetName().Name ?? "Unknown"; Harmony val = new Harmony("SunhavenMods.SceneRootSurvivor." + text); val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SceneRootSurvivor), "OnGetRootGameObjectsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _harmony = val; } } } } private static void OnGetRootGameObjectsPostfix(ref GameObject[] __result) { if (__result == null || __result.Length == 0) { return; } List list; lock (Lock) { if (NoKillSubstrings.Count == 0) { return; } list = new List(NoKillSubstrings); } List list2 = new List(__result); for (int i = 0; i < list.Count; i++) { string noKill = list[i]; list2.RemoveAll((GameObject a) => (Object)(object)a != (Object)null && ((Object)a).name.IndexOf(noKill, StringComparison.OrdinalIgnoreCase) >= 0); } __result = list2.ToArray(); } } public static class ReflectionHelper { public static readonly BindingFlags AllBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; public static Type FindType(string typeName, params string[] namespaces) { Type type = AccessTools.TypeByName(typeName); if (type != null) { return type; } for (int i = 0; i < namespaces.Length; i++) { type = AccessTools.TypeByName(namespaces[i] + "." + typeName); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == typeName || t.FullName == typeName); if (type != null) { return type; } } catch (ReflectionTypeLoadException) { } } return null; } public static Type FindWishType(string typeName) { return FindType(typeName, "Wish"); } public static object GetStaticValue(Type type, string memberName) { if (type == null) { return null; } try { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.GetMethod != null && property.GetIndexParameters().Length == 0) { return property.GetValue(null); } } catch (AmbiguousMatchException) { return null; } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { return field.GetValue(null); } return null; } public static object GetSingletonInstance(Type type) { if (type == null) { return null; } string[] array = new string[5] { "Instance", "instance", "_instance", "Singleton", "singleton" }; foreach (string memberName in array) { object staticValue = GetStaticValue(type, memberName); if (staticValue != null) { return staticValue; } } return null; } public static object GetInstanceValue(object instance, string memberName) { if (instance == null) { return null; } Type type = instance.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.GetMethod != null) { return property.GetValue(instance); } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { return field.GetValue(instance); } type = type.BaseType; } return null; } public static bool SetInstanceValue(object instance, string memberName, object value) { if (instance == null) { return false; } Type type = instance.GetType(); while (type != null) { PropertyInfo property = type.GetProperty(memberName, AllBindingFlags); if (property != null && property.SetMethod != null) { property.SetValue(instance, value); return true; } FieldInfo field = type.GetField(memberName, AllBindingFlags); if (field != null) { field.SetValue(instance, value); return true; } type = type.BaseType; } return false; } public static object InvokeMethod(object instance, string methodName, params object[] args) { if (instance == null) { return null; } Type type = instance.GetType(); Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes; MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null); if (methodInfo == null) { methodInfo = type.GetMethod(methodName, AllBindingFlags); } if (methodInfo == null) { return null; } return methodInfo.Invoke(instance, args); } public static object InvokeStaticMethod(Type type, string methodName, params object[] args) { if (type == null) { return null; } Type[] array = args?.Select((object a) => a?.GetType() ?? typeof(object)).ToArray() ?? Type.EmptyTypes; MethodInfo methodInfo = AccessTools.Method(type, methodName, array, (Type[])null); if (methodInfo == null) { methodInfo = type.GetMethod(methodName, AllBindingFlags); } if (methodInfo == null) { return null; } return methodInfo.Invoke(null, args); } public static FieldInfo[] GetAllFields(Type type) { if (type == null) { return Array.Empty(); } FieldInfo[] fields = type.GetFields(AllBindingFlags); IEnumerable second; if (!(type.BaseType != null) || !(type.BaseType != typeof(object))) { second = Enumerable.Empty(); } else { IEnumerable allFields = GetAllFields(type.BaseType); second = allFields; } return fields.Concat(second).Distinct().ToArray(); } public static PropertyInfo[] GetAllProperties(Type type) { if (type == null) { return Array.Empty(); } PropertyInfo[] properties = type.GetProperties(AllBindingFlags); IEnumerable second; if (!(type.BaseType != null) || !(type.BaseType != typeof(object))) { second = Enumerable.Empty(); } else { IEnumerable allProperties = GetAllProperties(type.BaseType); second = allProperties; } return (from p in properties.Concat(second) group p by p.Name into g select g.First()).ToArray(); } public static T TryGetValue(object instance, string memberName, T defaultValue = default(T)) { try { object instanceValue = GetInstanceValue(instance, memberName); if (instanceValue is T result) { return result; } if (instanceValue != null && typeof(T).IsAssignableFrom(instanceValue.GetType())) { return (T)instanceValue; } return defaultValue; } catch { return defaultValue; } } } public static class GameSaveCharacterName { public static string TryGetCurrent(string fallback = null, Action logWarning = null) { try { Type type = AccessTools.TypeByName("Wish.GameSave"); if (type == null) { return fallback; } object obj = AccessTools.Property(type, "CurrentCharacter")?.GetValue(null); if (obj != null) { string text = ReadCharacterName(obj); if (!string.IsNullOrEmpty(text)) { return text; } } object obj2 = AccessTools.Property(type, "Instance")?.GetValue(null); if (obj2 != null) { object obj3 = AccessTools.Property(type, "CurrentSave")?.GetValue(obj2); if (obj3 != null) { object obj4 = AccessTools.Property(obj3.GetType(), "characterData")?.GetValue(obj3); if (obj4 != null) { string text2 = ReadCharacterName(obj4); if (!string.IsNullOrEmpty(text2)) { return text2; } } } } } catch (Exception ex) { logWarning?.Invoke(ex.Message); } return fallback; } private static string ReadCharacterName(object characterOrData) { return AccessTools.Property(characterOrData.GetType(), "characterName")?.GetValue(characterOrData) as string; } } internal static class MinimalJsonParser { internal static void WriteJsonString(StringBuilder sb, string value) { sb.Append('"'); if (value != null) { foreach (char c in value) { switch (c) { case '"': sb.Append("\\\""); break; case '\\': sb.Append("\\\\"); break; case '\n': sb.Append("\\n"); break; case '\r': sb.Append("\\r"); break; case '\t': sb.Append("\\t"); break; case '\b': sb.Append("\\b"); break; case '\f': sb.Append("\\f"); break; default: sb.Append(c); break; } } } sb.Append('"'); } internal static void SkipWhitespace(string json, ref int pos) { while (pos < json.Length && char.IsWhiteSpace(json[pos])) { pos++; } } internal static object ParseValue(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length) { return null; } char c = json[pos]; switch (c) { case '"': return ParseString(json, ref pos); case '{': return ParseObject(json, ref pos); case '[': return ParseArray(json, ref pos); case 't': return ParseLiteral(json, ref pos, "true", true); case 'f': return ParseLiteral(json, ref pos, "false", false); case 'n': return ParseLiteral(json, ref pos, "null", null); default: if (!char.IsDigit(c)) { return null; } goto case '-'; case '-': return ParseNumber(json, ref pos); } } internal static Dictionary ParseObject(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '{') { return null; } pos++; Dictionary dictionary = new Dictionary(); SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == '}') { pos++; return dictionary; } while (pos < json.Length) { SkipWhitespace(json, ref pos); string text = ParseString(json, ref pos); if (text == null) { break; } SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ':') { break; } pos++; SkipWhitespace(json, ref pos); dictionary[text] = ParseValue(json, ref pos); SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ',') { break; } pos++; } SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == '}') { pos++; } return dictionary; } internal static List ParseArray(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '[') { return null; } pos++; List list = new List(); SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == ']') { pos++; return list; } while (pos < json.Length) { SkipWhitespace(json, ref pos); list.Add(ParseValue(json, ref pos)); SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != ',') { break; } pos++; } SkipWhitespace(json, ref pos); if (pos < json.Length && json[pos] == ']') { pos++; } return list; } internal static string ParseString(string json, ref int pos) { SkipWhitespace(json, ref pos); if (pos >= json.Length || json[pos] != '"') { return null; } pos++; StringBuilder stringBuilder = new StringBuilder(); while (pos < json.Length) { char c = json[pos]; if (c == '\\' && pos + 1 < json.Length) { pos++; switch (json[pos]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': { if (pos + 4 < json.Length && ushort.TryParse(json.Substring(pos + 1, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { pos += 4; if (result >= 55296 && result <= 56319 && pos + 5 < json.Length && json[pos] == '\\' && json[pos + 1] == 'u' && ushort.TryParse(json.Substring(pos + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result2) && result2 >= 56320 && result2 <= 57343) { stringBuilder.Append(char.ConvertFromUtf32(char.ConvertToUtf32((char)result, (char)result2))); pos += 6; } else { stringBuilder.Append((char)result); } } else { stringBuilder.Append('u'); } break; } default: stringBuilder.Append(json[pos]); break; } pos++; } else { if (c == '"') { pos++; return stringBuilder.ToString(); } stringBuilder.Append(c); pos++; } } return stringBuilder.ToString(); } internal static object ParseNumber(string json, ref int pos) { int num = pos; bool flag = false; if (pos < json.Length && json[pos] == '-') { pos++; } while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } if (pos < json.Length && json[pos] == '.') { flag = true; pos++; while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } } if (pos < json.Length && (json[pos] == 'e' || json[pos] == 'E')) { flag = true; pos++; if (pos < json.Length && (json[pos] == '+' || json[pos] == '-')) { pos++; } while (pos < json.Length && char.IsDigit(json[pos])) { pos++; } } string s = json.Substring(num, pos - num); if (flag && double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } if (!flag && long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2)) { return result2; } return 0L; } internal static object ParseLiteral(string json, ref int pos, string literal, object result) { if (pos + literal.Length <= json.Length && json.Substring(pos, literal.Length) == literal) { pos += literal.Length; return result; } pos++; return null; } internal static int ToInt(object val) { if (val is long num) { return (int)num; } if (val is double num2) { return (int)num2; } if (val is int) { return (int)val; } return 0; } } public static class ItemSearch { private static readonly ManualLogSource _log = Logger.CreateLogSource("ItemSearch"); private static object _dbInstance; private static FieldInfo _dictField; public static string FormatDisplay(string name, int itemId) { if (string.IsNullOrEmpty(name)) { return $"#{itemId}"; } return $"{name} (#{itemId})"; } public static List> SearchItems(string query, int maxResults = 50) { List> list = new List>(); if (string.IsNullOrEmpty(query) || query.Trim().Length < 2) { return list; } try { Dictionary itemDictionary = GetItemDictionary(); if (itemDictionary == null) { return list; } string text = query.Trim().ToLowerInvariant(); int result; bool flag = int.TryParse(query.Trim(), out result); List> list2 = new List>(); List> list3 = new List>(); List> list4 = new List>(); foreach (KeyValuePair item2 in itemDictionary) { int key = item2.Key; string text2 = item2.Value?.name; if (!string.IsNullOrEmpty(text2)) { KeyValuePair item = new KeyValuePair(key, text2); string text3 = text2.ToLowerInvariant(); if (flag && key == result) { list2.Add(item); } else if (text3 == text) { list2.Add(item); } else if (text3.StartsWith(text)) { list3.Add(item); } else if (text3.Contains(text)) { list4.Add(item); } else if (flag && key.ToString().Contains(query.Trim())) { list4.Add(item); } } } list3.Sort((KeyValuePair a, KeyValuePair b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); list4.Sort((KeyValuePair a, KeyValuePair b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); list.AddRange(list2); list.AddRange(list3); list.AddRange(list4); if (list.Count > maxResults) { list.RemoveRange(maxResults, list.Count - maxResults); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] SearchItems error: " + ex.Message)); } } return list; } public static string GetItemName(int itemId) { try { Dictionary itemDictionary = GetItemDictionary(); if (itemDictionary == null || !itemDictionary.ContainsKey(itemId)) { return null; } return itemDictionary[itemId]?.name; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[ItemSearch] GetItemName({itemId}): {ex.Message}"); } return null; } } public static ItemSellInfo GetItemSellInfo(int itemId) { try { Dictionary itemDictionary = GetItemDictionary(); if (itemDictionary != null && itemDictionary.TryGetValue(itemId, out var value)) { return value; } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[ItemSearch] GetItemSellInfo({itemId}): {ex.Message}"); } } return null; } public static List> GetAllItems() { List> list = new List>(); try { Dictionary itemDictionary = GetItemDictionary(); if (itemDictionary == null) { return list; } foreach (KeyValuePair item in itemDictionary) { string value = item.Value?.name; if (!string.IsNullOrEmpty(value)) { list.Add(new KeyValuePair(item.Key, value)); } } list.Sort((KeyValuePair a, KeyValuePair b) => string.Compare(a.Value, b.Value, StringComparison.OrdinalIgnoreCase)); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetAllItems: " + ex.Message)); } } return list; } private static Dictionary GetItemDictionary() { try { if (_dbInstance != null) { object dbInstance = _dbInstance; Object val = (Object)((dbInstance is Object) ? dbInstance : null); if (val == null || !(val == (Object)null)) { goto IL_005b; } } _dbInstance = null; _dictField = null; _dbInstance = GetSingletonInstance("Wish.ItemInfoDatabase"); if (_dbInstance != null) { _dictField = _dbInstance.GetType().GetField("allItemSellInfos", BindingFlags.Instance | BindingFlags.Public); } goto IL_005b; IL_005b: if (_dbInstance == null || _dictField == null) { return null; } return _dictField.GetValue(_dbInstance) as Dictionary; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetItemDictionary: " + ex.Message)); } return null; } } private static object GetSingletonInstance(string typeName) { try { Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type == null) { return null; } Type type2 = AccessTools.TypeByName(typeName); if (type2 == null) { return null; } return type.MakeGenericType(type2).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)?.GetValue(null); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[ItemSearch] GetSingletonInstance(" + typeName + "): " + ex.Message)); } return null; } } } public static class IconCache { private struct CachedIcon { public Texture2D Texture; public bool OwnsTexture; } private static readonly Dictionary _iconCache = new Dictionary(); private const int MaxCacheSize = 200; private static readonly HashSet _loadingItems = new HashSet(); private static readonly HashSet _failedItems = new HashSet(); private static readonly Dictionary _currencyToItemId = new Dictionary(); private static Texture2D _fallbackTexture; private static ManualLogSource _log; private static Type _databaseType; private static Type _itemDataType; private static MethodInfo _getDataMethod; private static bool _reflectionInitialized; private static bool _initialized; private static bool _iconsLoaded; private static int[] _pendingPreloadItemIds; public static void Initialize(ManualLogSource log, int[] preloadItemIds = null) { _log = log; if (_initialized) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogDebug((object)"[IconCache] Already initialized"); } return; } _initialized = true; ManualLogSource log3 = _log; if (log3 != null) { log3.LogInfo((object)"[IconCache] Initializing icon cache..."); } _fallbackTexture = CreateFallbackTexture(); ManualLogSource log4 = _log; if (log4 != null) { log4.LogInfo((object)"[IconCache] Created fallback texture"); } if (preloadItemIds != null && preloadItemIds.Length != 0) { _pendingPreloadItemIds = (int[])preloadItemIds.Clone(); ManualLogSource log5 = _log; if (log5 != null) { log5.LogInfo((object)$"[IconCache] Preload: {_pendingPreloadItemIds.Length} item ID(s) will queue with LoadAllIcons"); } } } public static void RegisterCurrency(string currencyId, int itemId) { _currencyToItemId[currencyId] = itemId; } public static void LoadAllIcons() { if (_iconsLoaded) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)"[IconCache] Icons already loaded, skipping"); } return; } if (!InitializeReflection()) { ManualLogSource log2 = _log; if (log2 != null) { log2.LogError((object)"[IconCache] Failed to initialize reflection, cannot load icons"); } _iconsLoaded = true; return; } foreach (KeyValuePair item in _currencyToItemId) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogDebug((object)$"[IconCache] Queuing load for: {item.Key} (ItemID: {item.Value})"); } LoadIcon(item.Value); } if (_pendingPreloadItemIds != null) { int[] pendingPreloadItemIds = _pendingPreloadItemIds; foreach (int num in pendingPreloadItemIds) { if (num > 0) { ManualLogSource log4 = _log; if (log4 != null) { log4.LogDebug((object)$"[IconCache] Queuing preload item ID: {num}"); } LoadIcon(num); } } } _iconsLoaded = true; ManualLogSource log5 = _log; if (log5 != null) { log5.LogInfo((object)$"[IconCache] Queued {_currencyToItemId.Count} currency icon(s); preload queue processed."); } } public static Texture2D GetIconForCurrency(string currencyId) { if (_currencyToItemId.TryGetValue(currencyId, out var value)) { return GetIcon(value); } return GetFallbackTexture(); } public static Texture2D GetIcon(int itemId) { if (itemId <= 0) { return GetFallbackTexture(); } if (_iconCache.TryGetValue(itemId, out var value)) { return value.Texture; } if (!_loadingItems.Contains(itemId) && !_failedItems.Contains(itemId)) { LoadIcon(itemId); } return GetFallbackTexture(); } private static Texture2D GetFallbackTexture() { if ((Object)(object)_fallbackTexture == (Object)null) { _fallbackTexture = CreateFallbackTexture(); } return _fallbackTexture; } public static bool IsIconLoaded(int itemId) { return _iconCache.ContainsKey(itemId); } public static bool IsIconLoaded(string currencyId) { if (_currencyToItemId.TryGetValue(currencyId, out var value)) { return IsIconLoaded(value); } return false; } public static int GetItemIdForCurrency(string currencyId) { if (!_currencyToItemId.TryGetValue(currencyId, out var value)) { return -1; } return value; } private static bool InitializeReflection() { if (_reflectionInitialized) { if (_databaseType != null && _itemDataType != null) { return _getDataMethod != null; } return false; } _reflectionInitialized = true; try { string[] array = new string[4] { "Database", "Wish.Database", "PSS.Database", "SunHaven.Database" }; for (int i = 0; i < array.Length; i++) { _databaseType = AccessTools.TypeByName(array[i]); if (_databaseType != null) { ManualLogSource log = _log; if (log != null) { log.LogInfo((object)("[IconCache] Found Database type: " + _databaseType.FullName)); } break; } } MethodInfo[] methods; if (_databaseType == null) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type[] types = assembly.GetTypes(); foreach (Type type in types) { if (!(type.Name == "Database") || type.IsNested) { continue; } methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { if (methodInfo.Name == "GetData" && methodInfo.IsGenericMethod) { _databaseType = type; ManualLogSource log2 = _log; if (log2 != null) { log2.LogInfo((object)("[IconCache] Found Database type: " + type.FullName)); } break; } } if (_databaseType != null) { break; } } if (_databaseType != null) { break; } } catch (Exception ex) { ManualLogSource log3 = _log; if (log3 != null) { log3.LogDebug((object)("[IconCache] Skipping assembly " + assembly.GetName().Name + ": " + ex.Message)); } } } } if (_databaseType == null) { ManualLogSource log4 = _log; if (log4 != null) { log4.LogError((object)"[IconCache] Could not find Database type"); } return false; } _itemDataType = AccessTools.TypeByName("Wish.ItemData"); if (_itemDataType == null) { ManualLogSource log5 = _log; if (log5 != null) { log5.LogError((object)"[IconCache] Could not find Wish.ItemData type"); } return false; } methods = _databaseType.GetMethods(BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo2 in methods) { if (!(methodInfo2.Name == "GetData") || !methodInfo2.IsGenericMethod) { continue; } ParameterInfo[] parameters = methodInfo2.GetParameters(); if (methodInfo2.GetGenericArguments().Length == 1 && parameters.Length == 3 && parameters[0].ParameterType == typeof(int)) { _getDataMethod = methodInfo2.MakeGenericMethod(_itemDataType); ManualLogSource log6 = _log; if (log6 != null) { log6.LogInfo((object)"[IconCache] Found Database.GetData method"); } break; } } if (_getDataMethod == null) { ManualLogSource log7 = _log; if (log7 != null) { log7.LogError((object)"[IconCache] Could not find Database.GetData method"); } return false; } return true; } catch (Exception ex2) { ManualLogSource log8 = _log; if (log8 != null) { log8.LogError((object)("[IconCache] Error initializing reflection: " + ex2.Message)); } return false; } } private static void LoadIcon(int itemId) { if (itemId <= 0 || _loadingItems.Contains(itemId) || _iconCache.ContainsKey(itemId)) { return; } _loadingItems.Add(itemId); try { if (!InitializeReflection() || _getDataMethod == null) { _failedItems.Add(itemId); _loadingItems.Remove(itemId); return; } Type delegateType = typeof(Action<>).MakeGenericType(_itemDataType); ParameterExpression parameterExpression = Expression.Parameter(_itemDataType, "itemData"); ConstantExpression arg = Expression.Constant(itemId); MethodCallExpression body = Expression.Call(typeof(IconCache).GetMethod("OnIconLoadedInternal", BindingFlags.Static | BindingFlags.NonPublic), arg, Expression.Convert(parameterExpression, typeof(object))); Delegate obj = Expression.Lambda(delegateType, body, parameterExpression).Compile(); Action action = Expression.Lambda(Expression.Call(typeof(IconCache).GetMethod("OnIconLoadFailed", BindingFlags.Static | BindingFlags.NonPublic), arg), Array.Empty()).Compile(); _getDataMethod.Invoke(null, new object[3] { itemId, obj, action }); } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[IconCache] Error loading icon {itemId}: {ex.Message}"); } _failedItems.Add(itemId); _loadingItems.Remove(itemId); } } private static void OnIconLoadedInternal(int itemId, object itemData) { _loadingItems.Remove(itemId); if (itemData == null) { _failedItems.Add(itemId); return; } try { Type type = itemData.GetType(); object obj = null; BindingFlags[] array = new BindingFlags[4] { BindingFlags.Instance | BindingFlags.Public, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy, BindingFlags.Instance | BindingFlags.NonPublic, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy }; BindingFlags[] array2 = array; foreach (BindingFlags bindingAttr in array2) { PropertyInfo property = type.GetProperty("icon", bindingAttr); if (property != null) { obj = property.GetValue(itemData); break; } } if (obj == null) { array2 = array; foreach (BindingFlags bindingAttr2 in array2) { FieldInfo field = type.GetField("icon", bindingAttr2); if (field != null) { obj = field.GetValue(itemData); break; } } } if (obj == null) { Type type2 = type; while (type2 != null && obj == null) { PropertyInfo[] properties = type2.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { if (propertyInfo.Name.Equals("icon", StringComparison.OrdinalIgnoreCase)) { obj = propertyInfo.GetValue(itemData); break; } } if (obj == null) { FieldInfo[] fields = type2.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.Name.Equals("icon", StringComparison.OrdinalIgnoreCase)) { obj = fieldInfo.GetValue(itemData); break; } } } type2 = type2.BaseType; } } Sprite val = (Sprite)((obj is Sprite) ? obj : null); if (val != null) { CacheSprite(itemId, val); } else { _failedItems.Add(itemId); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[IconCache] Error processing icon {itemId}: {ex.Message}"); } _failedItems.Add(itemId); } } private static void OnIconLoadFailed(int itemId) { _loadingItems.Remove(itemId); _failedItems.Add(itemId); } private static void CacheSprite(int itemId, Sprite sprite) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)sprite == (Object)null || (Object)(object)sprite.texture == (Object)null) { _failedItems.Add(itemId); return; } try { Rect rect = sprite.rect; Texture2D val; bool ownsTexture; if (((Rect)(ref rect)).width == (float)((Texture)sprite.texture).width) { rect = sprite.rect; if (((Rect)(ref rect)).height == (float)((Texture)sprite.texture).height) { val = sprite.texture; ownsTexture = false; goto IL_0077; } } val = ExtractSpriteTexture(sprite); ownsTexture = (Object)(object)val != (Object)null; goto IL_0077; IL_0077: if ((Object)(object)val != (Object)null) { _iconCache[itemId] = new CachedIcon { Texture = val, OwnsTexture = ownsTexture }; if (_iconCache.Count <= 200) { return; } int num = -1; int num2 = -1; foreach (int key in _iconCache.Keys) { if (num2 < 0) { num2 = key; } if (!_loadingItems.Contains(key) && !_failedItems.Contains(key)) { num = key; break; } } if (num < 0) { num = num2; } if (num >= 0 && _iconCache.TryGetValue(num, out var value)) { if (value.OwnsTexture && (Object)(object)value.Texture != (Object)null) { Object.Destroy((Object)(object)value.Texture); } _iconCache.Remove(num); } } else { _failedItems.Add(itemId); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)$"[IconCache] Error caching sprite {itemId}: {ex.Message}"); } _failedItems.Add(itemId); } } private static Texture2D ExtractSpriteTexture(Sprite sprite) { //IL_0001: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown try { Rect rect = sprite.rect; int num = (int)((Rect)(ref rect)).width; int num2 = (int)((Rect)(ref rect)).height; if (!((Texture)sprite.texture).isReadable) { return CopyTextureViaRenderTexture(sprite); } Texture2D val = new Texture2D(num, num2, (TextureFormat)4, false); Color[] pixels = sprite.texture.GetPixels((int)((Rect)(ref rect)).x, (int)((Rect)(ref rect)).y, num, num2); val.SetPixels(pixels); val.Apply(); return val; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[IconCache] Error extracting sprite texture: " + ex.Message)); } return null; } } private static Texture2D CopyTextureViaRenderTexture(Sprite sprite) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Expected O, but got Unknown RenderTexture val = null; RenderTexture active = RenderTexture.active; try { Rect rect = sprite.rect; int num = (int)((Rect)(ref rect)).width; int num2 = (int)((Rect)(ref rect)).height; val = RenderTexture.GetTemporary(((Texture)sprite.texture).width, ((Texture)sprite.texture).height, 0, (RenderTextureFormat)0); Graphics.Blit((Texture)(object)sprite.texture, val); RenderTexture.active = val; Texture2D val2 = new Texture2D(num, num2, (TextureFormat)4, false); val2.ReadPixels(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, (float)num, (float)num2), 0, 0); val2.Apply(); return val2; } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[IconCache] Error copying texture via RenderTexture: " + ex.Message)); } return null; } finally { RenderTexture.active = active; if ((Object)(object)val != (Object)null) { RenderTexture.ReleaseTemporary(val); } } } private static Texture2D CreateFallbackTexture() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0066: 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) int num = 32; Texture2D val = new Texture2D(num, num); Color val2 = default(Color); ((Color)(ref val2))..ctor(0.3f, 0.3f, 0.4f, 0.8f); Color val3 = default(Color); ((Color)(ref val3))..ctor(0.5f, 0.5f, 0.6f, 1f); for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { if (j == 0 || j == num - 1 || i == 0 || i == num - 1) { val.SetPixel(j, i, val3); } else { val.SetPixel(j, i, val2); } } } val.Apply(); return val; } public static void Clear() { foreach (KeyValuePair item in _iconCache) { if (item.Value.OwnsTexture && (Object)(object)item.Value.Texture != (Object)null) { Object.Destroy((Object)(object)item.Value.Texture); } } _iconCache.Clear(); _loadingItems.Clear(); _failedItems.Clear(); _initialized = false; _iconsLoaded = false; _pendingPreloadItemIds = null; } public static (int loaded, int loading, int failed) GetStats() { return (loaded: _iconCache.Count, loading: _loadingItems.Count, failed: _failedItems.Count); } public static void LogStatus() { (int, int, int) stats = GetStats(); ManualLogSource log = _log; if (log != null) { log.LogInfo((object)$"[IconCache] Loaded: {stats.Item1}, Loading: {stats.Item2}, Failed: {stats.Item3}"); } } } public static class OvernightHookUtility { public static bool TryHookOvernightEvent(ref bool overnightHooked, ref UnityAction overnightCallback, UnityAction callback, Func singletonResolver, Action logInfo = null, Action logWarning = null) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown if (overnightHooked) { return true; } try { Type type = AccessTools.TypeByName("Wish.DayCycle"); if (type != null) { FieldInfo fieldInfo = AccessTools.Field(type, "OnDayStart"); if (fieldInfo != null) { object? value = fieldInfo.GetValue(null); UnityAction val = (UnityAction)((value is UnityAction) ? value : null); overnightCallback = callback; if (val != null) { val = (UnityAction)Delegate.Remove((Delegate?)(object)val, (Delegate?)(object)overnightCallback); val = (UnityAction)Delegate.Combine((Delegate?)(object)val, (Delegate?)(object)overnightCallback); fieldInfo.SetValue(null, val); } else { fieldInfo.SetValue(null, overnightCallback); } overnightHooked = true; logInfo?.Invoke("Hooked into DayCycle.OnDayStart"); return true; } } Type type2 = AccessTools.TypeByName("Wish.UIHandler"); if (type2 == null) { return false; } object obj = singletonResolver?.Invoke(type2); if (obj == null) { return false; } FieldInfo fieldInfo2 = AccessTools.Field(type2, "OnCompleteOvernight"); if (fieldInfo2 == null) { return false; } object? value2 = fieldInfo2.GetValue(obj); UnityAction val2 = (UnityAction)((value2 is UnityAction) ? value2 : null); overnightCallback = callback; if (val2 != null) { val2 = (UnityAction)Delegate.Remove((Delegate?)(object)val2, (Delegate?)(object)overnightCallback); val2 = (UnityAction)Delegate.Combine((Delegate?)(object)val2, (Delegate?)(object)overnightCallback); fieldInfo2.SetValue(obj, val2); } else { fieldInfo2.SetValue(obj, overnightCallback); } overnightHooked = true; logInfo?.Invoke("Hooked into UIHandler.OnCompleteOvernight"); return true; } catch (Exception ex) { logWarning?.Invoke("Failed to hook overnight event: " + ex.Message); return false; } } } public static class TextInputFocusGuard { private const float DefaultPollIntervalSeconds = 0.25f; private static float _nextPollTime = -1f; private static bool _cachedDefer; private static bool _tmpTypeLookupDone; private static Type _tmpInputFieldType; private static bool _qcLookupDone; private static Type _qcType; private static PropertyInfo _qcInstanceProp; private static PropertyInfo _qcIsActiveProp; private static FieldInfo _qcIsActiveField; public static bool ShouldDeferModHotkeys(ManualLogSource debugLog = null, float pollIntervalSeconds = 0.25f) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextPollTime) { return _cachedDefer; } _nextPollTime = realtimeSinceStartup + Mathf.Max(0.05f, pollIntervalSeconds); bool flag = false; try { if (GUIUtility.keyboardControl != 0) { flag = true; } if (!flag) { EventSystem current = EventSystem.current; GameObject val = ((current != null) ? current.currentSelectedGameObject : null); if ((Object)(object)val != (Object)null) { if ((Object)(object)val.GetComponent() != (Object)null) { flag = true; } else if (TryGetTmpInputField(val)) { flag = true; } } } if (!flag && IsQuantumConsoleActive(debugLog)) { flag = true; } } catch (Exception ex) { if (debugLog != null) { debugLog.LogDebug((object)("[TextInputFocusGuard] " + ex.Message)); } } _cachedDefer = flag; return flag; } private static bool TryGetTmpInputField(GameObject go) { if (!_tmpTypeLookupDone) { _tmpTypeLookupDone = true; _tmpInputFieldType = AccessTools.TypeByName("TMPro.TMP_InputField"); } if (_tmpInputFieldType == null) { return false; } return (Object)(object)go.GetComponent(_tmpInputFieldType) != (Object)null; } private static bool IsQuantumConsoleActive(ManualLogSource debugLog) { try { if (!_qcLookupDone) { _qcLookupDone = true; _qcType = AccessTools.TypeByName("QFSW.QC.QuantumConsole"); if (_qcType != null) { _qcInstanceProp = AccessTools.Property(_qcType, "Instance"); _qcIsActiveProp = AccessTools.Property(_qcType, "IsActive"); _qcIsActiveField = AccessTools.Field(_qcType, "isActive") ?? AccessTools.Field(_qcType, "_isActive"); } } if (_qcType == null) { return false; } object obj = _qcInstanceProp?.GetValue(null); if (obj == null) { return false; } if (_qcIsActiveProp != null && _qcIsActiveProp.PropertyType == typeof(bool)) { return (bool)_qcIsActiveProp.GetValue(obj); } if (_qcIsActiveField != null && _qcIsActiveField.FieldType == typeof(bool)) { return (bool)_qcIsActiveField.GetValue(obj); } } catch (Exception ex) { if (debugLog != null) { debugLog.LogDebug((object)("[TextInputFocusGuard] Quantum Console focus check failed: " + ex.Message)); } } return false; } } public static class ModLocalization { private static readonly string[] SupportedLanguageCodes = new string[16] { "en", "da", "de", "es", "fr", "it", "ja", "ko", "nl", "pt", "pt-BR", "ru", "sv", "zh-CN", "zh-TW", "uk" }; private static readonly Dictionary LanguageAlias = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "pt-br", "pt-BR" }, { "pt_br", "pt-BR" }, { "zh-cn", "zh-CN" }, { "zh_cn", "zh-CN" }, { "zh-tw", "zh-TW" }, { "zh_tw", "zh-TW" } }; private static string _modId; private static Dictionary> _tables; private static ManualLogSource _log; private static bool _initialized; private static bool _forceEnglish; public static string CurrentLanguage { get; private set; } = "en"; public static bool ForceEnglish => _forceEnglish; public static bool IsReady { get { if (_initialized && _tables != null) { return _tables.Count > 0; } return false; } } public static event Action LanguageChanged { add { LanguageChangeWatcher.LanguageChanged += value; } remove { LanguageChangeWatcher.LanguageChanged -= value; } } public static void Init(string modId, Dictionary> tables, Harmony harmony, ManualLogSource log) { _modId = modId ?? string.Empty; _tables = tables ?? new Dictionary>(); _log = log; _initialized = true; RefreshCurrentLanguage(); LanguageChangeWatcher.EnsurePatched(harmony); } public static void SetForceEnglish(bool forceEnglish) { _forceEnglish = forceEnglish; ApplyEffectiveLanguage(); } internal static void OnGameLanguageChanged(string languageCode) { if (_tables == null || _forceEnglish) { return; } string text = NormalizeLanguageCode(languageCode); if (!string.Equals(CurrentLanguage, text, StringComparison.OrdinalIgnoreCase)) { CurrentLanguage = text; ManualLogSource log = _log; if (log != null) { log.LogDebug((object)("[" + _modId + "] Language changed to " + CurrentLanguage)); } } } public static void RefreshCurrentLanguage() { ApplyEffectiveLanguage(); } private static void ApplyEffectiveLanguage() { if (_forceEnglish) { CurrentLanguage = "en"; } else { RefreshCurrentLanguageFromGame(); } } private static void RefreshCurrentLanguageFromGame() { try { string currentLanguageCode = LocalizationManager.CurrentLanguageCode; if (!string.IsNullOrWhiteSpace(currentLanguageCode)) { CurrentLanguage = NormalizeLanguageCode(currentLanguageCode); } } catch (Exception ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("[" + _modId + "] Failed to read LocalizationManager.CurrentLanguageCode: " + ex.Message)); } CurrentLanguage = "en"; } } public static string T(string key) { if (!TryT(key, out string value)) { return key; } return value; } public static string T(string key, params object[] args) { string text = T(key); if (args == null || args.Length == 0) { return text; } try { return string.Format(CultureInfo.InvariantCulture, text, args); } catch (FormatException ex) { ManualLogSource log = _log; if (log != null) { log.LogWarning((object)("[" + _modId + "] Format failed for key '" + key + "': " + ex.Message)); } return text; } } public static bool TryT(string key, out string value) { value = null; if (string.IsNullOrEmpty(key)) { return false; } if (_tables == null || !_tables.TryGetValue(key, out Dictionary value2) || value2 == null) { return false; } if (TryGetForLanguage(value2, CurrentLanguage, out value)) { return true; } if (!string.Equals(CurrentLanguage, "en", StringComparison.OrdinalIgnoreCase) && TryGetForLanguage(value2, "en", out value)) { return true; } return false; } private static bool TryGetForLanguage(Dictionary translations, string languageCode, out string value) { value = null; if (translations == null) { return false; } string text = NormalizeLanguageCode(languageCode); if (translations.TryGetValue(text, out value) && !string.IsNullOrEmpty(value)) { return true; } foreach (KeyValuePair translation in translations) { if (string.Equals(translation.Key, text, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(translation.Value)) { value = translation.Value; return true; } } return false; } public static string NormalizeLanguageCode(string code) { if (string.IsNullOrWhiteSpace(code)) { return "en"; } string text = code.Trim(); if (LanguageAlias.TryGetValue(text, out string value)) { return value; } string[] supportedLanguageCodes = SupportedLanguageCodes; foreach (string text2 in supportedLanguageCodes) { if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase)) { return text2; } } return "en"; } public static Dictionary> ParseStringsJson(string json) { Dictionary> dictionary = new Dictionary>(StringComparer.Ordinal); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int pos = 0; Dictionary dictionary2 = MinimalJsonParser.ParseObject(json, ref pos); if (dictionary2 == null) { return dictionary; } foreach (KeyValuePair item in dictionary2) { if (!(item.Value is Dictionary dictionary3)) { continue; } Dictionary dictionary4 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair item2 in dictionary3) { if (item2.Value is string value) { dictionary4[NormalizeLanguageCode(item2.Key)] = value; } } if (dictionary4.Count > 0) { dictionary[item.Key] = dictionary4; } } return dictionary; } public static Dictionary> LoadEmbeddedStrings(Assembly assembly, string resourceName, ManualLogSource log = null) { try { using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream == null) { if (log != null) { log.LogError((object)("Localization resource not found: " + resourceName)); } return new Dictionary>(); } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8); return ParseStringsJson(streamReader.ReadToEnd()); } catch (Exception ex) { if (log != null) { log.LogError((object)("Failed to load localization resource '" + resourceName + "': " + ex.Message)); } return new Dictionary>(); } } public static void Shutdown() { _log = null; } } public static class LanguageChangeWatcher { private static bool _patched; public static event Action LanguageChanged; public static void EnsurePatched(Harmony harmony) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown if (_patched || harmony == null) { return; } try { MethodInfo methodInfo = AccessTools.Method(typeof(LanguageChangeWatcher), "OnSetLanguageAndCode", (Type[])null, (Type[])null); harmony.Patch((MethodBase)AccessTools.Method(typeof(LocalizationManager), "SetLanguageAndCode", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; } catch (Exception innerException) { throw new InvalidOperationException("Failed to patch LocalizationManager.SetLanguageAndCode", innerException); } } private static void OnSetLanguageAndCode(string LanguageName, string LanguageCode) { string text = ModLocalization.NormalizeLanguageCode(string.IsNullOrWhiteSpace(LanguageCode) ? LocalizationManager.CurrentLanguageCode : LanguageCode); ModLocalization.OnGameLanguageChanged(text); LanguageChangeWatcher.LanguageChanged?.Invoke(text); } internal static void RaiseLanguageChanged(string languageCode) { string obj = ModLocalization.NormalizeLanguageCode(languageCode); LanguageChangeWatcher.LanguageChanged?.Invoke(obj); } } public static class LocalizationBootstrap { public static ConfigEntry BindForceEnglish(ConfigFile config) { ConfigEntry entry = config.Bind("Localization", "ForceEnglish", false, "Keep this mod's UI in English and ignore Sun Haven's in-game language setting."); ApplyForceEnglish(entry.Value); entry.SettingChanged += delegate { ApplyForceEnglish(entry.Value); }; return entry; } private static void ApplyForceEnglish(bool forceEnglish) { ModLocalization.SetForceEnglish(forceEnglish); LanguageChangeWatcher.RaiseLanguageChanged(ModLocalization.CurrentLanguage); } public static void Init(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null) { if ((object)assembly == null) { assembly = Assembly.GetCallingAssembly(); } Dictionary> tables = ModLocalization.LoadEmbeddedStrings(assembly, pluginGuid + ".Localization.strings.json", log); ModLocalization.Init(pluginGuid, tables, harmony, log); } public static void EnsureInitialized(string pluginGuid, Harmony harmony, ManualLogSource log, Assembly assembly = null) { if (!ModLocalization.IsReady) { Init(pluginGuid, harmony, log, assembly); } } } } namespace GiftingAssistant { [BepInPlugin("com.azraelgodking.giftingassistant", "Gifting Assistant", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static Action <0>__OnLanguageChanged; public static UnityAction <1>__OnNewDay; } private static GiftRosterManager _staticManager; private static GiftRosterSaveSystem _staticSaveSystem; private static GiftingWindow _staticWindow; private static GameObject _persistentRunner; private static PersistentRunner _persistentRunnerComponent; private static BirthdayIntegration _birthdayIntegration; private static TodoIntegration _todoIntegration; private static KeyCode _staticToggleKey = (KeyCode)103; private static bool _staticRequireCtrl = true; private static bool _staticShowPossession = true; private static float _staticUIScale = 1f; private static bool _staticEnabled = true; private static bool _staticAlmanacIntegrationEnabled; private static bool _overnightHooked; private static UnityAction _overnightCallback; private static string _lastTodoRefreshDateKey = ""; private static bool _applicationQuitting; private static bool _wasInMenuScene = true; private static float _lastAutoSaveTime; private static float _staticAutoSaveInterval = 60f; private static bool _staticAutoSave = true; private GiftingAssistantConfig _config; private Harmony _harmony; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static ConfigFile ConfigFile { get; private set; } public static KeyCode StaticToggleKey => _staticToggleKey; public static bool StaticRequireCtrl => _staticRequireCtrl; public static bool StaticEnabled => _staticEnabled; public static bool StaticAlmanacIntegrationEnabled => _staticAlmanacIntegrationEnabled; public static string GetOpenShortcutDisplay() { string text = ((object)Unsafe.As(ref _staticToggleKey)/*cast due to .constrained prefix*/).ToString(); if (!_staticRequireCtrl) { return text; } return "Ctrl+" + text; } private void Awake() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigFile = CreateNamedConfig(); ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, ConfigFile, (Action)Log.LogWarning); _config = new GiftingAssistantConfig(ConfigFile); _staticEnabled = _config.Enabled.Value; if (!_config.Enabled.Value) { Log.LogInfo((object)"Gifting Assistant disabled in config."); return; } Log.LogInfo((object)"Loading Gifting Assistant v1.0.0"); BindConfigEvents(); IconCache.Initialize(Log); LocalizationBootstrap.BindForceEnglish(ConfigFile); _harmony = new Harmony("com.azraelgodking.giftingassistant"); LocalizationBootstrap.Init("com.azraelgodking.giftingassistant", _harmony, Log, Assembly.GetExecutingAssembly()); ModLocalization.LanguageChanged += OnLanguageChanged; _birthdayIntegration = new BirthdayIntegration(); _todoIntegration = new TodoIntegration { IsEnabled = (_config.ReminderMode.Value == GiftReminderMode.PushToTodo) }; _staticAlmanacIntegrationEnabled = _config.UseAlmanacIntegration.Value; CreatePersistentRunner(); InitializeManagers(); ApplyPatches(); SceneManager.sceneLoaded += OnSceneLoaded; if (_config.CheckForUpdates.Value) { VersionChecker.CheckForUpdate("com.azraelgodking.giftingassistant", "1.0.0", Log, delegate(VersionChecker.VersionCheckResult result) { result.NotifyUpdateAvailable(Log); }); } Log.LogInfo((object)"Gifting Assistant loaded successfully!"); } private void BindConfigEvents() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) _staticToggleKey = _config.ToggleKey.Value; _config.ToggleKey.SettingChanged += delegate { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) _staticToggleKey = _config.ToggleKey.Value; }; _staticRequireCtrl = _config.RequireCtrl.Value; _config.RequireCtrl.SettingChanged += delegate { _staticRequireCtrl = _config.RequireCtrl.Value; }; _staticShowPossession = _config.ShowInventoryPossession.Value; _config.ShowInventoryPossession.SettingChanged += delegate { _staticShowPossession = _config.ShowInventoryPossession.Value; _staticWindow?.SetShowPossession(_staticShowPossession); }; _staticUIScale = Mathf.Clamp(_config.UIScale.Value, 0.5f, 2.5f); _config.UIScale.SettingChanged += delegate { _staticUIScale = Mathf.Clamp(_config.UIScale.Value, 0.5f, 2.5f); _staticWindow?.SetScale(_staticUIScale); }; _config.ReminderMode.SettingChanged += delegate { if (_todoIntegration != null) { _todoIntegration.IsEnabled = _config.ReminderMode.Value == GiftReminderMode.PushToTodo; } }; _config.UseAlmanacIntegration.SettingChanged += delegate { _staticAlmanacIntegrationEnabled = _config.UseAlmanacIntegration.Value; }; _staticAutoSaveInterval = Mathf.Max(5f, _config.AutoSaveInterval.Value); _config.AutoSaveInterval.SettingChanged += delegate { _staticAutoSaveInterval = Mathf.Max(5f, _config.AutoSaveInterval.Value); }; _staticAutoSave = _config.AutoSave.Value; _config.AutoSave.SettingChanged += delegate { _staticAutoSave = _config.AutoSave.Value; }; } private static ConfigFile CreateNamedConfig() { return ConfigFileHelper.CreateNamedConfig("com.azraelgodking.giftingassistant", "GiftingAssistant.cfg", delegate(string message) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)message); } }); } private void CreatePersistentRunner() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown if (!((Object)(object)_persistentRunner != (Object)null) || !((Object)(object)_persistentRunnerComponent != (Object)null)) { _persistentRunner = new GameObject("GiftingAssistant_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _persistentRunnerComponent = _persistentRunner.AddComponent(); Log.LogInfo((object)"[PersistentRunner] Created"); } } private void InitializeManagers() { _staticManager = new GiftRosterManager(); _staticSaveSystem = new GiftRosterSaveSystem(_staticManager); } private void ApplyPatches() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown try { Type type = AccessTools.TypeByName("Wish.Player"); MethodInfo methodInfo = ((type != null) ? AccessTools.Method(type, "InitializeAsOwner", (Type[])null, (Type[])null) : null); if (methodInfo != null) { MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayerLoadPatch), "OnPlayerInitialized", (Type[])null, (Type[])null); _harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Applied player initialization patch"); } else { Log.LogWarning((object)"Could not find Wish.Player.InitializeAsOwner - roster auto-load disabled"); } } catch (Exception ex) { Log.LogWarning((object)("Failed to apply player init patch: " + ex.Message)); } try { Type type2 = AccessTools.TypeByName("Wish.NPCAI"); Type type3 = AccessTools.TypeByName("Wish.Item"); MethodInfo methodInfo3 = ((type2 != null && type3 != null) ? AccessTools.Method(type2, "Gift", new Type[1] { type3 }, (Type[])null) : null); if (methodInfo3 != null) { MethodInfo methodInfo4 = AccessTools.Method(typeof(GiftPatch), "OnGiftGiven", (Type[])null, (Type[])null); _harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Log.LogInfo((object)"Applied gift tracking patch (NPCAI.Gift)"); } else { Log.LogWarning((object)"Could not find NPCAI.Gift(Item) - instant gift tracking disabled"); } } catch (Exception ex2) { Log.LogWarning((object)("Failed to apply gift patch: " + ex2.Message)); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (((Scene)(ref scene)).name == "MainMenu" || ((Scene)(ref scene)).name == "Bootstrap") { _overnightHooked = false; _overnightCallback = null; if (!_wasInMenuScene) { SaveData(); PlayerLoadPatch.ResetForMenu(); } _wasInMenuScene = true; } else { _wasInMenuScene = false; EnsureUIComponentsExist(); } } public static void EnsureUIComponentsExist() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown try { LocalizationBootstrap.EnsureInitialized("com.azraelgodking.giftingassistant", Instance?._harmony, Log, typeof(Plugin).Assembly); if ((Object)(object)_persistentRunner == (Object)null || (Object)(object)_persistentRunnerComponent == (Object)null) { _persistentRunner = new GameObject("GiftingAssistant_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _persistentRunnerComponent = _persistentRunner.AddComponent(); } if ((Object)(object)_staticWindow == (Object)null) { GameObject val = new GameObject("GiftingAssistant_UI"); Object.DontDestroyOnLoad((Object)val); _staticWindow = val.AddComponent(); _staticWindow.Initialize(_staticManager, _birthdayIntegration, _todoIntegration); _staticWindow.SetScale(_staticUIScale); _staticWindow.SetShowPossession(_staticShowPossession); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[EnsureUI] GiftingWindow created"); } } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)("[EnsureUI] Error: " + ex.Message)); } } } public void LoadDataForCharacter(string characterName) { if (string.IsNullOrEmpty(characterName)) { Log.LogWarning((object)"Cannot load roster: no character name"); return; } GiftRosterData data = _staticSaveSystem.Load(characterName); _staticManager.LoadForCharacter(characterName, data); _staticWindow?.InvalidateNpcIndex(); GiftGameData.ScheduleNpcCacheWarm(); Log.LogInfo((object)("Loaded gift roster for character: " + characterName)); TryHookOvernight(); _todoIntegration?.ResetTracking(); QueueEnsureGiftTodos(); } public static void SaveData() { if (_staticManager != null && _staticManager.IsDirty) { _staticSaveSystem?.Save(); } } public static void MarkNpcGifted(string npcName) { if (_staticManager == null || string.IsNullOrEmpty(npcName)) { return; } npcName = GiftGameData.NormalizeNpcName(npcName); if (string.IsNullOrEmpty(npcName)) { return; } if (_staticManager.Contains(npcName)) { _staticManager.SetManualGifted(npcName, gifted: true); GiftGameData.SetGiftedToday(npcName, gifted: true); _staticWindow?.MarkGiftedSortDirty(); QueueCompleteGiftTodo(npcName, completed: true); return; } foreach (GiftRosterEntry entry in _staticManager.GetEntries()) { if (string.Equals(GiftGameData.NormalizeNpcName(entry.NpcName), npcName, StringComparison.OrdinalIgnoreCase)) { _staticManager.SetManualGifted(entry.NpcName, gifted: true); GiftGameData.SetGiftedToday(entry.NpcName, gifted: true); _staticWindow?.MarkGiftedSortDirty(); QueueCompleteGiftTodo(entry.NpcName, completed: true); break; } } } public static void CompleteGiftTodo(string npcName, bool completed) { QueueCompleteGiftTodo(npcName, completed); } public static void QueueCompleteGiftTodo(string npcName, bool completed) { if (_todoIntegration != null && _todoIntegration.CanPushTodos) { _todoIntegration.QueueComplete(npcName, completed); } } public static void QueueRemoveGiftTodo(string npcName) { if (_todoIntegration != null && _todoIntegration.CanPushTodos) { _todoIntegration.QueueRemove(npcName); } } public static void EnsureGiftTodos() { QueueEnsureGiftTodos(); } public static void QueueEnsureGiftTodos() { if (_todoIntegration != null && _todoIntegration.CanPushTodos) { _todoIntegration.QueueEnsureAll(); } } public static void PublishGiftTodo(GiftRosterEntry entry) { QueuePublishGiftTodo(entry); } public static void QueuePublishGiftTodo(GiftRosterEntry entry) { if (entry != null && _todoIntegration != null && _todoIntegration.CanPushTodos) { _todoIntegration.QueuePublish(entry); } } internal static void ProcessDeferredWork() { GiftGameData.ProcessDeferredNpcCache(); _staticWindow?.NotifyNpcCacheReady(); _todoIntegration?.ProcessPending(); } public static void TryHookOvernight() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown object obj = <>O.<1>__OnNewDay; if (obj == null) { UnityAction val = OnNewDay; <>O.<1>__OnNewDay = val; obj = (object)val; } OvernightHookUtility.TryHookOvernightEvent(ref _overnightHooked, ref _overnightCallback, (UnityAction)obj, delegate(Type type) { try { Type type2 = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type2 != null) { return type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null); } } catch (Exception) { } return (object)null; }, delegate(string msg) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)msg); } }, delegate(string msg) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)msg); } }); } private static void OnNewDay() { if (_staticManager != null) { string currentDateKey = GetCurrentDateKey(); _staticManager.ResetDailyGifted(currentDateKey); GiftGameData.InvalidateCache(); GiftGameData.ScheduleNpcCacheWarm(); GiftGameData.RefreshGiftedTodayCache(); _staticWindow?.InvalidateGiftedTodaySort(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[GiftingAssistant] New day - reset daily gifted flags."); } RefreshDailyTodos(currentDateKey); SaveData(); } } private static void RefreshDailyTodos(string dateKey) { if (_todoIntegration != null && _todoIntegration.CanPushTodos && (string.IsNullOrEmpty(dateKey) || !string.Equals(_lastTodoRefreshDateKey, dateKey, StringComparison.Ordinal))) { _lastTodoRefreshDateKey = dateKey ?? ""; _todoIntegration?.ResetTracking(); _todoIntegration?.QueueRefreshDaily(); } } private static string GetCurrentDateKey() { try { Type type = AccessTools.TypeByName("Wish.DayCycle"); if (type == null) { return ""; } int num = ((AccessTools.Property(type, "Year")?.GetValue(null) is int num2) ? num2 : 0); int num3 = ((AccessTools.Property(type, "MonthDay")?.GetValue(null) is int num4) ? num4 : 0); string arg = ""; Type type2 = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type2 != null) { object obj = type2.MakeGenericType(type).GetProperty("Instance")?.GetValue(null); if (obj != null) { arg = AccessTools.Property(type, "Season")?.GetValue(obj)?.ToString() ?? ""; } } return $"{num}_{arg}_{num3}"; } catch (Exception) { return ""; } } public static void ToggleUI() { EnsureUIComponentsExist(); _staticWindow?.Toggle(); } public static void ShowUI() { EnsureUIComponentsExist(); _staticWindow?.Show(); } public static void HideUI() { _staticWindow?.Hide(); } public static GiftRosterManager GetManager() { return _staticManager; } public static GiftingWindow GetWindow() { return _staticWindow; } internal static void TickAutoSave() { if (_staticManager != null && _staticManager.IsDirty && _staticAutoSave && !(Time.unscaledTime - _lastAutoSaveTime < _staticAutoSaveInterval)) { SaveData(); _lastAutoSaveTime = Time.unscaledTime; } } private static void OnLanguageChanged(string _) { _staticWindow?.RefreshLocalization(); } private void OnDestroy() { //IL_003b: 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) SceneManager.sceneLoaded -= OnSceneLoaded; ModLocalization.LanguageChanged -= OnLanguageChanged; ModLocalization.Shutdown(); SaveData(); Scene activeScene = SceneManager.GetActiveScene(); string text = ((Scene)(ref activeScene)).name ?? string.Empty; string text2 = text.ToLowerInvariant(); if (_applicationQuitting || !Application.isPlaying || text2.Contains("menu") || text2.Contains("title")) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[Lifecycle] Plugin OnDestroy during expected teardown (scene: " + text + ")")); } } else { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("[Lifecycle] Plugin OnDestroy outside expected teardown (scene: " + text + ")")); } } } private void OnApplicationQuit() { _applicationQuitting = true; SaveData(); } } public class PersistentRunner : MonoBehaviour { private void Update() { CheckHotkeys(); Plugin.TickAutoSave(); Plugin.ProcessDeferredWork(); } private void CheckHotkeys() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (Plugin.StaticEnabled && !TextInputFocusGuard.ShouldDeferModHotkeys(Plugin.Log)) { bool flag = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); if (Input.GetKeyDown(Plugin.StaticToggleKey) && flag == Plugin.StaticRequireCtrl) { Plugin.ToggleUI(); } } } private void OnDestroy() { //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(); string text = (((Scene)(ref activeScene)).name ?? string.Empty).ToLowerInvariant(); if (!Application.isPlaying || text.Contains("menu") || text.Contains("title")) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[PersistentRunner] OnDestroy during app quit/menu unload (expected)."); } } else { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[PersistentRunner] OnDestroy outside quit/menu (unexpected)."); } } } } internal static class PluginInfo { public const string PLUGIN_GUID = "com.azraelgodking.giftingassistant"; public const string PLUGIN_NAME = "Gifting Assistant"; public const string PLUGIN_VERSION = "1.0.0"; } } namespace GiftingAssistant.UI { public class GiftingWindow : MonoBehaviour { private const int WINDOW_ID = 6388295; private const float BASE_WINDOW_WIDTH = 560f; private const float BASE_WINDOW_HEIGHT = 620f; private const float BASE_HEADER_HEIGHT = 46f; private const float BASE_ICON_SIZE = 26f; private const int MAX_LOVED_ICONS = 4; private const int MAX_LIKED_ICONS = 3; private const string PAUSE_ID = "GiftingAssistant_UI"; private const float BIRTHDAY_REFRESH_INTERVAL = 10f; private float _scale = 1f; private bool _showPossession = true; private bool _isVisible; private bool _showPicker; private Rect _windowRect; private Vector2 _scrollRoster; private Vector2 _scrollPicker; private Vector2 _scrollGiftEdit; private string _pickerSearch = ""; private string _editGiftsNpc; private string _pendingGiftSelectorNpc; private bool _giftSelectorLoading; private float _openAnimation; private readonly List _pickerCandidates = new List(); private bool _pickerListDirty = true; private string _pickerSearchApplied = ""; private const int GIFT_SELECTOR_ROWS_PER_FRAME = 12; private int _giftSelectorVisibleRows; private readonly Dictionary _itemNameCache = new Dictionary(); private GiftRosterManager _manager; private BirthdayIntegration _birthdayIntegration; private TodoIntegration _todoIntegration; private UiStyle _style; private bool _stylesDirty = true; private Dictionary _npcIndex; private bool _npcIndexDirty = true; private readonly List _sortedRoster = new List(); private bool _sortDirty = true; private HashSet _birthdayNames = new HashSet(StringComparer.OrdinalIgnoreCase); private float _birthdayTimer; private bool _sortProcessing; private float WindowWidth => 560f * _scale; private float WindowHeight => 620f * _scale; private float HeaderHeight => 46f * _scale; private float IconSize => 26f * _scale; public bool IsVisible => _isVisible; public void Initialize(GiftRosterManager manager, BirthdayIntegration birthdayIntegration, TodoIntegration todoIntegration) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) _manager = manager; _birthdayIntegration = birthdayIntegration; _todoIntegration = todoIntegration; if (_manager != null) { _manager.OnRosterChanged += MarkDirty; _manager.OnDataLoaded += OnDataLoaded; } float windowWidth = WindowWidth; float windowHeight = WindowHeight; _windowRect = new Rect(((float)Screen.width - windowWidth) / 2f, ((float)Screen.height - windowHeight) / 2f, windowWidth, windowHeight); } private void OnDataLoaded() { _sortDirty = true; } private void MarkDirty() { _sortDirty = true; _pickerListDirty = true; } public void SetScale(float scale) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) _scale = Mathf.Clamp(scale, 0.5f, 2.5f); _stylesDirty = true; _windowRect = new Rect(((float)Screen.width - WindowWidth) / 2f, ((float)Screen.height - WindowHeight) / 2f, WindowWidth, WindowHeight); } public void SetShowPossession(bool show) { _showPossession = show; } public void RefreshLocalization() { } public void InvalidateNpcIndex() { _npcIndexDirty = true; GiftGameData.ScheduleNpcCacheWarm(); } public void MarkGiftedSortDirty() { _sortDirty = true; } public void InvalidateGiftedTodaySort() { _sortDirty = true; GiftSuggestionResolver.ClearPrimaryIconCache(); } public void NotifyNpcCacheReady() { if (GiftGameData.IsNpcCacheReady) { _npcIndexDirty = true; _pickerListDirty = true; } } public void Show() { _isVisible = true; _openAnimation = 0f; _npcIndexDirty = true; _sortDirty = true; GiftSuggestionResolver.ClearPrimaryIconCache(); GiftGameData.RefreshGiftedTodayCache(); GiftGameData.RefreshRelationshipCache(); RefreshBirthdays(); GiftGameData.ScheduleNpcCacheWarm(); IconCache.LoadAllIcons(); PauseGame(pause: true); } public void Hide() { _isVisible = false; _showPicker = false; _editGiftsNpc = null; _pendingGiftSelectorNpc = null; _giftSelectorLoading = false; PauseGame(pause: false); } public void Toggle() { if (_isVisible) { Hide(); } else { Show(); } } private void PauseGame(bool pause) { try { if ((Object)(object)Player.Instance != (Object)null) { if (pause) { Player.Instance.AddPauseObject("GiftingAssistant_UI"); } else { Player.Instance.RemovePauseObject("GiftingAssistant_UI"); } } Type type = Type.GetType("PlayerInput, Assembly-CSharp"); if (type != null) { type.GetMethod(pause ? "DisableInput" : "EnableInput", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(string) }, null)?.Invoke(null, new object[1] { "GiftingAssistant_UI" }); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftingAssistant] Input blocking failed: " + ex.Message)); } } } private void Update() { if (!_isVisible) { return; } if (Input.GetKeyDown((KeyCode)27)) { if (_editGiftsNpc != null || _giftSelectorLoading || _pendingGiftSelectorNpc != null) { _editGiftsNpc = null; _pendingGiftSelectorNpc = null; _giftSelectorLoading = false; } else if (_showPicker) { _showPicker = false; } else { Hide(); } } else { _openAnimation = Mathf.MoveTowards(_openAnimation, 1f, Time.unscaledDeltaTime * 8f); _birthdayTimer += Time.unscaledDeltaTime; if (_birthdayTimer >= 10f) { _birthdayTimer = 0f; RefreshBirthdays(); } ProcessDeferredUiWork(); } } private void ProcessDeferredUiWork() { //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) if (_sortDirty && !_sortProcessing) { _sortProcessing = true; try { EnsureSorted(); } finally { _sortProcessing = false; } } if (_showPicker && _pickerListDirty) { RebuildPickerCandidates(); } string text = _pickerSearch?.Trim().ToLowerInvariant() ?? ""; if (_showPicker && text != _pickerSearchApplied) { _pickerListDirty = true; } if (_pendingGiftSelectorNpc != null) { GiftGameData.EnsureNpcCacheReady(); _editGiftsNpc = _pendingGiftSelectorNpc; _pendingGiftSelectorNpc = null; _giftSelectorLoading = false; _giftSelectorVisibleRows = 12; _scrollGiftEdit = Vector2.zero; _itemNameCache.Clear(); } if (_editGiftsNpc != null && !_giftSelectorLoading) { _giftSelectorVisibleRows = Mathf.Min(_giftSelectorVisibleRows + 12, GetGiftSelectorRowCount(_editGiftsNpc) + 12); } } private int GetGiftSelectorRowCount(string npcName) { GiftNpcInfo giftNpcInfo = ResolveNpc(npcName); if (giftNpcInfo == null) { return 0; } return giftNpcInfo.LovedItemIds.Count + giftNpcInfo.LikedItemIds.Count; } private void RebuildPickerCandidates() { _pickerListDirty = false; _pickerCandidates.Clear(); _pickerSearchApplied = _pickerSearch?.Trim().ToLowerInvariant() ?? ""; if (!GiftGameData.IsNpcCacheReady) { GiftGameData.ScheduleNpcCacheWarm(); return; } string pickerSearchApplied = _pickerSearchApplied; foreach (GiftNpcInfo allNpc in GiftGameData.GetAllNpcs()) { if (!_manager.Contains(allNpc.Name) && !_manager.Contains(GiftGameData.NormalizeNpcName(allNpc.Name)) && (pickerSearchApplied.Length <= 0 || allNpc.Name.ToLowerInvariant().IndexOf(pickerSearchApplied, StringComparison.Ordinal) >= 0)) { _pickerCandidates.Add(allNpc); } } } private void RefreshBirthdays() { if (_birthdayIntegration != null && _birthdayIntegration.IsAvailable) { _birthdayNames = _birthdayIntegration.GetTodaysBirthdayNames(); } else if (_birthdayNames.Count > 0) { _birthdayNames = new HashSet(StringComparer.OrdinalIgnoreCase); } } private void OnGUI() { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) if (_isVisible && _manager != null) { if (_stylesDirty || _style == null) { _style = _style ?? new UiStyle(); _style.Build(_scale); _stylesDirty = false; } ((Rect)(ref _windowRect)).width = WindowWidth; ((Rect)(ref _windowRect)).height = WindowHeight; GUI.color = new Color(1f, 1f, 1f, _openAnimation); GUI.depth = -1000; _windowRect = GUI.Window(6388295, _windowRect, new WindowFunction(DrawWindow), "", _style.Window); ((Rect)(ref _windowRect)).x = Mathf.Clamp(((Rect)(ref _windowRect)).x, 0f, (float)Screen.width - ((Rect)(ref _windowRect)).width); ((Rect)(ref _windowRect)).y = Mathf.Clamp(((Rect)(ref _windowRect)).y, 0f, (float)Screen.height - ((Rect)(ref _windowRect)).height); GUI.color = Color.white; } } private void DrawWindow(int windowId) { //IL_0079: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(Array.Empty()); DrawHeader(); DrawDivider(); DrawStats(); DrawTrackingMode(); if (_editGiftsNpc != null || _giftSelectorLoading || _pendingGiftSelectorNpc != null) { DrawGiftSelector(); } else if (_showPicker) { DrawPicker(); } else { DrawRoster(); } DrawFooter(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, WindowWidth, HeaderHeight)); } private void DrawHeader() { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(ModLocalization.T("gift.window.title"), _style.Title, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(_showPicker ? ModLocalization.T("gift.button.cancel") : ModLocalization.T("gift.button.add"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(90f)), GUILayout.Height(_style.S(28f)) })) { _showPicker = !_showPicker; if (_showPicker) { _pickerSearch = ""; _pickerListDirty = true; GiftGameData.RefreshRelationshipCache(); GiftGameData.ScheduleNpcCacheWarm(); } } GUILayout.Space(_style.S(8f)); if (GUILayout.Button("X", _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(28f)), GUILayout.Height(_style.S(28f)) })) { Hide(); } GUILayout.EndHorizontal(); } private void DrawDivider() { //IL_003c: 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) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Invalid comparison between Unknown and I4 //IL_0062: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(_style.S(4f)); Rect rect = GUILayoutUtility.GetRect(WindowWidth - _style.S(40f), _style.S(3f)); if ((int)Event.current.type == 7 && (Object)(object)_style.GoldLine != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)_style.GoldLine); } GUILayout.Space(_style.S(4f)); } private void DrawStats() { int count = _manager.GetEntries().Count; int num = 0; foreach (GiftRosterEntry entry in _manager.GetEntries()) { if (!IsGiftedToday(entry)) { num++; } } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ModLocalization.T("gift.stats.tracked", count), _style.Stats, Array.Empty()); GUILayout.Space(_style.S(20f)); GUILayout.Label(ModLocalization.T("gift.stats.remaining", num), _style.Stats, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(_style.S(6f)); } private void DrawTrackingMode() { if (_todoIntegration != null) { string text = null; string key; if (_todoIntegration.CanPushTodos) { key = "gift.tracking.mode.todo"; } else if (_todoIntegration.IsEnabled && !_todoIntegration.IsAvailable) { key = "gift.tracking.mode.roster"; text = "gift.tracking.hint.todoNotInstalled"; } else { key = "gift.tracking.mode.roster"; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ModLocalization.T(key), _style.Footer, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); if (text != null) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ModLocalization.T(text), _style.Footer, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } GUILayout.Space(_style.S(4f)); } } private void DrawRoster() { //IL_002a: 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_0043: Unknown result type (might be due to invalid IL or missing references) if (_sortDirty) { GUILayout.Label(ModLocalization.T("gift.list.loading"), _style.Label, Array.Empty()); return; } _scrollRoster = GUILayout.BeginScrollView(_scrollRoster, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); if (_sortedRoster.Count == 0) { GUILayout.Space(_style.S(20f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ModLocalization.T("gift.list.empty"), _style.Label, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } else { for (int i = 0; i < _sortedRoster.Count; i++) { DrawRosterRow(_sortedRoster[i], i); } } GUILayout.EndScrollView(); } private void DrawRosterRow(GiftRosterEntry entry, int index) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) bool flag = IsGiftedToday(entry); _style.Row.normal.background = _style.RowBackground(index, flag); GUILayout.BeginVertical(_style.Row, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); Color value; Color val = (_style.PriorityColors.TryGetValue(entry.Priority, out value) ? value : _style.TextDark); Color color = GUI.color; GUI.color = new Color(val.r, val.g, val.b, _openAnimation); if (GUILayout.Button(ModLocalization.T($"gift.priority.{entry.Priority}"), _style.PriorityBadge, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(64f)), GUILayout.Height(_style.S(24f)) })) { _manager.SetPriority(entry.NpcName, NextPriority(entry.Priority)); } GUI.color = color; GUILayout.Space(_style.S(6f)); string text = entry.NpcName; if (_birthdayNames.Contains(entry.NpcName)) { text = text + " (" + ModLocalization.T("gift.label.birthday") + ")"; } GUILayout.Label(text, _style.LabelBold, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_style.S(24f)) }); string text2 = FormatRelationshipLabel(entry.NpcName); if (!string.IsNullOrEmpty(text2)) { GUILayout.Space(_style.S(4f)); GUILayout.Label(text2, _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_style.S(24f)) }); } GUILayout.FlexibleSpace(); bool flag2 = GUILayout.Toggle(flag, flag ? ModLocalization.T("gift.label.gifted") : ModLocalization.T("gift.label.notGifted"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(96f)), GUILayout.Height(_style.S(24f)) }); if (flag2 != flag) { _manager.SetManualGifted(entry.NpcName, flag2); if (flag2) { GiftGameData.SetGiftedToday(entry.NpcName, gifted: true); } _sortDirty = true; } GUILayout.Space(_style.S(4f)); if (GUILayout.Button(ModLocalization.T("gift.button.gifts"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(54f)), GUILayout.Height(_style.S(24f)) })) { _showPicker = false; _pendingGiftSelectorNpc = entry.NpcName; _giftSelectorLoading = true; _editGiftsNpc = null; GiftGameData.ScheduleNpcCacheWarm(); } if (_todoIntegration != null && _todoIntegration.CanPushTodos) { GUILayout.Space(_style.S(4f)); if (GUILayout.Button(ModLocalization.T("gift.button.todo"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(54f)), GUILayout.Height(_style.S(24f)) })) { Plugin.QueuePublishGiftTodo(entry); } } GUILayout.Space(_style.S(4f)); if (GUILayout.Button("x", _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(24f)), GUILayout.Height(_style.S(24f)) })) { _manager.RemoveNpc(entry.NpcName); Plugin.QueueRemoveGiftTodo(entry.NpcName); _sortDirty = true; _pickerListDirty = true; } GUILayout.EndHorizontal(); DrawGiftIcons(entry); GUILayout.EndVertical(); } private void DrawGiftIcons(GiftRosterEntry entry) { List list = GiftSuggestionResolver.ResolveDisplayGiftIds(entry); if (list.Count > 0) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(_style.S(6f)); GUILayout.Label(ModLocalization.T("gift.label.preferred"), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(_style.S(64f)) }); DrawIconRow(list, 4); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); return; } GiftNpcInfo giftNpcInfo = ResolveNpc(entry.NpcName); if (giftNpcInfo != null) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(_style.S(6f)); if (giftNpcInfo.LovedItemIds.Count > 0) { GUILayout.Label(ModLocalization.T("gift.label.loves"), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(_style.S(48f)) }); DrawIconRow(giftNpcInfo.LovedItemIds, 4); } if (giftNpcInfo.LikedItemIds.Count > 0) { GUILayout.Space(_style.S(8f)); GUILayout.Label(ModLocalization.T("gift.label.likes"), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(_style.S(48f)) }); DrawIconRow(giftNpcInfo.LikedItemIds, 3); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } } private void DrawIconRow(List itemIds, int max) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.Min(itemIds.Count, max); for (int i = 0; i < num; i++) { int itemId = itemIds[i]; Texture2D icon = IconCache.GetIcon(itemId); string cachedItemName = GetCachedItemName(itemId); int num2 = (_showPossession ? PlayerInventoryReader.GetAmount(itemId) : 0); Rect rect = GUILayoutUtility.GetRect(IconSize, IconSize, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(IconSize), GUILayout.Height(IconSize) }); if ((Object)(object)icon != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)icon, (ScaleMode)2); } GUI.Label(rect, new GUIContent("", cachedItemName)); if (_showPossession && num2 > 0) { GUILayout.Label(num2.ToString(), _style.LabelBold, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(22f)), GUILayout.Height(IconSize) }); } GUILayout.Space(_style.S(2f)); } if (itemIds.Count > max) { GUILayout.Label($"+{itemIds.Count - max}", _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(IconSize) }); } } private void DrawPicker() { //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(_style.Panel, Array.Empty()); GUILayout.Label(ModLocalization.T("gift.picker.title"), _style.Header, Array.Empty()); GUILayout.Space(_style.S(4f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(ModLocalization.T("gift.picker.search"), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(_style.S(56f)) }); _pickerSearch = GUILayout.TextField(_pickerSearch, _style.TextField, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_style.S(24f)) }); GUILayout.EndHorizontal(); GUILayout.Space(_style.S(6f)); _scrollPicker = GUILayout.BeginScrollView(_scrollPicker, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); if (!GiftGameData.IsNpcCacheReady || _pickerListDirty) { GUILayout.Space(_style.S(12f)); GUILayout.Label(ModLocalization.T("gift.picker.loading"), _style.Label, Array.Empty()); } else if (_pickerCandidates.Count == 0) { GUILayout.Space(_style.S(12f)); GUILayout.Label(ModLocalization.T("gift.picker.empty"), _style.Label, Array.Empty()); } else { foreach (GiftNpcInfo pickerCandidate in _pickerCandidates) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("+", _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(28f)), GUILayout.Height(_style.S(24f)) })) { _manager.AddNpc(pickerCandidate.Name); _sortDirty = true; _pickerListDirty = true; _npcIndexDirty = true; } GUILayout.Space(_style.S(6f)); GUILayout.Label(pickerCandidate.Name, _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_style.S(24f)) }); GUILayout.Space(_style.S(4f)); GUILayout.Label(FormatRelationshipLabel(pickerCandidate.Name), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(_style.S(24f)) }); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(_style.S(2f)); } } GUILayout.EndScrollView(); GUILayout.Space(_style.S(6f)); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button(ModLocalization.T("gift.button.done"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(90f)), GUILayout.Height(_style.S(26f)) })) { _showPicker = false; } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void DrawGiftSelector() { //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(_style.Panel, Array.Empty()); if (_giftSelectorLoading || _pendingGiftSelectorNpc != null) { string text = _pendingGiftSelectorNpc ?? _editGiftsNpc ?? ""; GUILayout.Label(ModLocalization.T("gift.gifts.title", text), _style.Header, Array.Empty()); GUILayout.Space(_style.S(12f)); GUILayout.Label(ModLocalization.T("gift.gifts.loading"), _style.Label, Array.Empty()); GUILayout.EndVertical(); return; } string editGiftsNpc = _editGiftsNpc; GUILayout.Label(ModLocalization.T("gift.gifts.title", editGiftsNpc), _style.Header, Array.Empty()); GUILayout.Label(ModLocalization.T("gift.gifts.hint"), _style.Label, Array.Empty()); GUILayout.Space(_style.S(6f)); GiftNpcInfo giftNpcInfo = ResolveNpc(editGiftsNpc); _scrollGiftEdit = GUILayout.BeginScrollView(_scrollGiftEdit, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); if (giftNpcInfo == null || (giftNpcInfo.LovedItemIds.Count == 0 && giftNpcInfo.LikedItemIds.Count == 0)) { GUILayout.Space(_style.S(12f)); GUILayout.Label(ModLocalization.T("gift.gifts.none"), _style.Label, Array.Empty()); } else { int num = 0; int giftSelectorVisibleRows = _giftSelectorVisibleRows; if (giftNpcInfo.LovedItemIds.Count > 0) { GUILayout.Label(ModLocalization.T("gift.label.loves"), _style.LabelBold, Array.Empty()); foreach (int lovedItemId in giftNpcInfo.LovedItemIds) { if (num >= giftSelectorVisibleRows) { break; } DrawGiftSelectorRow(editGiftsNpc, lovedItemId); num++; } if (num < giftNpcInfo.LovedItemIds.Count) { GUILayout.Label(ModLocalization.T("gift.gifts.loadingMore"), _style.Label, Array.Empty()); } else { GUILayout.Space(_style.S(6f)); } } if (num < giftSelectorVisibleRows && giftNpcInfo.LikedItemIds.Count > 0) { GUILayout.Label(ModLocalization.T("gift.label.likes"), _style.LabelBold, Array.Empty()); foreach (int likedItemId in giftNpcInfo.LikedItemIds) { if (num >= giftSelectorVisibleRows) { break; } DrawGiftSelectorRow(editGiftsNpc, likedItemId); num++; } if (num < giftNpcInfo.LovedItemIds.Count + giftNpcInfo.LikedItemIds.Count) { GUILayout.Label(ModLocalization.T("gift.gifts.loadingMore"), _style.Label, Array.Empty()); } } } GUILayout.EndScrollView(); GUILayout.Space(_style.S(6f)); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(ModLocalization.T("gift.gifts.clear"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(110f)), GUILayout.Height(_style.S(26f)) })) { _manager.ClearPreferredGifts(editGiftsNpc); _sortDirty = true; } GUILayout.FlexibleSpace(); if (GUILayout.Button(ModLocalization.T("gift.button.done"), _style.Button, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(90f)), GUILayout.Height(_style.S(26f)) })) { _editGiftsNpc = null; } GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void DrawGiftSelectorRow(string npcName, int itemId) { //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) bool flag = _manager.IsPreferredGift(npcName, itemId); string cachedItemName = GetCachedItemName(itemId); float num = _style.S(28f); _style.Row.normal.background = (flag ? _style.GiftSelectorRowSelected : null); GUILayout.BeginHorizontal(_style.Row, Array.Empty()); if (GUILayout.Toggle(flag, flag ? "✓" : "", _style.Checkbox, (GUILayoutOption[])(object)new GUILayoutOption[3] { GUILayout.Width(num), GUILayout.MinWidth(num), GUILayout.Height(IconSize) }) != flag) { _manager.TogglePreferredGift(npcName, itemId); _sortDirty = true; } GUILayout.Space(_style.S(6f)); Texture2D icon = IconCache.GetIcon(itemId); Rect rect = GUILayoutUtility.GetRect(IconSize, IconSize, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(IconSize), GUILayout.Height(IconSize) }); if ((Object)(object)icon != (Object)null) { GUI.DrawTexture(rect, (Texture)(object)icon, (ScaleMode)2); } GUI.Label(rect, new GUIContent("", cachedItemName)); GUILayout.Space(_style.S(8f)); GUIStyle val = (flag ? _style.LabelBold : _style.Label); GUILayout.Label(cachedItemName, val, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.ExpandWidth(true), GUILayout.Height(IconSize) }); if (_showPossession) { int amount = PlayerInventoryReader.GetAmount(itemId); if (amount > 0) { GUILayout.Label(ModLocalization.T("gift.gifts.owned", amount), _style.Label, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(_style.S(72f)), GUILayout.Height(IconSize) }); } } GUILayout.EndHorizontal(); GUILayout.Space(_style.S(2f)); } private void DrawFooter() { DrawDivider(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(ModLocalization.T("gift.footer.hint", Plugin.GetOpenShortcutDisplay()), _style.Footer, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void EnsureSorted() { if (!_sortDirty) { return; } _sortDirty = false; _sortedRoster.Clear(); _sortedRoster.AddRange(_manager.GetEntries()); _sortedRoster.Sort(delegate(GiftRosterEntry a, GiftRosterEntry b) { bool flag = IsGiftedToday(a); bool flag2 = IsGiftedToday(b); if (flag != flag2) { if (!flag) { return -1; } return 1; } int num = ((int)b.Priority).CompareTo((int)a.Priority); return (num != 0) ? num : string.Compare(a.NpcName, b.NpcName, StringComparison.OrdinalIgnoreCase); }); } private bool IsGiftedToday(GiftRosterEntry entry) { if (entry == null) { return false; } if (entry.ManualGiftedToday) { return true; } return GiftGameData.HasGivenGiftToday(entry.NpcName); } private GiftNpcInfo ResolveNpc(string npcName) { if (_npcIndex == null || _npcIndexDirty) { RebuildNpcIndex(); } if (_npcIndex == null) { return null; } if (_npcIndex.TryGetValue(npcName, out GiftNpcInfo value)) { return value; } string text = GiftGameData.NormalizeNpcName(npcName); if (string.IsNullOrEmpty(text) || !_npcIndex.TryGetValue(text, out value)) { return null; } return value; } private void RebuildNpcIndex() { _npcIndexDirty = false; _npcIndex = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!GiftGameData.IsNpcCacheReady) { GiftGameData.ScheduleNpcCacheWarm(); return; } foreach (GiftRosterEntry entry in _manager.GetEntries()) { if (entry == null || string.IsNullOrEmpty(entry.NpcName)) { continue; } GiftNpcInfo npcInfo = GiftGameData.GetNpcInfo(entry.NpcName); if (npcInfo != null) { string text = GiftGameData.NormalizeNpcName(entry.NpcName); if (!string.IsNullOrEmpty(text)) { _npcIndex[text] = npcInfo; } _npcIndex[entry.NpcName] = npcInfo; } } } private string GetCachedItemName(int itemId) { if (_itemNameCache.TryGetValue(itemId, out string value)) { return value; } value = ItemSearch.GetItemName(itemId) ?? $"#{itemId}"; _itemNameCache[itemId] = value; return value; } private string FormatRelationshipLabel(string npcName) { float? relationshipPoints = GiftGameData.GetRelationshipPoints(npcName); if (!relationshipPoints.HasValue) { return ModLocalization.T("gift.label.heartsUnknown"); } return GiftGameData.FormatRelationshipHearts(relationshipPoints.Value); } private static GiftPriority NextPriority(GiftPriority current) { return current switch { GiftPriority.Low => GiftPriority.Normal, GiftPriority.Normal => GiftPriority.High, GiftPriority.High => GiftPriority.Urgent, _ => GiftPriority.Low, }; } private void OnDestroy() { if (_manager != null) { _manager.OnRosterChanged -= MarkDirty; _manager.OnDataLoaded -= OnDataLoaded; } _style?.Destroy(); } } internal sealed class UiStyle { private readonly Color _parchmentLight = new Color(0.96f, 0.93f, 0.86f, 0.98f); private readonly Color _parchment = new Color(0.92f, 0.87f, 0.78f, 0.97f); private readonly Color _parchmentDark = new Color(0.85f, 0.78f, 0.65f, 0.95f); private readonly Color _woodDark = new Color(0.35f, 0.25f, 0.15f); private readonly Color _woodMedium = new Color(0.5f, 0.38f, 0.25f); private readonly Color _woodLight = new Color(0.65f, 0.52f, 0.38f); private readonly Color _goldRich = new Color(0.85f, 0.68f, 0.2f); private readonly Color _goldBright = new Color(0.95f, 0.8f, 0.3f); private readonly Color _goldPale = new Color(0.98f, 0.95f, 0.85f); private readonly Color _successGreen = new Color(0.35f, 0.65f, 0.35f); private readonly Color _textDark = new Color(0.25f, 0.2f, 0.15f); private readonly Color _borderDark = new Color(0.4f, 0.3f, 0.2f, 0.8f); public readonly Dictionary PriorityColors = new Dictionary { { GiftPriority.Low, new Color(0.5f, 0.6f, 0.7f) }, { GiftPriority.Normal, new Color(0.45f, 0.55f, 0.45f) }, { GiftPriority.High, new Color(0.85f, 0.65f, 0.25f) }, { GiftPriority.Urgent, new Color(0.8f, 0.3f, 0.25f) } }; public GUIStyle Window; public GUIStyle Panel; public GUIStyle Title; public GUIStyle Header; public GUIStyle Label; public GUIStyle LabelBold; public GUIStyle Stats; public GUIStyle Footer; public GUIStyle Button; public GUIStyle TextField; public GUIStyle Tab; public GUIStyle TabActive; public GUIStyle Row; public GUIStyle PriorityBadge; public GUIStyle Checkbox; public Texture2D GoldLine; public Texture2D GiftSelectorRowSelected; private Texture2D _windowBg; private Texture2D _panelBg; private Texture2D _buttonNormal; private Texture2D _buttonHover; private Texture2D _buttonActive; private Texture2D _tabNormal; private Texture2D _tabActive; private Texture2D _textFieldBg; private Texture2D _rowEven; private Texture2D _rowOdd; private Texture2D _rowGifted; private Texture2D _rowSelected; private Texture2D _checkboxNormal; private Texture2D _checkboxChecked; public float Scale { get; private set; } = 1f; public Color TextDark => _textDark; public Color WoodDark => _woodDark; public Color SuccessGreen => _successGreen; public int Font(int baseSize) { return Mathf.Max(8, Mathf.RoundToInt((float)baseSize * Scale)); } public float S(float value) { return value * Scale; } public int SInt(float value) { return Mathf.RoundToInt(value * Scale); } public Texture2D RowBackground(int index, bool gifted) { if (gifted) { return _rowGifted; } if (index % 2 != 0) { return _rowOdd; } return _rowEven; } public void Build(float scale) { Scale = Mathf.Clamp(scale, 0.5f, 2.5f); DestroyTextures(); CreateTextures(); CreateStyles(); } private void CreateTextures() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: 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_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d4: Unknown result type (might be due to invalid IL or missing references) //IL_01da: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0201: Unknown result type (might be due to invalid IL or missing references) _windowBg = MakeRoundedRect(32, 32, _parchment, _borderDark, 4); _panelBg = MakeRoundedRect(16, 16, _parchmentLight, _borderDark, 2); _buttonNormal = MakeRoundedRect(8, 8, _parchmentDark, _borderDark, 2); _buttonHover = MakeRoundedRect(8, 8, _woodLight, _borderDark, 2); _buttonActive = MakeRoundedRect(8, 8, _goldPale, _goldRich, 2); _tabNormal = MakeRoundedRect(8, 8, _parchmentDark, _borderDark, 1); _tabActive = MakeRoundedRect(8, 8, _goldPale, _goldRich, 2); _textFieldBg = MakeSolid(new Color(1f, 1f, 1f, 0.9f)); _rowEven = MakeSolid(new Color(_parchmentLight.r, _parchmentLight.g, _parchmentLight.b, 0.4f)); _rowOdd = MakeSolid(new Color(_parchment.r, _parchment.g, _parchment.b, 0.4f)); _rowGifted = MakeSolid(new Color(_successGreen.r, _successGreen.g, _successGreen.b, 0.18f)); _rowSelected = MakeSolid(new Color(_goldPale.r, _goldPale.g, _goldPale.b, 0.55f)); _checkboxNormal = MakeRoundedRect(8, 8, _parchmentLight, _woodMedium, 2); _checkboxChecked = MakeRoundedRect(8, 8, _goldPale, _goldRich, 2); GiftSelectorRowSelected = _rowSelected; GoldLine = MakeGradient(64, 3, _goldBright, _goldRich); } private void CreateStyles() { //IL_0001: 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_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown //IL_009b: Expected O, but got Unknown //IL_009c: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_0136: Expected O, but got Unknown //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Expected O, but got Unknown //IL_01a5: Expected O, but got Unknown //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ab: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Expected O, but got Unknown //IL_0214: Expected O, but got Unknown //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Expected O, but got Unknown //IL_027c: Expected O, but got Unknown //IL_0283: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Expected O, but got Unknown //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02b5: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02cb: Expected O, but got Unknown //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Expected O, but got Unknown //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_033b: Unknown result type (might be due to invalid IL or missing references) //IL_0342: Unknown result type (might be due to invalid IL or missing references) //IL_0353: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0375: Unknown result type (might be due to invalid IL or missing references) //IL_037c: Unknown result type (might be due to invalid IL or missing references) //IL_0386: Unknown result type (might be due to invalid IL or missing references) //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_039e: Unknown result type (might be due to invalid IL or missing references) //IL_03a8: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e6: Expected O, but got Unknown //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Expected O, but got Unknown //IL_0422: Expected O, but got Unknown //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_0436: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Unknown result type (might be due to invalid IL or missing references) //IL_0458: Unknown result type (might be due to invalid IL or missing references) //IL_0469: Unknown result type (might be due to invalid IL or missing references) //IL_0470: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Expected O, but got Unknown //IL_04b1: Unknown result type (might be due to invalid IL or missing references) //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_04e8: Expected O, but got Unknown //IL_04ed: Expected O, but got Unknown //IL_04ee: Unknown result type (might be due to invalid IL or missing references) //IL_04f3: Unknown result type (might be due to invalid IL or missing references) //IL_0501: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_0519: Unknown result type (might be due to invalid IL or missing references) //IL_0523: Unknown result type (might be due to invalid IL or missing references) //IL_0534: Unknown result type (might be due to invalid IL or missing references) //IL_053b: Unknown result type (might be due to invalid IL or missing references) //IL_0545: Unknown result type (might be due to invalid IL or missing references) //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_055d: Unknown result type (might be due to invalid IL or missing references) //IL_0567: Unknown result type (might be due to invalid IL or missing references) //IL_056e: Unknown result type (might be due to invalid IL or missing references) //IL_059b: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Expected O, but got Unknown //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_05be: Unknown result type (might be due to invalid IL or missing references) //IL_05c8: Expected O, but got Unknown //IL_05c8: Unknown result type (might be due to invalid IL or missing references) //IL_05f5: Unknown result type (might be due to invalid IL or missing references) //IL_05ff: Expected O, but got Unknown //IL_0604: Expected O, but got Unknown //IL_060b: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Unknown result type (might be due to invalid IL or missing references) //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_063e: Expected O, but got Unknown //IL_063f: Unknown result type (might be due to invalid IL or missing references) //IL_0644: Unknown result type (might be due to invalid IL or missing references) //IL_0671: Unknown result type (might be due to invalid IL or missing references) //IL_067b: Expected O, but got Unknown //IL_067b: Unknown result type (might be due to invalid IL or missing references) //IL_0694: Unknown result type (might be due to invalid IL or missing references) //IL_069e: Expected O, but got Unknown //IL_06a3: Expected O, but got Unknown //IL_06aa: Unknown result type (might be due to invalid IL or missing references) //IL_06af: Unknown result type (might be due to invalid IL or missing references) //IL_06bd: Unknown result type (might be due to invalid IL or missing references) //IL_06c9: Expected O, but got Unknown //IL_06ca: Unknown result type (might be due to invalid IL or missing references) //IL_06cf: Unknown result type (might be due to invalid IL or missing references) //IL_06dd: Unknown result type (might be due to invalid IL or missing references) //IL_06e4: Unknown result type (might be due to invalid IL or missing references) //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_06fc: Unknown result type (might be due to invalid IL or missing references) //IL_0706: Unknown result type (might be due to invalid IL or missing references) //IL_0717: Unknown result type (might be due to invalid IL or missing references) //IL_071e: Unknown result type (might be due to invalid IL or missing references) //IL_0728: Unknown result type (might be due to invalid IL or missing references) //IL_0739: Unknown result type (might be due to invalid IL or missing references) //IL_0740: Unknown result type (might be due to invalid IL or missing references) //IL_074a: Unknown result type (might be due to invalid IL or missing references) //IL_075b: Unknown result type (might be due to invalid IL or missing references) //IL_0762: Unknown result type (might be due to invalid IL or missing references) //IL_076c: Unknown result type (might be due to invalid IL or missing references) //IL_077d: Unknown result type (might be due to invalid IL or missing references) //IL_0784: Unknown result type (might be due to invalid IL or missing references) //IL_078e: Unknown result type (might be due to invalid IL or missing references) //IL_079f: Unknown result type (might be due to invalid IL or missing references) //IL_07a6: Unknown result type (might be due to invalid IL or missing references) //IL_07b0: Unknown result type (might be due to invalid IL or missing references) //IL_07b7: Unknown result type (might be due to invalid IL or missing references) //IL_07be: Unknown result type (might be due to invalid IL or missing references) //IL_07eb: Unknown result type (might be due to invalid IL or missing references) //IL_07f5: Expected O, but got Unknown //IL_07f5: Unknown result type (might be due to invalid IL or missing references) //IL_0822: Unknown result type (might be due to invalid IL or missing references) //IL_082c: Expected O, but got Unknown //IL_0831: Expected O, but got Unknown GUIStyle val = new GUIStyle(); val.normal.background = _windowBg; val.normal.textColor = _textDark; val.padding = new RectOffset(SInt(15f), SInt(15f), SInt(15f), SInt(15f)); val.border = new RectOffset(SInt(8f), SInt(8f), SInt(8f), SInt(8f)); Window = val; GUIStyle val2 = new GUIStyle(); val2.normal.background = _panelBg; val2.normal.textColor = _textDark; val2.padding = new RectOffset(SInt(10f), SInt(10f), SInt(10f), SInt(10f)); val2.border = new RectOffset(SInt(6f), SInt(6f), SInt(6f), SInt(6f)); Panel = val2; GUIStyle val3 = new GUIStyle { fontSize = Font(22), fontStyle = (FontStyle)1 }; val3.normal.textColor = _woodDark; val3.alignment = (TextAnchor)3; val3.padding = new RectOffset(SInt(5f), SInt(5f), SInt(5f), SInt(5f)); Title = val3; GUIStyle val4 = new GUIStyle { fontSize = Font(15), fontStyle = (FontStyle)1 }; val4.normal.textColor = _woodDark; val4.alignment = (TextAnchor)3; val4.padding = new RectOffset(SInt(4f), SInt(4f), SInt(4f), SInt(4f)); Header = val4; GUIStyle val5 = new GUIStyle { fontSize = Font(12) }; val5.normal.textColor = _textDark; val5.alignment = (TextAnchor)3; val5.padding = new RectOffset(SInt(2f), SInt(2f), SInt(2f), SInt(2f)); Label = val5; LabelBold = new GUIStyle(Label) { fontStyle = (FontStyle)1 }; GUIStyle val6 = new GUIStyle(Label) { fontSize = Font(11), alignment = (TextAnchor)4 }; val6.normal.textColor = _woodMedium; Stats = val6; GUIStyle val7 = new GUIStyle(Label) { fontSize = Font(10), fontStyle = (FontStyle)2 }; val7.normal.textColor = new Color(_textDark.r, _textDark.g, _textDark.b, 0.6f); Footer = val7; GUIStyle val8 = new GUIStyle { fontSize = Font(11), fontStyle = (FontStyle)1 }; val8.normal.background = _buttonNormal; val8.normal.textColor = _textDark; val8.hover.background = _buttonHover; val8.hover.textColor = _textDark; val8.active.background = _buttonActive; val8.active.textColor = _woodDark; val8.alignment = (TextAnchor)4; val8.padding = new RectOffset(SInt(8f), SInt(8f), SInt(4f), SInt(4f)); val8.border = new RectOffset(SInt(4f), SInt(4f), SInt(4f), SInt(4f)); Button = val8; GUIStyle val9 = new GUIStyle { fontSize = Font(12) }; val9.normal.background = _textFieldBg; val9.normal.textColor = _textDark; val9.focused.background = _textFieldBg; val9.focused.textColor = _textDark; val9.padding = new RectOffset(SInt(6f), SInt(6f), SInt(4f), SInt(4f)); val9.border = new RectOffset(SInt(2f), SInt(2f), SInt(2f), SInt(2f)); TextField = val9; GUIStyle val10 = new GUIStyle { fontSize = Font(10) }; val10.normal.background = _tabNormal; val10.normal.textColor = _textDark; val10.hover.background = _buttonHover; val10.hover.textColor = _textDark; val10.active.background = _tabActive; val10.active.textColor = _woodDark; val10.alignment = (TextAnchor)4; val10.padding = new RectOffset(SInt(6f), SInt(6f), SInt(3f), SInt(3f)); val10.margin = new RectOffset(SInt(2f), SInt(2f), 0, 0); val10.border = new RectOffset(SInt(4f), SInt(4f), SInt(4f), SInt(4f)); Tab = val10; GUIStyle val11 = new GUIStyle(Tab); val11.normal.background = _tabActive; val11.normal.textColor = _woodDark; val11.fontStyle = (FontStyle)1; TabActive = val11; Row = new GUIStyle { padding = new RectOffset(SInt(8f), SInt(8f), SInt(6f), SInt(6f)), margin = new RectOffset(0, 0, SInt(2f), SInt(2f)) }; PriorityBadge = new GUIStyle(LabelBold) { fontSize = Font(11), alignment = (TextAnchor)4 }; GUIStyle val12 = new GUIStyle { fontSize = Font(14), fontStyle = (FontStyle)1 }; val12.normal.background = _checkboxNormal; val12.normal.textColor = _woodDark; val12.hover.background = _buttonHover; val12.hover.textColor = _woodDark; val12.onNormal.background = _checkboxChecked; val12.onNormal.textColor = _successGreen; val12.onHover.background = _checkboxChecked; val12.onHover.textColor = _successGreen; val12.onActive.background = _checkboxChecked; val12.onActive.textColor = _woodDark; val12.active.background = _buttonActive; val12.active.textColor = _woodDark; val12.alignment = (TextAnchor)4; val12.clipping = (TextClipping)1; val12.padding = new RectOffset(SInt(2f), SInt(2f), SInt(2f), SInt(2f)); val12.border = new RectOffset(SInt(3f), SInt(3f), SInt(3f), SInt(3f)); Checkbox = val12; } public void Destroy() { DestroyTextures(); } private void DestroyTextures() { DestroyTex(ref _windowBg); DestroyTex(ref _panelBg); DestroyTex(ref _buttonNormal); DestroyTex(ref _buttonHover); DestroyTex(ref _buttonActive); DestroyTex(ref _tabNormal); DestroyTex(ref _tabActive); DestroyTex(ref _textFieldBg); DestroyTex(ref _rowEven); DestroyTex(ref _rowOdd); DestroyTex(ref _rowGifted); DestroyTex(ref _rowSelected); DestroyTex(ref _checkboxNormal); DestroyTex(ref _checkboxChecked); GiftSelectorRowSelected = null; DestroyTex(ref GoldLine); } private static void DestroyTex(ref Texture2D tex) { if ((Object)(object)tex != (Object)null) { Object.Destroy((Object)(object)tex); tex = null; } } private static Texture2D MakeSolid(Color color) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1); val.SetPixel(0, 0, color); val.Apply(); return val; } private static Texture2D MakeGradient(int width, int height, Color top, Color bottom) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_003e: 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) Texture2D val = new Texture2D(width, height); Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < height; i++) { float num = ((height > 1) ? ((float)i / (float)(height - 1)) : 0f); Color val2 = Color.Lerp(top, bottom, num); for (int j = 0; j < width; j++) { array[i * width + j] = val2; } } val.SetPixels(array); val.Apply(); return val; } private static Texture2D MakeRoundedRect(int width, int height, Color fill, Color border, int borderWidth) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_0046: 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_0047: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(width, height); Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { bool flag = j < borderWidth || j >= width - borderWidth || i < borderWidth || i >= height - borderWidth; array[i * width + j] = (flag ? border : fill); } } val.SetPixels(array); val.Apply(); return val; } } } namespace GiftingAssistant.Patches { public static class GiftPatch { public static void OnGiftGiven(object __instance, object __0) { try { if (__instance != null) { Type type = __instance.GetType(); string npcName = (AccessTools.Property(type, "OriginalName") ?? AccessTools.Property(type, "ActualNPCName") ?? AccessTools.Property(type, "NPCName") ?? AccessTools.Property(type, "npcName") ?? AccessTools.Property(type, "Name"))?.GetValue(__instance)?.ToString(); npcName = GiftGameData.NormalizeNpcName(npcName); if (!string.IsNullOrEmpty(npcName)) { Plugin.MarkNpcGifted(npcName); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftingAssistant] Error tracking gift: " + ex.Message)); } } } } public static class PlayerLoadPatch { private static string _loadedCharacterName; public static void OnPlayerInitialized(object __instance) { try { Plugin.EnsureUIComponentsExist(); GiftGameData.InvalidateCache(); string text = GameSaveCharacterName.TryGetCurrent(_loadedCharacterName, delegate(string msg) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[GiftingAssistant] Failed to get character name: " + msg)); } }); if (string.IsNullOrEmpty(text)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[GiftingAssistant] Character name unavailable; skipping roster load."); } return; } if (_loadedCharacterName != text) { Plugin.SaveData(); _loadedCharacterName = text; } Plugin.Instance?.LoadDataForCharacter(text); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("[GiftingAssistant] Error in OnPlayerInitialized: " + ex.Message)); } } } internal static void ResetForMenu() { _loadedCharacterName = null; } } } namespace GiftingAssistant.Integration { public sealed class BirthdayIntegration { private const string PluginGuid = "com.azraelgodking.squirrelsbirthdayreminder"; public bool IsAvailable { get { if (Chainloader.PluginInfos != null) { return Chainloader.PluginInfos.ContainsKey("com.azraelgodking.squirrelsbirthdayreminder"); } return false; } } public HashSet GetTodaysBirthdayNames() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (!IsAvailable) { return hashSet; } try { if (!Chainloader.PluginInfos.TryGetValue("com.azraelgodking.squirrelsbirthdayreminder", out var value) || (Object)(object)((value != null) ? value.Instance : null) == (Object)null) { return hashSet; } object obj = AccessTools.Method(((object)value.Instance).GetType(), "GetManager", Type.EmptyTypes, (Type[])null)?.Invoke(null, null); if (obj == null) { return hashSet; } Type type = obj.GetType(); object obj2 = AccessTools.Property(type, "HasBirthdays")?.GetValue(obj); if (obj2 is bool && !(bool)obj2) { return hashSet; } if (!(AccessTools.Property(type, "TodaysBirthdays")?.GetValue(obj) is IList list)) { return hashSet; } foreach (object item in list) { if (item != null) { string text = AccessTools.Property(item.GetType(), "NPCName")?.GetValue(item) as string; if (!string.IsNullOrEmpty(text)) { hashSet.Add(text); } } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftingAssistant] Birthday integration: " + ex.Message)); } } return hashSet; } } public sealed class GiftTodoRequest { public string NpcName { get; } public int IconItemId { get; } public string GiftNames { get; } public GiftTodoRequest(string npcName, int iconItemId, string giftNames) { NpcName = npcName; IconItemId = iconItemId; GiftNames = giftNames ?? ""; } } public sealed class TodoIntegration { private sealed class TodoContext { public object PluginInstance; public Type PluginType; public object Manager; public Type ManagerType; public Assembly Asm; public Type TodoItemType; public Type TodoPriorityType; public Type TodoCategoryType; public MethodInfo AddTodoMethod; public MethodInfo RemoveTodoMethod; public MethodInfo ToggleCompleteMethod; public MethodInfo GetTodoByIdMethod; public MethodInfo GetActiveTodosMethod; public MethodInfo GetAllTodosMethod; } private const string PluginGuid = "com.azraelgodking.sunhaventodo"; private const string AutoMarker = "Auto-generated by Gifting Assistant"; private const string TitlePrefix = "Gift "; private const string TitleSuffix = " today"; private bool _availableCached; private readonly Dictionary _todoIdsByNpc = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly HashSet _pendingPublish = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _pendingComplete = new Dictionary(StringComparer.OrdinalIgnoreCase); private readonly HashSet _pendingRemove = new HashSet(StringComparer.OrdinalIgnoreCase); private bool _pendingEnsureAll; private bool _pendingRefreshDaily; private bool _flushScheduled; private TodoContext _cachedContext; private bool _contextValid; private bool _warnedNoType; private bool _warnedNoManager; private bool _warnedNoData; public bool IsAvailable { get { if (_availableCached) { return true; } _availableCached = Chainloader.PluginInfos != null && Chainloader.PluginInfos.ContainsKey("com.azraelgodking.sunhaventodo"); return _availableCached; } } public bool IsEnabled { get; set; } public bool CanPushTodos { get { if (IsAvailable) { return IsEnabled; } return false; } } public void QueuePublish(GiftRosterEntry entry) { if (CanPushTodos && entry != null && !string.IsNullOrEmpty(entry.NpcName)) { _pendingPublish.Add(entry.NpcName); _pendingRemove.Remove(entry.NpcName); ScheduleFlush(); } } public void QueueComplete(string npcName, bool completed) { if (CanPushTodos && !string.IsNullOrEmpty(npcName)) { _pendingComplete[npcName] = completed; ScheduleFlush(); } } public void QueueRemove(string npcName) { if (CanPushTodos && !string.IsNullOrEmpty(npcName)) { _pendingRemove.Add(npcName); _pendingPublish.Remove(npcName); _pendingComplete.Remove(npcName); ScheduleFlush(); } } public void QueueEnsureAll() { if (CanPushTodos) { _pendingEnsureAll = true; ScheduleFlush(); } } public void QueueRefreshDaily() { if (CanPushTodos) { _pendingRefreshDaily = true; ScheduleFlush(); } } public void PublishGiftTodo(GiftTodoRequest request) { if (request != null && !string.IsNullOrEmpty(request.NpcName)) { _pendingPublish.Add(request.NpcName); ScheduleFlush(); } } public void EnsureGiftTodos(IList requests) { QueueEnsureAll(); } public void RefreshDailyGiftTodos(IList requests) { QueueRefreshDaily(); } public void CompleteGiftTodo(string npcName, bool completed) { QueueComplete(npcName, completed); } public void ResetTracking() { _todoIdsByNpc.Clear(); _pendingPublish.Clear(); _pendingComplete.Clear(); _pendingRemove.Clear(); _pendingEnsureAll = false; _pendingRefreshDaily = false; _flushScheduled = false; InvalidateContext(); } public void ProcessPending() { if (!_flushScheduled || !CanPushTodos) { return; } _flushScheduled = false; if (_pendingRemove.Count == 0 && _pendingPublish.Count == 0 && _pendingComplete.Count == 0 && !_pendingEnsureAll && !_pendingRefreshDaily) { return; } try { TodoContext todoContext = TryGetContext(); if (todoContext == null) { ScheduleFlush(); return; } bool flag = false; if (_pendingRefreshDaily) { _pendingRefreshDaily = false; flag |= RemoveAllGiftTodos(todoContext); _todoIdsByNpc.Clear(); flag |= EnsureAllFromRoster(todoContext); } else { if (_pendingEnsureAll) { _pendingEnsureAll = false; flag |= EnsureAllFromRoster(todoContext); } foreach (string item in _pendingRemove) { flag |= RemoveGiftTodoForNpc(todoContext, item); } _pendingRemove.Clear(); foreach (string item2 in _pendingPublish) { flag |= RemoveGiftTodoForNpc(todoContext, item2); flag |= PublishForNpc(todoContext, item2, skipIfExists: false); } _pendingPublish.Clear(); foreach (KeyValuePair item3 in _pendingComplete) { flag |= CompleteForNpc(todoContext, item3.Key, item3.Value); } _pendingComplete.Clear(); } if (flag) { TrySaveTodoData(todoContext.PluginType); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[GiftingAssistant] Todo flush failed: " + ex.Message)); } ScheduleFlush(); } } private void ScheduleFlush() { _flushScheduled = true; } private void InvalidateContext() { _contextValid = false; _cachedContext = null; } private bool EnsureAllFromRoster(TodoContext ctx) { bool flag = false; GiftRosterManager manager = Plugin.GetManager(); if (manager == null) { return false; } foreach (GiftRosterEntry entry in manager.GetEntries()) { if (entry != null && !string.IsNullOrEmpty(entry.NpcName)) { flag |= PublishForNpc(ctx, entry.NpcName); } } return flag; } private bool PublishForNpc(TodoContext ctx, string npcName, bool skipIfExists = true) { GiftRosterEntry giftRosterEntry = Plugin.GetManager()?.GetEntry(npcName); if (giftRosterEntry == null) { return false; } GiftSuggestionResolver.RefreshPrimaryIconForNpc(npcName); int iconItemId = GiftSuggestionResolver.ResolvePrimaryIconId(giftRosterEntry, forceRefresh: true); string giftNames = GiftSuggestionResolver.ResolveGiftNames(giftRosterEntry, 3); return AddGiftTodo(ctx, new GiftTodoRequest(giftRosterEntry.NpcName, iconItemId, giftNames), skipIfExists); } private bool CompleteForNpc(TodoContext ctx, string npcName, bool completed) { string text = GiftGameData.NormalizeNpcName(npcName); if (string.IsNullOrEmpty(text)) { return false; } string text2 = FindTodoIdForNpc(ctx, text, npcName); if (string.IsNullOrEmpty(text2)) { return false; } object obj = ctx.GetTodoByIdMethod?.Invoke(ctx.Manager, new object[1] { text2 }); if (obj == null) { _todoIdsByNpc.Remove(text); return false; } if (GetTodoCompleted(obj) == completed) { return false; } ctx.ToggleCompleteMethod?.Invoke(ctx.Manager, new object[1] { text2 }); return true; } private bool RemoveGiftTodoForNpc(TodoContext ctx, string npcName) { string text = GiftGameData.NormalizeNpcName(npcName); string text2 = FindTodoIdForNpc(ctx, text, npcName); if (string.IsNullOrEmpty(text2)) { return false; } ctx.RemoveTodoMethod?.Invoke(ctx.Manager, new object[1] { text2 }); _todoIdsByNpc.Remove(text); return true; } private string FindTodoIdForNpc(TodoContext ctx, string normalized, string displayName) { if (_todoIdsByNpc.TryGetValue(normalized, out string value) && !string.IsNullOrEmpty(value)) { object obj = ctx.GetTodoByIdMethod?.Invoke(ctx.Manager, new object[1] { value }); if (obj != null && IsOurGiftTodoForNpc(obj, displayName)) { return value; } _todoIdsByNpc.Remove(normalized); } string title = BuildTitle(displayName); string text = FindOurGiftTodoIdByTitle(ctx, title); if (!string.IsNullOrEmpty(text)) { _todoIdsByNpc[normalized] = text; } return text; } private string FindOurGiftTodoIdByTitle(TodoContext ctx, string title) { try { foreach (object item in EnumerateAllTodos(ctx)) { if (item != null && IsOurTodo(item) && string.Equals(GetTodoTitle(item), title, StringComparison.OrdinalIgnoreCase)) { return GetTodoId(item); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftingAssistant] FindOurGiftTodoIdByTitle: " + ex.Message)); } } return null; } private static string FindActiveTodoIdByTitle(TodoContext ctx, string title) { try { if (ctx.GetActiveTodosMethod?.Invoke(ctx.Manager, null) is IEnumerable enumerable) { foreach (object item in enumerable) { if (item != null && string.Equals(GetTodoTitle(item), title, StringComparison.OrdinalIgnoreCase)) { return GetTodoId(item); } } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftingAssistant] FindActiveTodoIdByTitle: " + ex.Message)); } } return null; } private static bool IsOurGiftTodoForNpc(object todo, string npcName) { if (!IsOurTodo(todo)) { return false; } return string.Equals(GetTodoTitle(todo), BuildTitle(npcName), StringComparison.OrdinalIgnoreCase); } private static string BuildTitle(string npcName) { return "Gift " + npcName + " today"; } private static string BuildDescription(string giftNames) { if (!string.IsNullOrEmpty(giftNames)) { return "Auto-generated by Gifting Assistant. Preferred gifts: " + giftNames; } return "Auto-generated by Gifting Assistant"; } private static string ExtractNpcFromTitle(string title) { if (string.IsNullOrEmpty(title)) { return null; } if (!title.StartsWith("Gift ", StringComparison.OrdinalIgnoreCase) || !title.EndsWith(" today", StringComparison.OrdinalIgnoreCase)) { return null; } int length = "Gift ".Length; int num = title.Length - "Gift ".Length - " today".Length; if (num <= 0) { return null; } return title.Substring(length, num); } private bool AddGiftTodo(TodoContext ctx, GiftTodoRequest request, bool skipIfExists) { string text = BuildTitle(request.NpcName); if (skipIfExists && HasActiveTodoWithTitle(ctx, text)) { CacheTodoIdFromTitle(ctx, request.NpcName, text); return false; } Type todoItemType = ctx.TodoItemType; Type todoPriorityType = ctx.TodoPriorityType; Type todoCategoryType = ctx.TodoCategoryType; if (todoItemType == null || todoPriorityType == null || todoCategoryType == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[GiftingAssistant] Todo integration: Todo types not found"); } return false; } object obj = Activator.CreateInstance(todoItemType); AccessTools.Property(todoItemType, "Title")?.SetValue(obj, text); AccessTools.Property(todoItemType, "Description")?.SetValue(obj, BuildDescription(request.GiftNames)); AccessTools.Property(todoItemType, "Priority")?.SetValue(obj, Enum.Parse(todoPriorityType, "Normal")); AccessTools.Property(todoItemType, "Category")?.SetValue(obj, Enum.Parse(todoCategoryType, "Social")); if (request.IconItemId > 0) { AccessTools.Property(todoItemType, "IconItemId")?.SetValue(obj, request.IconItemId); } if (ctx.AddTodoMethod == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[GiftingAssistant] Todo integration: AddTodo(TodoItem) not found"); } return false; } ctx.AddTodoMethod.Invoke(ctx.Manager, new object[1] { obj }); string todoId = GetTodoId(obj); if (!string.IsNullOrEmpty(todoId)) { string text2 = GiftGameData.NormalizeNpcName(request.NpcName); if (!string.IsNullOrEmpty(text2)) { _todoIdsByNpc[text2] = todoId; } } ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)("[GiftingAssistant] Pushed gift todo for " + request.NpcName)); } return true; } private void CacheTodoIdFromTitle(TodoContext ctx, string npcName, string title) { string value = FindOurGiftTodoIdByTitle(ctx, title); if (!string.IsNullOrEmpty(value)) { string text = GiftGameData.NormalizeNpcName(npcName); if (!string.IsNullOrEmpty(text)) { _todoIdsByNpc[text] = value; } } } private bool RemoveAllGiftTodos(TodoContext ctx) { if (ctx.RemoveTodoMethod == null) { return false; } List list = new List(); foreach (object item in EnumerateAllTodos(ctx)) { if (item != null && IsOurTodo(item)) { string todoId = GetTodoId(item); if (!string.IsNullOrEmpty(todoId)) { list.Add(todoId); } } } foreach (string item2 in list) { ctx.RemoveTodoMethod.Invoke(ctx.Manager, new object[1] { item2 }); } return list.Count > 0; } private IEnumerable EnumerateAllTodos(TodoContext ctx) { if (ctx.GetAllTodosMethod == null || !(ctx.GetAllTodosMethod.Invoke(ctx.Manager, null) is IEnumerable enumerable)) { yield break; } List list = new List(); foreach (object item in enumerable) { list.Add(item); } foreach (object item2 in list) { yield return item2; } } private static bool IsOurTodo(object todo) { string text = AccessTools.Property(todo.GetType(), "Description")?.GetValue(todo) as string; if (!string.IsNullOrEmpty(text)) { return text.IndexOf("Auto-generated by Gifting Assistant", StringComparison.OrdinalIgnoreCase) >= 0; } return false; } private static string GetTodoTitle(object todo) { return AccessTools.Property(todo.GetType(), "Title")?.GetValue(todo) as string; } private static string GetTodoId(object todo) { return AccessTools.Property(todo.GetType(), "Id")?.GetValue(todo) as string; } private static bool GetTodoCompleted(object todo) { object obj = AccessTools.Property(todo.GetType(), "IsCompleted")?.GetValue(todo); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } private bool HasActiveTodoWithTitle(TodoContext ctx, string title) { return !string.IsNullOrEmpty(FindActiveTodoIdByTitle(ctx, title)); } private TodoContext TryGetContext() { if (_contextValid && _cachedContext != null) { if (_cachedContext.Manager != null) { object manager = _cachedContext.Manager; Object val = (Object)((manager is Object) ? manager : null); if (val == null || !(val == (Object)null)) { return _cachedContext; } } InvalidateContext(); } Type type = AccessTools.TypeByName("SunhavenTodo.Plugin"); object obj = null; if (Chainloader.PluginInfos != null && Chainloader.PluginInfos.TryGetValue("com.azraelgodking.sunhaventodo", out var value) && value != null) { BaseUnityPlugin instance = value.Instance; if ((Object)(object)instance != (Object)null) { obj = instance; if (type == null) { type = ((object)instance).GetType(); } } } if (type == null) { if (!_warnedNoType) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[GiftingAssistant] Todo integration: SunhavenTodo.Plugin type not found"); } _warnedNoType = true; } return null; } _warnedNoType = false; object obj2 = AccessTools.Method(type, "GetTodoManager", Type.EmptyTypes, (Type[])null)?.Invoke(null, null); if (obj2 == null) { if (!_warnedNoManager) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[GiftingAssistant] Todo integration: TodoManager unavailable"); } _warnedNoManager = true; } return null; } _warnedNoManager = false; Type type2 = obj2.GetType(); if (!EnsureTodoDataLoaded(obj, type, obj2, type2)) { return null; } Assembly assembly = type.Assembly; _cachedContext = new TodoContext { PluginInstance = obj, PluginType = type, Manager = obj2, ManagerType = type2, Asm = assembly, TodoItemType = assembly.GetType("SunhavenTodo.Data.TodoItem"), TodoPriorityType = assembly.GetType("SunhavenTodo.Data.TodoPriority"), TodoCategoryType = assembly.GetType("SunhavenTodo.Data.TodoCategory"), AddTodoMethod = AccessTools.Method(type2, "AddTodo", new Type[1] { assembly.GetType("SunhavenTodo.Data.TodoItem") }, (Type[])null), RemoveTodoMethod = AccessTools.Method(type2, "RemoveTodo", new Type[1] { typeof(string) }, (Type[])null), ToggleCompleteMethod = AccessTools.Method(type2, "ToggleComplete", new Type[1] { typeof(string) }, (Type[])null), GetTodoByIdMethod = AccessTools.Method(type2, "GetTodoById", new Type[1] { typeof(string) }, (Type[])null), GetActiveTodosMethod = AccessTools.Method(type2, "GetActiveTodos", Type.EmptyTypes, (Type[])null), GetAllTodosMethod = AccessTools.Method(type2, "GetAllTodos", Type.EmptyTypes, (Type[])null) }; _contextValid = true; return _cachedContext; } private bool EnsureTodoDataLoaded(object todoPluginInstance, Type pluginType, object manager, Type managerType) { if (HasTodoDataLoaded(manager, managerType)) { _warnedNoData = false; return true; } string text = Plugin.GetManager()?.CurrentCharacter; if (string.IsNullOrEmpty(text)) { text = GameSaveCharacterName.TryGetCurrent(null, delegate(string msg) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)("[GiftingAssistant] Todo integration: character lookup failed: " + msg)); } }); } if (string.IsNullOrEmpty(text)) { if (!_warnedNoData) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[GiftingAssistant] Todo integration: no character loaded in Sun Haven Todo or Gifting Assistant yet"); } _warnedNoData = true; } return false; } if (todoPluginInstance == null) { if (!_warnedNoData) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[GiftingAssistant] Todo integration: Sun Haven Todo data not loaded and its plugin instance is unavailable; will retry once data is loaded"); } _warnedNoData = true; } return false; } MethodInfo methodInfo = AccessTools.Method(pluginType, "LoadDataForCharacter", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null) { if (!_warnedNoData) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)"[GiftingAssistant] Todo integration: LoadDataForCharacter not found"); } _warnedNoData = true; } return false; } methodInfo.Invoke(todoPluginInstance, new object[1] { text }); if (!HasTodoDataLoaded(manager, managerType)) { if (!_warnedNoData) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)("[GiftingAssistant] Todo integration: failed to load todo data for character \"" + text + "\"")); } _warnedNoData = true; } return false; } _warnedNoData = false; return true; } private static bool HasTodoDataLoaded(object manager, Type managerType) { if (string.IsNullOrEmpty(AccessTools.Property(managerType, "CurrentCharacter")?.GetValue(manager) as string)) { return false; } return AccessTools.Method(managerType, "GetData", Type.EmptyTypes, (Type[])null)?.Invoke(manager, null) != null; } private static void TrySaveTodoData(Type pluginType) { try { AccessTools.Method(pluginType, "SaveData", Type.EmptyTypes, (Type[])null)?.Invoke(null, null); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[GiftingAssistant] Todo integration: SaveData failed: " + ex.Message)); } } } } } namespace GiftingAssistant.Game { public sealed class GiftNpcInfo { public string Name { get; } public List LovedItemIds { get; } = new List(); public List LikedItemIds { get; } = new List(); public GiftNpcInfo(string name) { Name = name; } } public static class GiftGameData { private static bool _reflectionInitialized; private static Type _npcManagerType; private static PropertyInfo _npcManagerInstanceProp; private static FieldInfo _npcsDictField; private static FieldInfo _love2Field; private static FieldInfo _like2Field; private static FieldInfo _giftTableField; private static FieldInfo _gaveGiftForDayField; private static bool _useGameData; private static MethodInfo _tryGetValueMethod; private static Type _tryGetValueDictType; private static PropertyInfo[] _npcNameProps; private static List _cachedNpcs; private static Dictionary _npcByNormalizedName; private static Dictionary _giftedTodayCache; private static bool _giftedTodayCacheValid; private static Dictionary _relationshipCache; private static bool _relationshipCacheValid; private static PropertyInfo _gameSaveCurrentCharacterProp; private static PropertyInfo _gameSaveInstanceProp; private static PropertyInfo _gameSaveCurrentSaveProp; private static PropertyInfo _saveCharacterDataProp; private static PropertyInfo _characterDataRelationshipsProp; private static bool _relationshipApiInitialized; private static bool _npcCacheBuildScheduled; public static bool IsNpcCacheReady { get { if (_cachedNpcs != null) { return _cachedNpcs.Count > 0; } return false; } } public static void ScheduleNpcCacheWarm() { _npcCacheBuildScheduled = true; } public static void ProcessDeferredNpcCache() { if (_npcCacheBuildScheduled || !IsNpcCacheReady) { _npcCacheBuildScheduled = false; BuildNpcCache(); } } public static void EnsureNpcCacheReady() { if (!IsNpcCacheReady) { BuildNpcCache(); } } private static void InitializeReflection() { if (_reflectionInitialized) { return; } _reflectionInitialized = true; try { _npcManagerType = AccessTools.TypeByName("Wish.NPCManager"); if (_npcManagerType == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[GiftGameData] Wish.NPCManager not found - NPC data unavailable"); } return; } Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type != null) { _npcManagerInstanceProp = type.MakeGenericType(_npcManagerType).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); } _npcsDictField = AccessTools.Field(_npcManagerType, "_npcs") ?? AccessTools.Field(_npcManagerType, "npcs"); Type type2 = AccessTools.TypeByName("Wish.NPCGiftTable"); if (type2 != null) { _love2Field = AccessTools.Field(type2, "love2"); _like2Field = AccessTools.Field(type2, "like2"); } Type type3 = AccessTools.TypeByName("Wish.NPCAI"); if (type3 != null) { _giftTableField = AccessTools.Field(type3, "giftTable"); _gaveGiftForDayField = AccessTools.Field(type3, "gaveGiftForDay"); _npcNameProps = new PropertyInfo[5] { AccessTools.Property(type3, "OriginalName"), AccessTools.Property(type3, "NPCName"), AccessTools.Property(type3, "ActualNPCName"), AccessTools.Property(type3, "npcName"), AccessTools.Property(type3, "Name") }; } InitializeRelationshipReflection(); _useGameData = _npcManagerInstanceProp != null && _npcsDictField != null && _giftTableField != null; if (_useGameData) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[GiftGameData] Game NPC/gift data API initialized"); } } else { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)"[GiftGameData] Game NPC/gift data API unavailable - check game version"); } } } catch (Exception ex) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)("[GiftGameData] Error initializing reflection: " + ex.Message)); } _useGameData = false; } } public static void InvalidateCache() { _cachedNpcs = null; _npcByNormalizedName = null; InvalidateGiftedTodayCache(); InvalidateRelationshipCache(); } private static void InitializeRelationshipReflection() { if (_relationshipApiInitialized) { return; } _relationshipApiInitialized = true; try { Type type = AccessTools.TypeByName("Wish.GameSave"); if (!(type == null)) { _gameSaveCurrentCharacterProp = AccessTools.Property(type, "CurrentCharacter"); Type type2 = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type2 != null) { _gameSaveInstanceProp = type2.MakeGenericType(type).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); } _gameSaveCurrentSaveProp = AccessTools.Property(type, "CurrentSave"); Type type3 = AccessTools.TypeByName("Wish.CharacterData"); if (type3 != null) { _characterDataRelationshipsProp = AccessTools.Property(type3, "Relationships"); } Type type4 = _gameSaveCurrentSaveProp?.PropertyType; if (type4 != null) { _saveCharacterDataProp = AccessTools.Property(type4, "characterData"); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] Relationship reflection init: " + ex.Message)); } } } public static void InvalidateRelationshipCache() { _relationshipCacheValid = false; } public static void RefreshRelationshipCache() { InitializeReflection(); InitializeRelationshipReflection(); if (_relationshipCache == null) { _relationshipCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } else { _relationshipCache.Clear(); } Dictionary dictionary = ReadRelationshipsDictionary(); if (dictionary == null) { _relationshipCacheValid = true; return; } try { foreach (KeyValuePair item in dictionary) { string text = NormalizeNpcName(item.Key); if (!string.IsNullOrEmpty(text)) { _relationshipCache[text] = item.Value; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] RefreshRelationshipCache: " + ex.Message)); } } _relationshipCacheValid = true; } public static float? GetRelationshipPoints(string npcName) { if (string.IsNullOrEmpty(npcName)) { return null; } string key = NormalizeNpcName(npcName); if (!_relationshipCacheValid || _relationshipCache == null || !_relationshipCache.TryGetValue(key, out var value)) { return null; } return value; } public static string FormatRelationshipHearts(float points) { int maxHeartsForPoints = GetMaxHeartsForPoints(points); int num = Mathf.Clamp(Mathf.FloorToInt(points / 5f), 0, maxHeartsForPoints); int num2 = maxHeartsForPoints - num; StringBuilder stringBuilder = new StringBuilder(maxHeartsForPoints); for (int i = 0; i < num; i++) { stringBuilder.Append('♥'); } for (int j = 0; j < num2; j++) { stringBuilder.Append('♡'); } return stringBuilder.ToString(); } public static int GetMaxHeartsForPoints(float points) { if (points >= 75f) { return 20; } if (points >= 50f) { return 15; } return 10; } public static void InvalidateGiftedTodayCache() { _giftedTodayCacheValid = false; } public static void RefreshGiftedTodayCache() { InitializeReflection(); if (_giftedTodayCache == null) { _giftedTodayCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } else { _giftedTodayCache.Clear(); } if (!_useGameData || _gaveGiftForDayField == null) { _giftedTodayCacheValid = true; return; } try { object obj = _npcManagerInstanceProp?.GetValue(null); object obj2 = ((obj == null) ? null : _npcsDictField?.GetValue(obj)); if (obj2 == null) { _giftedTodayCacheValid = true; return; } if (!(obj2.GetType().GetProperty("Values")?.GetValue(obj2) is IEnumerable enumerable)) { _giftedTodayCacheValid = true; return; } foreach (object item in enumerable) { if (item == null) { continue; } try { string text = ReadNpcName(item); if (!string.IsNullOrEmpty(text)) { string text2 = NormalizeNpcName(text); if (!string.IsNullOrEmpty(text2)) { bool value = (bool)_gaveGiftForDayField.GetValue(item); _giftedTodayCache[text2] = value; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] RefreshGiftedTodayCache NPC: " + ex.Message)); } } } } catch (Exception ex2) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("[GiftGameData] RefreshGiftedTodayCache: " + ex2.Message)); } } _giftedTodayCacheValid = true; } public static void SetGiftedToday(string npcName, bool gifted) { if (string.IsNullOrEmpty(npcName)) { return; } string text = NormalizeNpcName(npcName); if (!string.IsNullOrEmpty(text)) { if (_giftedTodayCache == null) { _giftedTodayCache = new Dictionary(StringComparer.OrdinalIgnoreCase); } _giftedTodayCache[text] = gifted; _giftedTodayCacheValid = true; } } public static IReadOnlyList GetAllNpcs() { if (_cachedNpcs != null) { return _cachedNpcs; } return Array.Empty(); } public static GiftNpcInfo GetNpcInfo(string npcName) { if (string.IsNullOrEmpty(npcName)) { return null; } string key = NormalizeNpcName(npcName); if (_npcByNormalizedName != null && _npcByNormalizedName.TryGetValue(key, out GiftNpcInfo value)) { return value; } return null; } private static void BuildNpcCache() { InitializeReflection(); List list = new List(); Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); if (!_useGameData) { _cachedNpcs = list; _npcByNormalizedName = dictionary; return; } try { object obj = _npcManagerInstanceProp?.GetValue(null); if (obj == null) { _cachedNpcs = list; _npcByNormalizedName = dictionary; return; } object obj2 = _npcsDictField?.GetValue(obj); if (obj2 == null) { _cachedNpcs = list; _npcByNormalizedName = dictionary; return; } if (!(obj2.GetType().GetProperty("Values")?.GetValue(obj2) is IEnumerable enumerable)) { _cachedNpcs = list; _npcByNormalizedName = dictionary; return; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (object item in enumerable) { if (item == null) { continue; } try { string text = ReadNpcName(item); if (string.IsNullOrEmpty(text)) { continue; } string text2 = NormalizeNpcName(text); if (!string.IsNullOrEmpty(text2) && hashSet.Add(text2)) { object obj3 = _giftTableField?.GetValue(item); if (obj3 != null) { GiftNpcInfo giftNpcInfo = new GiftNpcInfo(text2); AddItemIds(_love2Field?.GetValue(obj3) as IList, giftNpcInfo.LovedItemIds); AddItemIds(_like2Field?.GetValue(obj3) as IList, giftNpcInfo.LikedItemIds); list.Add(giftNpcInfo); dictionary[text2] = giftNpcInfo; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] Error reading NPC: " + ex.Message)); } } } list.Sort((GiftNpcInfo a, GiftNpcInfo b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); } catch (Exception ex2) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[GiftGameData] Error reading NPC list: " + ex2.Message)); } } _cachedNpcs = list; _npcByNormalizedName = dictionary; } public static bool HasGivenGiftToday(string npcName) { if (string.IsNullOrEmpty(npcName)) { return false; } string key = NormalizeNpcName(npcName); if (_giftedTodayCacheValid && _giftedTodayCache != null && _giftedTodayCache.TryGetValue(key, out var value)) { return value; } return false; } public static string NormalizeNpcName(string npcName) { if (string.IsNullOrWhiteSpace(npcName)) { return ""; } List list = (from p in npcName.Split(new char[1] { '+' }, StringSplitOptions.RemoveEmptyEntries) select p.Trim() into p where !string.IsNullOrEmpty(p) select p).Distinct(StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { return npcName.Trim(); } if (list.Count == 1) { return list[0]; } return string.Join("+", list); } private static string ReadNpcName(object npc) { if (npc == null) { return null; } if (_npcNameProps != null) { PropertyInfo[] npcNameProps = _npcNameProps; foreach (PropertyInfo propertyInfo in npcNameProps) { if (!(propertyInfo == null)) { object value = propertyInfo.GetValue(npc); if (value != null) { return value.ToString(); } } } } Type type = npc.GetType(); return (AccessTools.Property(type, "NPCName") ?? AccessTools.Property(type, "ActualNPCName") ?? AccessTools.Property(type, "OriginalName") ?? AccessTools.Property(type, "npcName") ?? AccessTools.Property(type, "Name"))?.GetValue(npc)?.ToString(); } private static MethodInfo GetTryGetValueMethod(object npcsDict) { if (npcsDict == null) { return null; } Type type = npcsDict.GetType(); if (_tryGetValueMethod != null && _tryGetValueDictType == type) { return _tryGetValueMethod; } _tryGetValueDictType = type; _tryGetValueMethod = type.GetMethod("TryGetValue"); return _tryGetValueMethod; } private static object FindNpc(object npcsDict, string name) { if (npcsDict == null || string.IsNullOrEmpty(name)) { return null; } string text = NormalizeNpcName(name); MethodInfo tryGetValueMethod = GetTryGetValueMethod(npcsDict); if (tryGetValueMethod != null) { string[] array = new string[2] { name, text }; foreach (string text2 in array) { if (!string.IsNullOrEmpty(text2)) { object[] array2 = new object[2] { text2, null }; if ((bool)tryGetValueMethod.Invoke(npcsDict, array2) && array2[1] != null) { return array2[1]; } } } } if (npcsDict is IDictionary dictionary) { foreach (DictionaryEntry item in dictionary) { string text3 = item.Key?.ToString(); if (!string.IsNullOrEmpty(text3) && string.Equals(NormalizeNpcName(text3), text, StringComparison.OrdinalIgnoreCase)) { return item.Value; } string text4 = ReadNpcName(item.Value); if (!string.IsNullOrEmpty(text4) && string.Equals(NormalizeNpcName(text4), text, StringComparison.OrdinalIgnoreCase)) { return item.Value; } } } return null; } private static void AddItemIds(IList source, List destination) { if (source == null) { return; } foreach (object item in source) { int num = ExtractItemId(item); if (num > 0 && !destination.Contains(num)) { destination.Add(num); } } } private static int ExtractItemId(object element) { try { if (element == null) { return -1; } if (element is int result) { return result; } Type type = element.GetType(); FieldInfo field = type.GetField("id", BindingFlags.Instance | BindingFlags.Public); if (field != null) { object value = field.GetValue(element); return (value is int num) ? num : Convert.ToInt32(value); } PropertyInfo propertyInfo = type.GetProperty("id") ?? type.GetProperty("ID"); if (propertyInfo != null) { object value2 = propertyInfo.GetValue(element); return (value2 is int num2) ? num2 : Convert.ToInt32(value2); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] ExtractItemId: " + ex.Message)); } } return -1; } private static Dictionary ReadRelationshipsDictionary() { if (_characterDataRelationshipsProp == null) { return null; } try { object obj = null; object obj2 = _gameSaveCurrentCharacterProp?.GetValue(null); if (obj2 != null) { obj = obj2; } else { object obj3 = _gameSaveInstanceProp?.GetValue(null); object obj4 = ((obj3 == null) ? null : _gameSaveCurrentSaveProp?.GetValue(obj3)); obj = ((obj4 == null) ? null : _saveCharacterDataProp?.GetValue(obj4)); } if (obj == null) { return null; } object value = _characterDataRelationshipsProp.GetValue(obj); if (value == null) { return null; } if (value is Dictionary result) { return result; } if (value is IDictionary dictionary) { Dictionary dictionary2 = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry item in dictionary) { string text = item.Key?.ToString(); if (!string.IsNullOrEmpty(text) && item.Value != null) { dictionary2[text] = ((item.Value is float num) ? num : Convert.ToSingle(item.Value)); } } return dictionary2; } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[GiftGameData] ReadRelationshipsDictionary: " + ex.Message)); } } return null; } } public static class GiftSuggestionResolver { private sealed class IconPickCacheEntry { public string Signature; public int IconId; } private static readonly Dictionary PrimaryIconCache = new Dictionary(StringComparer.OrdinalIgnoreCase); public static void ClearPrimaryIconCache() { PrimaryIconCache.Clear(); } public static void RefreshPrimaryIconForNpc(string npcName) { string text = GiftGameData.NormalizeNpcName(npcName); if (!string.IsNullOrEmpty(text)) { PrimaryIconCache.Remove(text); } } public static List ResolveDisplayGiftIds(GiftRosterEntry entry) { List list = new List(); if (entry?.PreferredGiftIds == null || entry.PreferredGiftIds.Count == 0) { return list; } foreach (int preferredGiftId in entry.PreferredGiftIds) { if (preferredGiftId > 0 && !list.Contains(preferredGiftId)) { list.Add(preferredGiftId); } } return list; } public static List ResolveGiftIds(GiftRosterEntry entry) { List list = new List(); if (entry == null) { return list; } if (entry.PreferredGiftIds != null && entry.PreferredGiftIds.Count > 0) { foreach (int preferredGiftId in entry.PreferredGiftIds) { if (preferredGiftId > 0 && !list.Contains(preferredGiftId)) { list.Add(preferredGiftId); } } return list; } GiftNpcInfo npcInfo = GiftGameData.GetNpcInfo(entry.NpcName); if (npcInfo != null) { AddRange(list, npcInfo.LovedItemIds); AddRange(list, npcInfo.LikedItemIds); } return list; } public static int ResolvePrimaryIconId(GiftRosterEntry entry, bool forceRefresh = false) { if (entry == null || string.IsNullOrEmpty(entry.NpcName)) { return -1; } string text = GiftGameData.NormalizeNpcName(entry.NpcName); if (string.IsNullOrEmpty(text)) { return -1; } string text2 = BuildGiftSignature(entry); if (!forceRefresh && PrimaryIconCache.TryGetValue(text, out IconPickCacheEntry value) && value.Signature == text2) { return value.IconId; } int num = PickRandomIconId(entry); PrimaryIconCache[text] = new IconPickCacheEntry { Signature = text2, IconId = num }; return num; } public static string ResolveGiftNames(GiftRosterEntry entry, int max) { List list = ResolveGiftIds(entry); if (list.Count == 0) { return ""; } StringBuilder stringBuilder = new StringBuilder(); int num = ((list.Count < max) ? list.Count : max); for (int i = 0; i < num; i++) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(ItemSearch.GetItemName(list[i]) ?? $"#{list[i]}"); } if (list.Count > num) { stringBuilder.Append(", ..."); } return stringBuilder.ToString(); } private static string BuildGiftSignature(GiftRosterEntry entry) { StringBuilder stringBuilder = new StringBuilder(); if (entry.PreferredGiftIds != null && entry.PreferredGiftIds.Count > 0) { stringBuilder.Append("p:"); AppendIds(stringBuilder, entry.PreferredGiftIds); } else { stringBuilder.Append("a:"); GiftNpcInfo npcInfo = GiftGameData.GetNpcInfo(entry.NpcName); if (npcInfo != null) { AppendIds(stringBuilder, npcInfo.LovedItemIds); stringBuilder.Append('|'); AppendIds(stringBuilder, npcInfo.LikedItemIds); } } return stringBuilder.ToString(); } private static void AppendIds(StringBuilder sb, IList ids) { if (ids == null) { return; } foreach (int id in ids) { if (id > 0) { sb.Append(id); sb.Append(','); } } } private static int PickRandomIconId(GiftRosterEntry entry) { if (entry.PreferredGiftIds != null && entry.PreferredGiftIds.Count > 0) { int num = PickRandomFromList(entry.PreferredGiftIds); if (num > 0) { return num; } } GiftNpcInfo npcInfo = GiftGameData.GetNpcInfo(entry.NpcName); if (npcInfo == null) { return -1; } int num2 = PickRandomFromList(npcInfo.LovedItemIds); if (num2 > 0) { return num2; } return PickRandomFromList(npcInfo.LikedItemIds); } private static int PickRandomFromList(IList ids) { if (ids == null || ids.Count == 0) { return -1; } List list = new List(); foreach (int id in ids) { if (id > 0 && !list.Contains(id)) { list.Add(id); } } if (list.Count == 0) { return -1; } if (list.Count == 1) { return list[0]; } return list[Random.Range(0, list.Count)]; } private static void AddRange(List destination, List source) { if (source == null) { return; } foreach (int item in source) { if (item > 0 && !destination.Contains(item)) { destination.Add(item); } } } } public static class PlayerInventoryReader { public static int GetAmount(int itemId) { if (itemId <= 0) { return 0; } try { Player instance = Player.Instance; object obj = ((instance != null) ? instance.PlayerInventory : null); if (obj == null) { return 0; } Inventory val = (Inventory)((obj is Inventory) ? obj : null); if (val != null) { return val.GetAmount(itemId); } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "GetAmount", new Type[1] { typeof(int) }, (Type[])null); if (methodInfo == null) { return 0; } return (methodInfo.Invoke(obj, new object[1] { itemId }) is int num) ? num : 0; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)$"[PlayerInventoryReader] GetAmount({itemId}): {ex.Message}"); } return 0; } } public static bool HasItem(int itemId) { return GetAmount(itemId) > 0; } } } namespace GiftingAssistant.Data { public static class GiftingAssistantAlmanacData { public readonly struct RosterEntrySnapshot { public string NpcName { get; } public GiftPriority Priority { get; } public bool IsGiftedToday { get; } public RosterEntrySnapshot(string npcName, GiftPriority priority, bool isGiftedToday) { NpcName = npcName; Priority = priority; IsGiftedToday = isGiftedToday; } } public static bool IsIntegrationEnabled => Plugin.StaticAlmanacIntegrationEnabled; public static bool TryGetSummary(out string summary, out int rosterCount, out int pendingCount) { summary = ""; rosterCount = 0; pendingCount = 0; if (!IsIntegrationEnabled) { return false; } GiftRosterManager manager = Plugin.GetManager(); if (manager == null || string.IsNullOrEmpty(manager.CurrentCharacter)) { return false; } List list = BuildSortedSnapshots(manager); rosterCount = list.Count; foreach (RosterEntrySnapshot item in list) { if (!item.IsGiftedToday) { pendingCount++; } } summary = ((rosterCount == 0) ? "No roster" : $"{pendingCount} pending / {rosterCount} roster"); return true; } public static List GetSortedRosterEntries(int maxCount = 0) { GiftRosterManager manager = Plugin.GetManager(); if (!IsIntegrationEnabled || manager == null || string.IsNullOrEmpty(manager.CurrentCharacter)) { return new List(); } List list = BuildSortedSnapshots(manager); if (maxCount > 0 && list.Count > maxCount) { list = list.GetRange(0, maxCount); } return list; } public static int CountPendingByPriority(GiftPriority minimumPriority) { int num = 0; foreach (RosterEntrySnapshot sortedRosterEntry in GetSortedRosterEntries()) { if (!sortedRosterEntry.IsGiftedToday && sortedRosterEntry.Priority >= minimumPriority) { num++; } } return num; } private static List BuildSortedSnapshots(GiftRosterManager manager) { List list = new List(); foreach (GiftRosterEntry entry in manager.GetEntries()) { if (entry != null && !string.IsNullOrEmpty(entry.NpcName)) { list.Add(new RosterEntrySnapshot(entry.NpcName, entry.Priority, IsGiftedToday(entry))); } } list.Sort(delegate(RosterEntrySnapshot a, RosterEntrySnapshot b) { if (a.IsGiftedToday != b.IsGiftedToday) { if (!a.IsGiftedToday) { return -1; } return 1; } int num = ((int)b.Priority).CompareTo((int)a.Priority); return (num != 0) ? num : string.Compare(a.NpcName, b.NpcName, StringComparison.OrdinalIgnoreCase); }); return list; } private static bool IsGiftedToday(GiftRosterEntry entry) { if (entry == null) { return false; } if (entry.ManualGiftedToday) { return true; } return GiftGameData.HasGivenGiftToday(entry.NpcName); } } public enum GiftPriority { Low, Normal, High, Urgent } [Serializable] public class GiftRosterEntry { public string NpcName { get; set; } public GiftPriority Priority { get; set; } public bool ManualGiftedToday { get; set; } public List PreferredGiftIds { get; set; } public GiftRosterEntry() { NpcName = ""; Priority = GiftPriority.Normal; ManualGiftedToday = false; PreferredGiftIds = new List(); } public GiftRosterEntry(string npcName, GiftPriority priority = GiftPriority.Normal) : this() { NpcName = npcName; Priority = priority; } } [Serializable] public class GiftRosterData { public string CharacterName { get; set; } public List Entries { get; set; } public string LastResetDateKey { get; set; } public DateTime LastUpdated { get; set; } public GiftRosterData() { Entries = new List(); LastResetDateKey = ""; LastUpdated = DateTime.Now; } public GiftRosterData(string characterName) : this() { CharacterName = characterName; } } internal static class GiftRosterJson { internal static string Serialize(GiftRosterData data) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("{"); stringBuilder.Append(" \"CharacterName\": "); MinimalJsonParser.WriteJsonString(stringBuilder, data.CharacterName); stringBuilder.AppendLine(","); stringBuilder.Append(" \"LastResetDateKey\": "); MinimalJsonParser.WriteJsonString(stringBuilder, data.LastResetDateKey ?? ""); stringBuilder.AppendLine(","); stringBuilder.Append(" \"LastUpdated\": "); MinimalJsonParser.WriteJsonString(stringBuilder, data.LastUpdated.ToString("o")); stringBuilder.AppendLine(","); stringBuilder.AppendLine(" \"Entries\": ["); for (int i = 0; i < data.Entries.Count; i++) { GiftRosterEntry giftRosterEntry = data.Entries[i]; stringBuilder.AppendLine(" {"); stringBuilder.Append(" \"NpcName\": "); MinimalJsonParser.WriteJsonString(stringBuilder, giftRosterEntry.NpcName ?? ""); stringBuilder.AppendLine(","); stringBuilder.AppendLine($" \"Priority\": {(int)giftRosterEntry.Priority},"); stringBuilder.AppendLine(" \"ManualGiftedToday\": " + (giftRosterEntry.ManualGiftedToday ? "true" : "false") + ","); stringBuilder.Append(" \"PreferredGiftIds\": ["); if (giftRosterEntry.PreferredGiftIds != null) { for (int j = 0; j < giftRosterEntry.PreferredGiftIds.Count; j++) { stringBuilder.Append(giftRosterEntry.PreferredGiftIds[j].ToString(CultureInfo.InvariantCulture)); if (j < giftRosterEntry.PreferredGiftIds.Count - 1) { stringBuilder.Append(", "); } } } stringBuilder.AppendLine("]"); stringBuilder.Append(" }"); if (i < data.Entries.Count - 1) { stringBuilder.AppendLine(","); } else { stringBuilder.AppendLine(); } } stringBuilder.AppendLine(" ]"); stringBuilder.Append("}"); return stringBuilder.ToString(); } internal static GiftRosterData Deserialize(string json) { if (string.IsNullOrEmpty(json)) { return null; } try { return DeserializeCore(json); } catch (Exception) { return null; } } private static GiftRosterData DeserializeCore(string json) { int pos = 0; Dictionary dictionary = MinimalJsonParser.ParseObject(json, ref pos); if (dictionary == null) { return null; } GiftRosterData giftRosterData = new GiftRosterData(); if (dictionary.TryGetValue("CharacterName", out var value)) { giftRosterData.CharacterName = (value as string) ?? ""; } if (dictionary.TryGetValue("LastResetDateKey", out var value2)) { giftRosterData.LastResetDateKey = (value2 as string) ?? ""; } if (dictionary.TryGetValue("LastUpdated", out var value3) && value3 is string s) { giftRosterData.LastUpdated = (DateTime.TryParse(s, null, DateTimeStyles.RoundtripKind, out var result) ? result : DateTime.Now); } if (dictionary.TryGetValue("Entries", out var value4) && value4 is List list) { bool flag = default(bool); foreach (object item in list) { if (!(item is Dictionary dictionary2)) { continue; } GiftRosterEntry giftRosterEntry = new GiftRosterEntry(); if (dictionary2.TryGetValue("NpcName", out var value5)) { giftRosterEntry.NpcName = (value5 as string) ?? ""; } if (dictionary2.TryGetValue("Priority", out var value6)) { giftRosterEntry.Priority = (GiftPriority)MinimalJsonParser.ToInt(value6); } if (dictionary2.TryGetValue("ManualGiftedToday", out var value7)) { int num; if (value7 is bool) { flag = (bool)value7; num = 1; } else { num = 0; } giftRosterEntry.ManualGiftedToday = (byte)((uint)num & (flag ? 1u : 0u)) != 0; } if (dictionary2.TryGetValue("PreferredGiftIds", out var value8) && value8 is List list2) { foreach (object item2 in list2) { int num2 = MinimalJsonParser.ToInt(item2); if (num2 > 0 && !giftRosterEntry.PreferredGiftIds.Contains(num2)) { giftRosterEntry.PreferredGiftIds.Add(num2); } } } if (!string.IsNullOrEmpty(giftRosterEntry.NpcName)) { giftRosterData.Entries.Add(giftRosterEntry); } } } return giftRosterData; } } public class GiftRosterManager { private GiftRosterData _data; private string _currentCharacter; private bool _isDirty; public bool IsDirty => _isDirty; public string CurrentCharacter => _currentCharacter; public event Action OnRosterChanged; public event Action OnDataLoaded; public void LoadForCharacter(string characterName, GiftRosterData data) { _currentCharacter = characterName; _data = data ?? new GiftRosterData(characterName); _isDirty = false; this.OnDataLoaded?.Invoke(); } public void ClearData() { _data = null; _currentCharacter = null; _isDirty = false; } public GiftRosterData GetData() { return _data; } public void MarkClean() { _isDirty = false; } public IReadOnlyList GetEntries() { IReadOnlyList readOnlyList = _data?.Entries; return readOnlyList ?? Array.Empty(); } public bool Contains(string npcName) { if (_data?.Entries == null || string.IsNullOrEmpty(npcName)) { return false; } return _data.Entries.Exists((GiftRosterEntry e) => string.Equals(e.NpcName, npcName, StringComparison.OrdinalIgnoreCase)); } public GiftRosterEntry GetEntry(string npcName) { if (_data?.Entries == null || string.IsNullOrEmpty(npcName)) { return null; } return _data.Entries.Find((GiftRosterEntry e) => string.Equals(e.NpcName, npcName, StringComparison.OrdinalIgnoreCase)); } public void AddNpc(string npcName, GiftPriority priority = GiftPriority.Normal) { if (_data != null && !string.IsNullOrEmpty(npcName) && !Contains(npcName)) { _data.Entries.Add(new GiftRosterEntry(npcName, priority)); Touch(); } } public void RemoveNpc(string npcName) { if (_data?.Entries != null && _data.Entries.RemoveAll((GiftRosterEntry e) => string.Equals(e.NpcName, npcName, StringComparison.OrdinalIgnoreCase)) > 0) { Touch(); } } public void SetPriority(string npcName, GiftPriority priority) { GiftRosterEntry entry = GetEntry(npcName); if (entry != null && entry.Priority != priority) { entry.Priority = priority; Touch(); } } public void SetManualGifted(string npcName, bool gifted) { GiftRosterEntry entry = GetEntry(npcName); if (entry != null && entry.ManualGiftedToday != gifted) { entry.ManualGiftedToday = gifted; Touch(); } } public void TogglePreferredGift(string npcName, int itemId) { GiftRosterEntry entry = GetEntry(npcName); if (entry != null && itemId > 0) { entry.PreferredGiftIds = entry.PreferredGiftIds ?? new List(); if (entry.PreferredGiftIds.Contains(itemId)) { entry.PreferredGiftIds.Remove(itemId); } else { entry.PreferredGiftIds.Add(itemId); } Touch(); } } public bool IsPreferredGift(string npcName, int itemId) { GiftRosterEntry entry = GetEntry(npcName); if (entry?.PreferredGiftIds != null) { return entry.PreferredGiftIds.Contains(itemId); } return false; } public void ClearPreferredGifts(string npcName) { GiftRosterEntry entry = GetEntry(npcName); if (entry?.PreferredGiftIds != null && entry.PreferredGiftIds.Count != 0) { entry.PreferredGiftIds.Clear(); Touch(); } } public bool ResetDailyGifted(string newDateKey) { if (_data == null) { return false; } bool flag = !string.IsNullOrEmpty(newDateKey) && string.Equals(_data.LastResetDateKey, newDateKey, StringComparison.Ordinal); if (flag) { return false; } bool flag2 = false; foreach (GiftRosterEntry entry in _data.Entries) { if (entry.ManualGiftedToday) { entry.ManualGiftedToday = false; flag2 = true; } } _data.LastResetDateKey = newDateKey ?? ""; if (flag2 || !flag) { Touch(); } return flag2; } private void Touch() { if (_data != null) { _data.LastUpdated = DateTime.Now; } _isDirty = true; this.OnRosterChanged?.Invoke(); } } public class GiftRosterSaveSystem { private readonly GiftRosterManager _manager; private readonly string _savePath; public GiftRosterSaveSystem(GiftRosterManager manager) { _manager = manager; _savePath = Path.Combine(Paths.ConfigPath, "com.azraelgodking.giftingassistant"); if (!Directory.Exists(_savePath)) { Directory.CreateDirectory(_savePath); } } private string GetSaveFilePath(string characterName) { string text = SanitizeFileName(characterName); return Path.Combine(_savePath, text + "_giftroster.json"); } private static string SanitizeFileName(string name) { if (string.IsNullOrEmpty(name)) { return "unknown"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { name = name.Replace(oldChar, '_'); } return name; } public void Save() { GiftRosterData data = _manager.GetData(); if (data == null || string.IsNullOrEmpty(data.CharacterName)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[Save] Cannot save: no data or character name"); } return; } try { string contents = GiftRosterJson.Serialize(data); string saveFilePath = GetSaveFilePath(data.CharacterName); string text = saveFilePath + ".tmp"; try { File.WriteAllText(text, contents); if (File.Exists(saveFilePath)) { string text2 = saveFilePath + ".bak"; if (File.Exists(text2)) { File.Delete(text2); } File.Move(saveFilePath, text2); } File.Move(text, saveFilePath); } finally { if (File.Exists(text)) { File.Delete(text); } } _manager.MarkClean(); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[Save] Saved gift roster for '{data.CharacterName}' ({data.Entries.Count} entries)"); } } catch (Exception arg) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogError((object)$"[Save] Failed to save gift roster: {arg}"); } } } public GiftRosterData Load(string characterName) { if (string.IsNullOrEmpty(characterName)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)"[Load] Cannot load: no character name"); } return null; } string saveFilePath = GetSaveFilePath(characterName); string text = saveFilePath + ".bak"; if (File.Exists(saveFilePath)) { GiftRosterData giftRosterData = TryLoadFromFile(saveFilePath, characterName); if (giftRosterData != null) { return giftRosterData; } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[Load] Main file corrupted for " + characterName + ", trying backup...")); } } if (File.Exists(text)) { GiftRosterData giftRosterData2 = TryLoadFromFile(text, characterName); if (giftRosterData2 != null) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)("[Load] Loaded gift roster from backup for " + characterName)); } return giftRosterData2; } } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)("[Load] No valid gift roster for " + characterName + ", creating new")); } return new GiftRosterData(characterName); } private GiftRosterData TryLoadFromFile(string filePath, string characterName) { try { string text = File.ReadAllText(filePath); if (string.IsNullOrWhiteSpace(text) || !text.TrimStart(Array.Empty()).StartsWith("{")) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[Load] " + filePath + " is not valid JSON")); } return null; } GiftRosterData giftRosterData = GiftRosterJson.Deserialize(text); if (giftRosterData == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[Load] Failed to deserialize " + filePath)); } return null; } if (string.IsNullOrEmpty(giftRosterData.CharacterName)) { giftRosterData.CharacterName = characterName; } return giftRosterData; } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogError((object)("[Load] Error loading " + filePath + ": " + ex.Message)); } return null; } } } } namespace GiftingAssistant.Config { public enum GiftReminderMode { RosterOnly, PushToTodo } internal sealed class GiftingAssistantConfig { public ConfigEntry Enabled { get; } public ConfigEntry CheckForUpdates { get; } public ConfigEntry ToggleKey { get; } public ConfigEntry RequireCtrl { get; } public ConfigEntry ShowInventoryPossession { get; } public ConfigEntry AutoSave { get; } public ConfigEntry AutoSaveInterval { get; } public ConfigEntry UIScale { get; } public ConfigEntry ReminderMode { get; } public ConfigEntry UseAlmanacIntegration { get; } public GiftingAssistantConfig(ConfigFile config) { //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Enable Gifting Assistant"); CheckForUpdates = config.Bind("General", "CheckForUpdates", true, "Check for Gifting Assistant updates on startup via GitHub Pages."); ToggleKey = config.Bind("Hotkeys", "ToggleKey", (KeyCode)103, "Key to toggle the Gifting Assistant window"); RequireCtrl = config.Bind("Hotkeys", "RequireCtrl", true, "Require Ctrl to be held when pressing the toggle key"); ShowInventoryPossession = config.Bind("Display", "ShowInventoryPossession", true, "Show how many of each gift item you currently carry in your bag"); AutoSave = config.Bind("Saving", "AutoSave", true, "Automatically save the gift roster periodically"); AutoSaveInterval = config.Bind("Saving", "AutoSaveInterval", 60f, "Auto-save interval in seconds"); UIScale = config.Bind("Display", "UIScale", 1f, new ConfigDescription("Scale factor for the Gifting Assistant window (1.0 = default)", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2.5f), Array.Empty())); bool value = config.Bind("Integrations", "UseTodoIntegration", false, "[Deprecated] Replaced by ReminderMode. If ReminderMode is missing, true migrates to PushToTodo on first load.").Value; ConfigEntry val = default(ConfigEntry); bool num = config.TryGetEntry("Integrations", "ReminderMode", ref val); ConfigEntry val2 = default(ConfigEntry); bool flag = config.TryGetEntry("Integrations", "UseTodoIntegration", ref val2); GiftReminderMode giftReminderMode = GiftReminderMode.PushToTodo; if (!num && flag) { giftReminderMode = (value ? GiftReminderMode.PushToTodo : GiftReminderMode.RosterOnly); } ReminderMode = config.Bind("Integrations", "ReminderMode", giftReminderMode, "RosterOnly = track gifts in this mod's daily roster (priorities, gifted-today flags). PushToTodo = same roster plus a +Todo button on each row to push reminders into Sun Haven Todo when that mod is installed (default; falls back to roster-only when Sun Haven Todo is not installed)."); UseAlmanacIntegration = config.Bind("Integrations", "UseAlmanacIntegration", true, "When Haven's Almanac is installed, share gift roster progress (pending count, priorities) with its HUD, dashboard, and daily briefing. Off = Gifting Assistant keeps its data private."); } } }