using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using BirthdayReminder.Data; using BirthdayReminder.Integration; using BirthdayReminder.UI; using HarmonyLib; using Microsoft.CodeAnalysis; using SunhavenMods.Shared; using SunhavenTodo; using SunhavenTodo.Data; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; [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("BirthdayReminder")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+5c08b5aa5d0be9c4b93df77f697dc55d5ac97088")] [assembly: AssemblyProduct("BirthdayReminder")] [assembly: AssemblyTitle("BirthdayReminder")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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 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 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 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) { string typeName2 = typeName; Type type = AccessTools.TypeByName(typeName2); if (type != null) { return type; } for (int i = 0; i < namespaces.Length; i++) { type = AccessTools.TypeByName(namespaces[i] + "." + typeName2); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == typeName2 || t.FullName == typeName2); 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 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; } } } namespace BirthdayReminder { [BepInPlugin("com.azraelgodking.squirrelsbirthdayreminder", "A Squirrel's Birthday Reminder", "1.4.2")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { [CompilerGenerated] private static class <>O { public static UnityAction <0>__OnOvernightComplete; public static Func <1>__GetSingletonInstance; } private static BirthdayManager _staticManager; private static BirthdayHUD _staticHUD; private static GameObject _hudObject; private static float _staticHUDPositionX = -1f; private static float _staticHUDPositionY = -1f; private static TodoIntegration _todoIntegration; private ConfigEntry _enabled; private ConfigEntry _hudPositionX; private ConfigEntry _hudPositionY; private ConfigEntry _toggleKey; private ConfigEntry _showGiftHints; private ConfigEntry _useNativeNotifications; private ConfigEntry _debugMode; private ConfigEntry _checkForUpdates; private ConfigEntry _uiScale; private static float _staticUIScale = 1f; private static string _currentCharacterName; private static bool _isCharacterLoaded; private BirthdayManager _manager; private BirthdayHUD _hud; private Harmony _harmony; private bool _applicationQuitting; private static bool _overnightHooked = false; private static UnityAction _overnightCallback; private static GameObject _persistentRunner; private static PersistentRunner _persistentRunnerComponent; private static KeyCode _staticToggleKey = (KeyCode)98; private static bool _staticDebugMode = false; private static bool _staticUseNativeNotifications = true; private static bool _staticEnabled = true; private static Type _notificationStackType; private static PropertyInfo _notificationStackInstance; private static MethodInfo _sendNotificationMethod; private static bool _notificationSystemInitialized = false; 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 StaticDebugMode => _staticDebugMode; public static bool StaticUseNativeNotifications => _staticUseNativeNotifications; public static bool StaticEnabled => _staticEnabled; public static string CurrentCharacter => _currentCharacterName; public static bool IsCharacterLoaded => _isCharacterLoaded; private void Awake() { Instance = this; Log = ((BaseUnityPlugin)this).Logger; ConfigFile = CreateNamedConfig(); ConfigFileHelper.ReplacePluginConfig((BaseUnityPlugin)(object)this, ConfigFile, (Action)Log.LogWarning); Log.LogInfo((object)"Loading A Squirrel's Birthday Reminder v1.4.2"); BindConfiguration(); if (!_enabled.Value) { Log.LogInfo((object)"A Squirrel's Birthday Reminder is disabled in config. Skipping initialization."); return; } CreatePersistentRunner(); InitializeManager(); ApplyPatches(); InitializeIntegrations(); SceneManager.sceneLoaded += OnSceneLoaded; if (_checkForUpdates.Value) { VersionChecker.CheckForUpdate("com.azraelgodking.squirrelsbirthdayreminder", "1.4.2", Log, delegate(VersionChecker.VersionCheckResult result) { result.NotifyUpdateAvailable(Log); }); } Log.LogInfo((object)"A Squirrel's Birthday Reminder loaded successfully!"); } private void BindConfiguration() { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Expected O, but got Unknown _enabled = ConfigFile.Bind("General", "Enabled", true, "Enable birthday reminders"); _staticEnabled = _enabled.Value; _enabled.SettingChanged += delegate { _staticEnabled = _enabled.Value; }; _hudPositionX = ConfigFile.Bind("HUD", "PositionX", -1f, "HUD X position (-1 for default)"); _staticHUDPositionX = _hudPositionX.Value; _hudPositionY = ConfigFile.Bind("HUD", "PositionY", -1f, "HUD Y position (-1 for default)"); _staticHUDPositionY = _hudPositionY.Value; _toggleKey = ConfigFile.Bind("Hotkeys", "ToggleKey", (KeyCode)98, "Key to toggle the birthday HUD (with Ctrl)"); _staticToggleKey = _toggleKey.Value; _toggleKey.SettingChanged += delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) _staticToggleKey = _toggleKey.Value; }; _showGiftHints = ConfigFile.Bind("General", "ShowGiftHints", true, "Show gift preferences in the birthday reminder"); _useNativeNotifications = ConfigFile.Bind("General", "UseNativeNotifications", true, "Show birthday notifications using the game's native notification system"); _debugMode = ConfigFile.Bind("Debug", "DebugMode", false, "Enable debug mode for testing (Ctrl+Shift+B to add test birthday)"); _staticDebugMode = _debugMode.Value; _debugMode.SettingChanged += delegate { _staticDebugMode = _debugMode.Value; }; _staticUseNativeNotifications = _useNativeNotifications.Value; _useNativeNotifications.SettingChanged += delegate { _staticUseNativeNotifications = _useNativeNotifications.Value; }; _checkForUpdates = ConfigFile.Bind("Updates", "CheckForUpdates", true, "Check for mod updates on startup"); _uiScale = ConfigFile.Bind("Display", "UIScale", 1f, new ConfigDescription("Scale factor for the birthday HUD (1.0 = default, 1.5 = 50% larger)", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2.5f), Array.Empty())); _staticUIScale = Mathf.Clamp(_uiScale.Value, 0.5f, 2.5f); _uiScale.SettingChanged += delegate { _staticUIScale = Mathf.Clamp(_uiScale.Value, 0.5f, 2.5f); _staticHUD?.SetScale(_staticUIScale); }; } private static ConfigFile CreateNamedConfig() { return ConfigFileHelper.CreateNamedConfig("com.azraelgodking.squirrelsbirthdayreminder", "BirthdayReminder.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("BirthdayReminder_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _persistentRunnerComponent = _persistentRunner.AddComponent(); Log.LogInfo((object)"[PersistentRunner] Created"); } } private void InitializeManager() { _manager = new BirthdayManager(); _staticManager = _manager; } private void InitializeIntegrations() { try { if (Chainloader.PluginInfos.ContainsKey("com.azraelgodking.sunhaventodo")) { _todoIntegration = new TodoIntegration(_staticManager); } else { Log.LogInfo((object)"[Integrations] SunhavenTodo not found - birthday todos disabled"); } } catch (Exception ex) { Log.LogWarning((object)("[Integrations] Error initializing: " + ex.Message)); } } private void ApplyPatches() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown _harmony = new Harmony("com.azraelgodking.squirrelsbirthdayreminder"); try { Type type = AccessTools.TypeByName("Wish.Player"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "InitializeAsOwner", (Type[])null, (Type[])null); if (methodInfo != null) { MethodInfo methodInfo2 = AccessTools.Method(typeof(PlayerPatches), "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 Player.InitializeAsOwner — birthday player hook inactive"); } } else { Log.LogWarning((object)"Could not find Wish.Player — birthday player hook inactive"); } } catch (Exception ex) { Log.LogWarning((object)("Failed to apply player init patch: " + ex.Message)); return; } try { Type type2 = AccessTools.TypeByName("Wish.NPCAI"); Type type3 = AccessTools.TypeByName("Wish.Item"); if (type2 != null && type3 != null) { MethodInfo methodInfo3 = AccessTools.Method(type2, "Gift", new Type[1] { type3 }, (Type[])null); if (methodInfo3 != null) { MethodInfo methodInfo4 = AccessTools.Method(typeof(GiftPatches), "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) — gift tracking disabled"); _harmony.UnpatchSelf(); } } else { Log.LogWarning((object)"Could not find NPCAI or Wish.Item — gift tracking disabled"); _harmony.UnpatchSelf(); } } catch (Exception ex2) { Log.LogWarning((object)("Failed to apply gift patch (rolling back Harmony): " + ex2.Message)); _harmony.UnpatchSelf(); } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { Log.LogDebug((object)("[BirthdayReminder] Scene loaded: " + ((Scene)(ref scene)).name)); if (((Scene)(ref scene)).name == "MainMenu" || ((Scene)(ref scene)).name == "Bootstrap") { Log.LogInfo((object)"[BirthdayReminder] Main menu detected - hiding HUD and resetting state"); _staticHUD?.Hide(); _isCharacterLoaded = false; _currentCharacterName = null; } else { EnsureUIComponentsExist(); } } public static void ResetOvernightHook() { _overnightHooked = false; _overnightCallback = null; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[BirthdayReminder] Overnight hook reset"); } } public static void TryHookOvernightEvent() { //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.<0>__OnOvernightComplete; if (obj == null) { UnityAction val = OnOvernightComplete; <>O.<0>__OnOvernightComplete = val; obj = (object)val; } OvernightHookUtility.TryHookOvernightEvent(ref _overnightHooked, ref _overnightCallback, (UnityAction)obj, GetSingletonInstance, delegate(string message) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)message); } }, delegate(string message) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)message); } }); } private static void OnOvernightComplete() { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[BirthdayReminder] Day started - checking for birthdays"); } _staticManager?.CheckTodaysBirthdays(); if (_staticManager != null && _staticManager.HasBirthdays) { EnsureUIComponentsExist(); _staticHUD?.Show(); SendAllBirthdayNotifications(); return; } _staticHUD?.Hide(); ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"[BirthdayReminder] No birthdays today - HUD hidden"); } } public static void EnsureUIComponentsExist() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Expected O, but got Unknown try { if ((Object)(object)_persistentRunner == (Object)null || (Object)(object)_persistentRunnerComponent == (Object)null) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"[EnsureUI] Recreating PersistentRunner..."); } _persistentRunner = new GameObject("BirthdayReminder_PersistentRunner"); Object.DontDestroyOnLoad((Object)(object)_persistentRunner); ((Object)_persistentRunner).hideFlags = (HideFlags)61; SceneRootSurvivor.TryRegisterPersistentRunnerGameObject(_persistentRunner); _persistentRunnerComponent = _persistentRunner.AddComponent(); ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"[EnsureUI] PersistentRunner recreated"); } } if ((Object)(object)_staticHUD == (Object)null) { ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)"[EnsureUI] Creating BirthdayHUD..."); } _hudObject = new GameObject("BirthdayReminder_HUD"); Object.DontDestroyOnLoad((Object)(object)_hudObject); _staticHUD = _hudObject.AddComponent(); _staticHUD.Initialize(_staticManager); _staticHUD.SetScale(_staticUIScale); if (_staticHUDPositionX >= 0f && _staticHUDPositionY >= 0f) { _staticHUD.SetPosition(_staticHUDPositionX, _staticHUDPositionY); } _staticHUD.OnPositionChanged = delegate(float x, float y) { _staticHUDPositionX = x; _staticHUDPositionY = y; Plugin instance = Instance; if (instance != null) { ConfigEntry hudPositionX = instance._hudPositionX; if (hudPositionX != null) { ((ConfigEntryBase)hudPositionX).SetSerializedValue(x.ToString()); } } Plugin instance2 = Instance; if (instance2 != null) { ConfigEntry hudPositionY = instance2._hudPositionY; if (hudPositionY != null) { ((ConfigEntryBase)hudPositionY).SetSerializedValue(y.ToString()); } } }; ManualLogSource log4 = Log; if (log4 != null) { log4.LogInfo((object)"[EnsureUI] BirthdayHUD created"); } } if ((Object)(object)Instance != (Object)null) { Instance._hud = _staticHUD; } } catch (Exception ex) { ManualLogSource log5 = Log; if (log5 != null) { log5.LogError((object)("[EnsureUI] Error: " + ex.Message)); } } } public static BirthdayManager GetManager() { return _staticManager; } public static BirthdayHUD GetHUD() { return _staticHUD; } public static TodoIntegration GetTodoIntegration() { return _todoIntegration; } private static void InitializeNotificationSystem() { if (_notificationSystemInitialized) { return; } _notificationSystemInitialized = true; try { _notificationStackType = AccessTools.TypeByName("Wish.NotificationStack"); if (_notificationStackType == null) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)"[Notifications] NotificationStack type not found"); } return; } Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type != null) { _notificationStackInstance = type.MakeGenericType(_notificationStackType).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public); } _sendNotificationMethod = AccessTools.Method(_notificationStackType, "SendNotification", new Type[5] { typeof(string), typeof(int), typeof(int), typeof(bool), typeof(bool) }, (Type[])null); if (_sendNotificationMethod != null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"[Notifications] Native notification system initialized"); } } else { ManualLogSource log3 = Log; if (log3 != null) { log3.LogDebug((object)"[Notifications] SendNotification method not found"); } } } catch (Exception ex) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogDebug((object)("[Notifications] Error initializing: " + ex.Message)); } } } public static void SendBirthdayNotification(string npcName, int itemId = 0) { if (!_staticUseNativeNotifications) { return; } InitializeNotificationSystem(); if (_sendNotificationMethod == null || _notificationStackInstance == null) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)"[Notifications] Cannot send - system not available"); } return; } try { object value = _notificationStackInstance.GetValue(null); if (value == null) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogDebug((object)"[Notifications] NotificationStack instance is null"); } return; } string text = "It's " + npcName + "'s birthday today!"; _sendNotificationMethod.Invoke(value, new object[5] { text, itemId, 1, false, true }); ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)("[Notifications] Sent birthday notification for " + npcName)); } } catch (Exception ex) { ManualLogSource log4 = Log; if (log4 != null) { log4.LogDebug((object)("[Notifications] Error sending notification: " + ex.Message)); } } } public static void SendAllBirthdayNotifications() { if (!_staticUseNativeNotifications || _staticManager == null || !_staticManager.HasBirthdays) { return; } foreach (BirthdayDisplayInfo todaysBirthday in _staticManager.TodaysBirthdays) { if (!todaysBirthday.HasBeenGifted) { SendBirthdayNotification(todaysBirthday.NPCName); } } } private static object GetSingletonInstance(Type targetType) { try { Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type != null) { return AccessTools.Property(type.MakeGenericType(targetType), "Instance")?.GetValue(null); } return AccessTools.Property(targetType, "Instance")?.GetValue(null); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogDebug((object)("[BirthdayReminder] GetSingletonInstance: " + ex.Message)); } return null; } } public static void CheckBirthdays() { _staticManager?.CheckTodaysBirthdays(); } public static void OnCharacterChanged(string newCharacterName) { if (_currentCharacterName != newCharacterName) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)("[BirthdayReminder] Character changed: " + (_currentCharacterName ?? "None") + " -> " + newCharacterName)); } _currentCharacterName = newCharacterName; _staticManager?.ResetForNewCharacter(newCharacterName); _todoIntegration?.Reset(); _staticHUD?.Hide(); _isCharacterLoaded = true; } } private void DebugAddTestBirthday() { Log.LogInfo((object)"[DEBUG] Adding test birthday..."); EnsureUIComponentsExist(); _staticManager?.DebugAddTestBirthday("Test NPC", "Loves: Diamonds, Gold"); if (_staticManager != null && _staticManager.HasBirthdays) { _staticHUD?.Show(); Log.LogInfo((object)"[DEBUG] Test birthday added and HUD shown"); } } private void DebugRefreshBirthdays() { Log.LogInfo((object)"[DEBUG] Ctrl+Shift+R pressed - Manual refresh triggered"); EnsureUIComponentsExist(); _staticManager?.ManualRefresh(); Log.LogInfo((object)$"[DEBUG] Found {_staticManager?.TodaysBirthdays.Count ?? 0} birthdays"); _staticHUD?.Show(); } private void DebugClearBirthdays() { Log.LogInfo((object)"[DEBUG] Clearing all birthdays..."); _staticManager?.DebugClearBirthdays(); _staticHUD?.Hide(); Log.LogInfo((object)"[DEBUG] Birthdays cleared and HUD hidden"); } private void DebugLoadAllBirthdays() { Log.LogInfo((object)"[DEBUG] Loading all NPC birthdays..."); EnsureUIComponentsExist(); _staticManager?.DebugLoadAllBirthdays(); if (_staticManager != null && _staticManager.HasBirthdays) { _staticHUD?.Show(); Log.LogInfo((object)$"[DEBUG] Loaded {_staticManager.TodaysBirthdays.Count} birthdays"); } } private void OnDestroy() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) SceneManager.sceneLoaded -= OnSceneLoaded; _todoIntegration?.Dispose(); _todoIntegration = null; 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 + ")")); } } Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } private void OnApplicationQuit() { _applicationQuitting = true; } } public static class PlayerPatches { private static string _lastCharacterName; public static void OnPlayerInitialized(object __instance) { try { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[PlayerPatches] Player initialized - checking character..."); } string characterName = GetCharacterName(__instance); if (_lastCharacterName != characterName) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[PlayerPatches] Character: " + characterName)); } Plugin.OnCharacterChanged(characterName); _lastCharacterName = characterName; } Plugin.EnsureUIComponentsExist(); Plugin.ResetOvernightHook(); Plugin.TryHookOvernightEvent(); Plugin.GetManager()?.CheckTodaysBirthdays(); BirthdayManager manager = Plugin.GetManager(); if (manager != null && manager.HasBirthdays) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"[PlayerPatches] Found {manager.TodaysBirthdays.Count} birthdays - showing HUD"); } Plugin.GetHUD()?.Show(); return; } ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)"[PlayerPatches] No birthdays found today - HUD will not show automatically"); } ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogInfo((object)"[PlayerPatches] Use Ctrl+Alt+B to add a test birthday for testing"); } } catch (Exception ex) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogError((object)("Error in OnPlayerInitialized: " + ex.Message)); } } } private static string GetCharacterName(object player) { try { Type type = AccessTools.TypeByName("Wish.GameSave"); if (type != null) { Type type2 = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type2 != null) { object obj = AccessTools.Property(type2.MakeGenericType(type), "Instance")?.GetValue(null); if (obj != null) { object obj2 = AccessTools.Property(type, "CurrentSave")?.GetValue(obj); if (obj2 != null) { object obj3 = AccessTools.Property(obj2.GetType(), "characterData")?.GetValue(obj2); if (obj3 != null) { string text = AccessTools.Property(obj3.GetType(), "characterName")?.GetValue(obj3) as string; if (!string.IsNullOrEmpty(text)) { return text; } } } } } } if (player != null) { string text2 = AccessTools.Property(player.GetType(), "playerName")?.GetValue(player) as string; if (!string.IsNullOrEmpty(text2)) { return text2; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("Failed to get character name: " + ex.Message)); } } return "Unknown"; } } public static class GiftPatches { public static void OnGiftGiven(object __instance, object __0) { try { if (__instance == null) { return; } 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(); if (string.IsNullOrEmpty(npcName)) { return; } string normalizedNpcName = NormalizeNpcName(npcName); if (string.IsNullOrEmpty(normalizedNpcName)) { return; } BirthdayManager manager = Plugin.GetManager(); if (manager == null || !manager.HasBirthdays) { return; } BirthdayDisplayInfo birthdayDisplayInfo = manager.TodaysBirthdays.Find((BirthdayDisplayInfo b) => string.Equals(NormalizeNpcName(b.NPCName), normalizedNpcName, StringComparison.OrdinalIgnoreCase) || string.Equals(b.NPCName, npcName, StringComparison.OrdinalIgnoreCase) || (b.NPCName != null && b.NPCName.Contains("+" + npcName))); if (birthdayDisplayInfo != null && !birthdayDisplayInfo.HasBeenGifted) { manager.MarkGifted(normalizedNpcName); manager.TryRecordLovedGift(normalizedNpcName, __0); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[BirthdayReminder] Marked " + normalizedNpcName + " as gifted on their birthday!")); } Plugin.GetTodoIntegration()?.OnGiftGiven(normalizedNpcName); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("Error tracking gift: " + ex.Message)); } } } private 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); } } public class PersistentRunner : MonoBehaviour { private void Update() { CheckHotkeys(); } private void CheckHotkeys() { //IL_00b5: 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_021c: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.StaticEnabled || TextInputFocusGuard.ShouldDeferModHotkeys(Plugin.Log)) { return; } bool flag = Input.GetKey((KeyCode)306) || Input.GetKey((KeyCode)305); bool flag2 = Input.GetKey((KeyCode)304) || Input.GetKey((KeyCode)303); bool flag3 = Input.GetKey((KeyCode)308) || Input.GetKey((KeyCode)307); if (flag && !flag2 && !flag3 && Input.GetKeyDown(Plugin.StaticToggleKey)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[PersistentRunner] Ctrl+B pressed - toggling HUD"); } Plugin.EnsureUIComponentsExist(); Plugin.GetHUD()?.Toggle(); } if (flag && flag3 && !flag2 && Input.GetKeyDown(Plugin.StaticToggleKey)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[PersistentRunner] Ctrl+Alt+B pressed - adding test birthday"); } Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.DebugAddTestBirthday("Test NPC", "Loves: Diamonds, Gold"); BirthdayHUD hUD = Plugin.GetHUD(); BirthdayManager manager = Plugin.GetManager(); if (manager != null && manager.HasBirthdays && (Object)(object)hUD != (Object)null) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"[PersistentRunner] Showing HUD - birthdays: {Plugin.GetManager()?.TodaysBirthdays?.Count}"); } hUD.Show(); ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)$"[PersistentRunner] HUD visible: {hUD.IsVisible}"); } } else { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)$"[PersistentRunner] Cannot show HUD - manager: {Plugin.GetManager() != null}, hud: {(Object)(object)hUD != (Object)null}"); } } } if (flag && flag3 && !flag2 && Input.GetKeyDown((KeyCode)97)) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)"[PersistentRunner] Ctrl+Alt+A pressed - loading all birthdays"); } Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.DebugLoadAllBirthdays(); } if (Plugin.StaticDebugMode && flag && flag2 && !flag3 && Input.GetKeyDown(Plugin.StaticToggleKey)) { Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.DebugAddTestBirthday("Test NPC", "Loves: Diamonds, Gold"); } if (Plugin.StaticDebugMode && flag && flag2 && Input.GetKeyDown((KeyCode)114)) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)"[PersistentRunner] Ctrl+Shift+R pressed - Manual refresh"); } Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.ManualRefresh(); Plugin.GetHUD()?.Show(); } if (Plugin.StaticDebugMode && flag && flag2 && Input.GetKeyDown((KeyCode)99)) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)"[PersistentRunner] Ctrl+Shift+C pressed - Clearing birthdays"); } Plugin.GetManager()?.DebugClearBirthdays(); Plugin.GetHUD()?.Hide(); } if (Plugin.StaticDebugMode && flag && flag2 && Input.GetKeyDown((KeyCode)97)) { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)"[PersistentRunner] Ctrl+Shift+A pressed - loading all birthdays"); } Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.DebugLoadAllBirthdays(); } if (Plugin.StaticDebugMode && flag && flag2 && Input.GetKeyDown((KeyCode)108)) { ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogInfo((object)"[PersistentRunner] Ctrl+Shift+L pressed - dumping Lynn's NPC info..."); } Plugin.EnsureUIComponentsExist(); Plugin.GetManager()?.DebugDumpNPCInfo(); } } 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)."); } } } } public static class PluginInfo { public const string PLUGIN_GUID = "com.azraelgodking.squirrelsbirthdayreminder"; public const string PLUGIN_NAME = "A Squirrel's Birthday Reminder"; public const string PLUGIN_VERSION = "1.4.2"; } } namespace BirthdayReminder.UI { public class BirthdayHUD : MonoBehaviour { private const int WINDOW_ID = 98770; private const int GIFT_WINDOW_ID = 98771; private const float BASE_WINDOW_WIDTH = 320f; private const float BASE_MIN_HEIGHT = 100f; private const float BASE_MAX_HEIGHT = 500f; private const float BASE_HEADER_HEIGHT = 28f; private const float BASE_ITEM_HEIGHT = 65f; private const float BASE_GIFT_WINDOW_WIDTH = 300f; private const float BASE_GIFT_WINDOW_HEIGHT = 400f; private float _scale = 1f; private BirthdayManager _manager; private bool _isVisible; private Rect _windowRect; private float _showTimer; private const float AUTO_HIDE_DELAY = 15f; private bool _showGiftPopup; private Rect _giftPopupRect; private BirthdayDisplayInfo _selectedNPC; private Vector2 _giftScrollPosition; public Action OnPositionChanged; private readonly Color _bgColor = new Color(0.18f, 0.14f, 0.12f, 0.95f); private readonly Color _headerColor = new Color(0.75f, 0.35f, 0.45f, 1f); private readonly Color _borderColor = new Color(0.85f, 0.65f, 0.5f, 1f); private readonly Color _textLight = new Color(0.95f, 0.92f, 0.88f); private readonly Color _textDark = new Color(0.25f, 0.2f, 0.15f); private readonly Color _giftedColor = new Color(0.5f, 0.85f, 0.5f); private readonly Color _ungiftedColor = new Color(1f, 0.75f, 0.3f); private readonly Color _hintColor = new Color(0.75f, 0.7f, 0.65f); private readonly Color _lovedColor = new Color(0.95f, 0.45f, 0.55f); private readonly Color _likedColor = new Color(0.55f, 0.7f, 0.95f); private readonly Color _universalColor = new Color(0.85f, 0.75f, 0.45f); private bool _stylesInitialized; private GUIStyle _windowStyle; private GUIStyle _headerStyle; private GUIStyle _nameStyle; private GUIStyle _giftedStyle; private GUIStyle _hintStyle; private GUIStyle _closeButtonStyle; private GUIStyle _moreButtonStyle; private GUIStyle _giftItemStyle; private GUIStyle _sectionHeaderStyle; private GUIStyle _universalItemStyle; private GUIStyle _statusMessageStyle; private GUIStyle _itemBoxStyle; private GUIStyle _statusGiftedStyle; private GUIStyle _statusUngiftedStyle; private GUIStyle _nameGiftedStyle; private GUIStyle _nameUngiftedStyle; private GUIStyle _lovedSectionStyle; private GUIStyle _likedSectionStyle; private GUIStyle _universalSubHeaderStyle; private GUIStyle _lovedBulletStyle; private GUIStyle _likedBulletStyle; private GUIStyle _universalBulletStyle; private Texture2D _windowBackground; private Texture2D _headerBackground; private Texture2D _itemBackground; private Texture2D _separatorTex; private float WindowWidth => 320f * _scale; private float MinHeight => 100f * _scale; private float MaxHeight => 500f * _scale; private float HeaderHeight => 28f * _scale; private float ItemHeight => 65f * _scale; private float GiftWindowWidth => 300f * _scale; private float GiftWindowHeight => 400f * _scale; public bool IsVisible => _isVisible; private int ScaledFont(int baseSize) { return Mathf.Max(8, Mathf.RoundToInt((float)baseSize * _scale)); } private float Scaled(float value) { return value * _scale; } private int ScaledInt(float value) { return Mathf.RoundToInt(value * _scale); } private void Awake() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) _windowRect = new Rect(100f, 100f, WindowWidth, MinHeight); _isVisible = false; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[BirthdayHUD] Awake called - isVisible set to false (waiting for player init)"); } } public void Initialize(BirthdayManager manager) { //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_007f: 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) _manager = manager; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayHUD] Initialize called, manager: {manager != null}, screen: ({Screen.width}x{Screen.height})"); } if (Screen.width > 0 && Screen.height > 0) { _windowRect = new Rect((float)Screen.width - WindowWidth - Scaled(20f), Scaled(80f), WindowWidth, MinHeight); ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[BirthdayHUD] Default position set to ({((Rect)(ref _windowRect)).x}, {((Rect)(ref _windowRect)).y})"); } } else { _windowRect = new Rect(100f, 100f, WindowWidth, MinHeight); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)"[BirthdayHUD] Using fallback position (100, 100) - screen not ready"); } } if (_manager != null) { _manager.OnBirthdaysUpdated += OnBirthdaysUpdated; } } public void SetPosition(float x, float y) { if (Screen.width > 0 && Screen.height > 0) { if (x >= 0f) { ((Rect)(ref _windowRect)).x = Mathf.Clamp(x, 0f, (float)Screen.width - ((Rect)(ref _windowRect)).width); } if (y >= 0f) { ((Rect)(ref _windowRect)).y = Mathf.Clamp(y, 0f, (float)Screen.height - ((Rect)(ref _windowRect)).height); } } } public (float x, float y) GetPosition() { return (((Rect)(ref _windowRect)).x, ((Rect)(ref _windowRect)).y); } public void SetScale(float scale) { _scale = Mathf.Clamp(scale, 0.5f, 2.5f); _stylesInitialized = false; EnsureOnScreen(); } public void Show() { _isVisible = true; _showTimer = 0f; EnsureOnScreen(); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayHUD] Show() called - isVisible: {_isVisible}, pos: ({((Rect)(ref _windowRect)).x}, {((Rect)(ref _windowRect)).y}), screen: ({Screen.width}x{Screen.height}), birthdays: {(_manager?.TodaysBirthdays?.Count).GetValueOrDefault()}"); } } private void EnsureOnScreen() { if (Screen.width <= 0 || Screen.height <= 0) { return; } if (((Rect)(ref _windowRect)).x < 0f || ((Rect)(ref _windowRect)).y < 0f || ((Rect)(ref _windowRect)).x > (float)(Screen.width - 50) || ((Rect)(ref _windowRect)).y > (float)(Screen.height - 50)) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayHUD] Resetting position - was ({((Rect)(ref _windowRect)).x}, {((Rect)(ref _windowRect)).y})"); } ((Rect)(ref _windowRect)).x = (float)Screen.width - WindowWidth - Scaled(20f); ((Rect)(ref _windowRect)).y = Scaled(80f); } ((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); } public void Hide() { _isVisible = false; } public void Toggle() { if (_isVisible) { Hide(); } else { Show(); } } private void OnBirthdaysUpdated() { if (_manager != null && _manager.HasBirthdays) { Show(); } else { Hide(); } } private void Update() { _manager?.CheckForDateChange(Time.unscaledDeltaTime); if (!_isVisible) { return; } _manager?.UpdateStatusMessage(Time.unscaledDeltaTime); if (_manager != null && !_manager.HasUngiftedBirthdays) { _showTimer += Time.unscaledDeltaTime; if (_showTimer >= 15f) { Hide(); } } else { _showTimer = 0f; } } private void OnGUI() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Expected O, but got Unknown //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Expected O, but got Unknown //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) if (_isVisible) { if (!_stylesInitialized) { InitializeStyles(); } int num = ((_manager != null && _manager.TodaysBirthdays != null) ? _manager.TodaysBirthdays.Count : 0); float num2 = HeaderHeight + Scaled(12f); num2 = ((num != 0) ? (num2 + (float)num * ItemHeight) : (num2 + Scaled(40f))); num2 += Scaled(16f); ((Rect)(ref _windowRect)).width = WindowWidth; ((Rect)(ref _windowRect)).height = Mathf.Clamp(num2, MinHeight, MaxHeight); Rect windowRect = _windowRect; DrawShadow(_windowRect, 4); GUI.depth = -800; _windowRect = GUI.Window(98770, _windowRect, new WindowFunction(DrawWindow), "", _windowStyle); ((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); if (_showGiftPopup && _selectedNPC != null) { DrawShadow(_giftPopupRect, 4); GUI.depth = -900; _giftPopupRect = GUI.Window(98771, _giftPopupRect, new WindowFunction(DrawGiftPopup), "", _windowStyle); ((Rect)(ref _giftPopupRect)).x = Mathf.Clamp(((Rect)(ref _giftPopupRect)).x, 0f, (float)Screen.width - ((Rect)(ref _giftPopupRect)).width); ((Rect)(ref _giftPopupRect)).y = Mathf.Clamp(((Rect)(ref _giftPopupRect)).y, 0f, (float)Screen.height - ((Rect)(ref _giftPopupRect)).height); } if (Math.Abs(((Rect)(ref _windowRect)).x - ((Rect)(ref windowRect)).x) > 0.1f || Math.Abs(((Rect)(ref _windowRect)).y - ((Rect)(ref windowRect)).y) > 0.1f) { OnPositionChanged?.Invoke(((Rect)(ref _windowRect)).x, ((Rect)(ref _windowRect)).y); } } } private void DrawShadow(Rect rect, int offset) { //IL_003d: 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_0052: Unknown result type (might be due to invalid IL or missing references) Color color = default(Color); ((Color)(ref color))..ctor(0f, 0f, 0f, 0.3f); Rect val = new Rect(((Rect)(ref rect)).x + (float)offset, ((Rect)(ref rect)).y + (float)offset, ((Rect)(ref rect)).width, ((Rect)(ref rect)).height); GUI.color = color; GUI.DrawTexture(val, (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; } private void DrawWindow(int windowId) { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) DrawBorder(((Rect)(ref _windowRect)).width, ((Rect)(ref _windowRect)).height, Mathf.Max(1, ScaledInt(2f))); GUILayout.BeginVertical(Array.Empty()); string text = _manager?.CurrentDateFormatted ?? ""; string title = (string.IsNullOrEmpty(text) ? "Birthday Today!" : ("Birthday Today! - " + text)); DrawHeader(title, ((Rect)(ref _windowRect)).width); DrawBirthdays(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _windowRect)).width - Scaled(24f), HeaderHeight)); } private void DrawBorder(float width, float height, int borderSize) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) GUI.color = _borderColor; GUI.DrawTexture(new Rect(0f, 0f, width, (float)borderSize), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(0f, height - (float)borderSize, width, (float)borderSize), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(0f, 0f, (float)borderSize, height), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(width - (float)borderSize, 0f, (float)borderSize, height), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; } private void DrawHeader(string title, float width) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, 0f, width, HeaderHeight); if ((Object)(object)_headerBackground != (Object)null) { GUI.DrawTexture(val, (Texture)(object)_headerBackground); } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(10f)); GUILayout.Label("[*]", _headerStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Scaled(24f)), GUILayout.Height(HeaderHeight) }); GUILayout.Label(title, _headerStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(HeaderHeight) }); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", _closeButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Scaled(24f)), GUILayout.Height(Scaled(24f)) })) { if (title.Contains("Gifts")) { _showGiftPopup = false; _selectedNPC = null; } else { Hide(); } } GUILayout.Space(Scaled(6f)); GUILayout.EndHorizontal(); } private void DrawBirthdays() { GUILayout.Space(Scaled(6f)); if (_manager != null && _manager.HasStatusMessage) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(_manager.StatusMessage, _statusMessageStyle, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(Scaled(4f)); } if (_manager == null || _manager.TodaysBirthdays == null || _manager.TodaysBirthdays.Count == 0) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label("No birthdays today", _hintStyle, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } else { foreach (BirthdayDisplayInfo todaysBirthday in _manager.TodaysBirthdays) { DrawBirthdayItem(todaysBirthday); } } GUILayout.Space(Scaled(6f)); } private void DrawBirthdayItem(BirthdayDisplayInfo birthday) { GUILayout.BeginVertical(_itemBoxStyle, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUIStyle val = (birthday.HasBeenGifted ? _statusGiftedStyle : _statusUngiftedStyle); GUILayout.Label(birthday.HasBeenGifted ? "[OK]" : "[!!]", val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(Scaled(40f)) }); GUILayout.BeginVertical(Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUIStyle val2 = (birthday.HasBeenGifted ? _nameGiftedStyle : _nameUngiftedStyle); GUILayout.Label(birthday.NPCName, val2, Array.Empty()); GUILayout.FlexibleSpace(); if ((birthday.AllLovedGifts.Count > 0 || birthday.AllLikedGifts.Count > 0) && GUILayout.Button("Gifts", _moreButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Scaled(50f)), GUILayout.Height(Scaled(20f)) })) { OpenGiftPopup(birthday); } GUILayout.EndHorizontal(); if (!birthday.HasBeenGifted && !string.IsNullOrEmpty(birthday.GiftHint)) { GUILayout.Space(Scaled(2f)); GUILayout.Label(birthday.GiftHint, _hintStyle, Array.Empty()); } GUILayout.EndVertical(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); } private void OpenGiftPopup(BirthdayDisplayInfo birthday) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) _selectedNPC = birthday; _showGiftPopup = true; _giftScrollPosition = Vector2.zero; _giftPopupRect = new Rect(((Rect)(ref _windowRect)).x + ((Rect)(ref _windowRect)).width + Scaled(10f), ((Rect)(ref _windowRect)).y, GiftWindowWidth, GiftWindowHeight); if (((Rect)(ref _giftPopupRect)).x + GiftWindowWidth > (float)Screen.width) { ((Rect)(ref _giftPopupRect)).x = ((Rect)(ref _windowRect)).x - GiftWindowWidth - Scaled(10f); } } private void DrawGiftPopup(int windowId) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0202: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) DrawBorder(GiftWindowWidth, GiftWindowHeight, ScaledInt(2f)); GUILayout.BeginVertical(Array.Empty()); Rect val = default(Rect); ((Rect)(ref val))..ctor(0f, 0f, GiftWindowWidth, HeaderHeight); if ((Object)(object)_headerBackground != (Object)null) { GUI.DrawTexture(val, (Texture)(object)_headerBackground); } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(10f)); GUILayout.Label(_selectedNPC.NPCName + "'s Gifts", _headerStyle, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(HeaderHeight) }); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", _closeButtonStyle, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Scaled(24f)), GUILayout.Height(Scaled(24f)) })) { _showGiftPopup = false; _selectedNPC = null; } GUILayout.Space(Scaled(6f)); GUILayout.EndHorizontal(); _giftScrollPosition = GUILayout.BeginScrollView(_giftScrollPosition, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(GiftWindowHeight - HeaderHeight - Scaled(10f)) }); GUILayout.Space(Scaled(6f)); DrawSectionHeader("LOVED GIFTS", _lovedColor); GUILayout.Space(Scaled(4f)); foreach (string allLovedGift in _selectedNPC.AllLovedGifts) { DrawGiftItem(allLovedGift, _lovedColor); } if (BirthdayCache.UniversalLoved.Count > 0) { GUILayout.Space(Scaled(4f)); DrawSubHeader("Universal Loved:", _universalColor); foreach (string item in BirthdayCache.UniversalLoved) { DrawGiftItem(item, _universalColor); } } GUILayout.Space(Scaled(8f)); DrawSeparator(); GUILayout.Space(Scaled(8f)); DrawSectionHeader("LIKED GIFTS", _likedColor); GUILayout.Space(Scaled(4f)); foreach (string allLikedGift in _selectedNPC.AllLikedGifts) { DrawGiftItem(allLikedGift, _likedColor); } if (BirthdayCache.UniversalLiked.Count > 0) { GUILayout.Space(Scaled(4f)); DrawSubHeader("Universal Liked:", _universalColor); foreach (string item2 in BirthdayCache.UniversalLiked) { DrawGiftItem(item2, _universalColor); } } GUILayout.Space(Scaled(10f)); GUILayout.EndScrollView(); GUILayout.EndVertical(); GUI.DragWindow(new Rect(0f, 0f, GiftWindowWidth - Scaled(24f), HeaderHeight)); } private void DrawSectionHeader(string text, Color color) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(8f)); GUIStyle val = ((color == _lovedColor) ? _lovedSectionStyle : ((!(color == _likedColor)) ? _sectionHeaderStyle : _likedSectionStyle)); GUILayout.Label(text, val, Array.Empty()); GUILayout.EndHorizontal(); } private void DrawSubHeader(string text, Color color) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(12f)); GUILayout.Label(text, _universalSubHeaderStyle, Array.Empty()); GUILayout.EndHorizontal(); } private void DrawGiftItem(string gift, Color bulletColor) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(16f)); GUIStyle val = ((bulletColor == _lovedColor) ? _lovedBulletStyle : ((!(bulletColor == _likedColor)) ? _universalBulletStyle : _likedBulletStyle)); GUILayout.Label("•", val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(Scaled(12f)) }); GUILayout.Label(gift, _giftItemStyle, Array.Empty()); GUILayout.EndHorizontal(); } private void DrawSeparator() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_008c: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(Scaled(20f)); float num = GiftWindowWidth - Scaled(40f); Rect rect = GUILayoutUtility.GetRect(num, 1f); GUI.color = new Color(_borderColor.r, _borderColor.g, _borderColor.b, 0.4f); GUI.DrawTexture(new Rect(((Rect)(ref rect)).x, ((Rect)(ref rect)).y, num, 1f), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GUILayout.EndHorizontal(); } private void InitializeStyles() { if (_stylesInitialized) { return; } try { CreateTextures(); CreateStyles(); _stylesInitialized = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[BirthdayHUD] Styles initialized successfully"); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("[BirthdayHUD] Failed to initialize styles: " + ex.Message)); } _windowStyle = GUI.skin.box; _stylesInitialized = true; } } private void CreateTextures() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) _windowBackground = MakeTex(4, 4, _bgColor); _headerBackground = MakeGradientTex(4, 8, _headerColor, new Color(_headerColor.r * 0.7f, _headerColor.g * 0.7f, _headerColor.b * 0.7f, 1f)); _itemBackground = MakeTex(4, 4, new Color(0.25f, 0.2f, 0.18f, 0.6f)); _separatorTex = MakeTex(1, 1, _borderColor); } private void CreateStyles() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: 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_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_006f: Expected O, but got Unknown //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0083: 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_009a: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown //IL_00dd: Expected O, but got Unknown //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Expected O, but got Unknown //IL_0164: 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_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018f: 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_01a2: Expected O, but got Unknown //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Expected O, but got Unknown //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0208: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02ca: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Expected O, but got Unknown //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Expected O, but got Unknown //IL_02e8: Expected O, but got Unknown //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_02fc: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0314: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_032a: Expected O, but got Unknown //IL_032f: Expected O, but got Unknown //IL_0330: Unknown result type (might be due to invalid IL or missing references) //IL_0335: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Expected O, but got Unknown //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0373: Unknown result type (might be due to invalid IL or missing references) //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Expected O, but got Unknown //IL_0390: Unknown result type (might be due to invalid IL or missing references) //IL_0395: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_03a6: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Expected O, but got Unknown //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03d0: Unknown result type (might be due to invalid IL or missing references) //IL_03fd: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Expected O, but got Unknown //IL_0407: Unknown result type (might be due to invalid IL or missing references) //IL_0434: Unknown result type (might be due to invalid IL or missing references) //IL_043e: Expected O, but got Unknown //IL_0443: Expected O, but got Unknown //IL_044a: Unknown result type (might be due to invalid IL or missing references) //IL_044f: Unknown result type (might be due to invalid IL or missing references) //IL_0456: Unknown result type (might be due to invalid IL or missing references) //IL_0460: Unknown result type (might be due to invalid IL or missing references) //IL_0467: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Expected O, but got Unknown //IL_0481: Unknown result type (might be due to invalid IL or missing references) //IL_0486: Unknown result type (might be due to invalid IL or missing references) //IL_048d: Unknown result type (might be due to invalid IL or missing references) //IL_0497: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) //IL_04b1: Expected O, but got Unknown //IL_04b8: Unknown result type (might be due to invalid IL or missing references) //IL_04bd: Unknown result type (might be due to invalid IL or missing references) //IL_04e9: 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_04fa: Unknown result type (might be due to invalid IL or missing references) //IL_050d: Expected O, but got Unknown //IL_0514: 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_0520: Unknown result type (might be due to invalid IL or missing references) //IL_052a: Unknown result type (might be due to invalid IL or missing references) //IL_0531: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Expected O, but got Unknown //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0550: Unknown result type (might be due to invalid IL or missing references) //IL_0557: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Expected O, but got Unknown //IL_056d: Unknown result type (might be due to invalid IL or missing references) //IL_0572: Unknown result type (might be due to invalid IL or missing references) //IL_0579: Unknown result type (might be due to invalid IL or missing references) //IL_0588: Expected O, but got Unknown //IL_058f: Unknown result type (might be due to invalid IL or missing references) //IL_0594: 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: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05bf: Expected O, but got Unknown //IL_05c6: Unknown result type (might be due to invalid IL or missing references) //IL_05cb: Unknown result type (might be due to invalid IL or missing references) //IL_05d2: Unknown result type (might be due to invalid IL or missing references) //IL_05e1: Expected O, but got Unknown //IL_05e8: Unknown result type (might be due to invalid IL or missing references) //IL_05ed: Unknown result type (might be due to invalid IL or missing references) //IL_05f4: Unknown result type (might be due to invalid IL or missing references) //IL_0603: Expected O, but got Unknown //IL_060a: Unknown result type (might be due to invalid IL or missing references) //IL_060f: Unknown result type (might be due to invalid IL or missing references) //IL_0616: Unknown result type (might be due to invalid IL or missing references) //IL_0625: Expected O, but got Unknown int num = Mathf.Max(1, ScaledInt(4f)); int num2 = Mathf.Max(1, ScaledInt(2f)); GUIStyle val = new GUIStyle(); val.normal.background = _windowBackground; val.normal.textColor = _textLight; val.padding = new RectOffset(num, num, num, num); val.border = new RectOffset(num2, num2, num2, num2); _windowStyle = val; GUIStyle val2 = new GUIStyle { fontSize = ScaledFont(13), fontStyle = (FontStyle)1 }; val2.normal.textColor = Color.white; val2.alignment = (TextAnchor)3; val2.padding = new RectOffset(ScaledInt(2f), ScaledInt(2f), ScaledInt(2f), ScaledInt(2f)); _headerStyle = val2; GUIStyle val3 = new GUIStyle { fontSize = ScaledFont(12), fontStyle = (FontStyle)1 }; val3.normal.textColor = _textLight; val3.alignment = (TextAnchor)3; _nameStyle = val3; GUIStyle val4 = new GUIStyle(_nameStyle) { fontStyle = (FontStyle)2 }; val4.normal.textColor = new Color(_textLight.r, _textLight.g, _textLight.b, 0.5f); _giftedStyle = val4; GUIStyle val5 = new GUIStyle { fontSize = ScaledFont(10), fontStyle = (FontStyle)2 }; val5.normal.textColor = _hintColor; val5.alignment = (TextAnchor)0; val5.wordWrap = true; _hintStyle = val5; GUIStyle val6 = new GUIStyle { fontSize = ScaledFont(14), fontStyle = (FontStyle)1 }; val6.normal.textColor = new Color(1f, 0.9f, 0.9f); val6.hover.textColor = Color.white; val6.alignment = (TextAnchor)4; _closeButtonStyle = val6; GUIStyle val7 = new GUIStyle(GUI.skin.button) { fontSize = ScaledFont(10), fontStyle = (FontStyle)1 }; val7.normal.textColor = _borderColor; val7.normal.background = MakeTex(1, 1, new Color(0.3f, 0.25f, 0.22f, 0.8f)); val7.hover.textColor = Color.white; val7.hover.background = MakeTex(1, 1, new Color(0.5f, 0.35f, 0.3f, 0.9f)); val7.alignment = (TextAnchor)4; val7.padding = new RectOffset(ScaledInt(4f), ScaledInt(4f), ScaledInt(2f), ScaledInt(2f)); val7.margin = new RectOffset(0, 0, 0, 0); _moreButtonStyle = val7; GUIStyle val8 = new GUIStyle { fontSize = ScaledFont(11) }; val8.normal.textColor = _textLight; val8.alignment = (TextAnchor)3; val8.wordWrap = true; val8.padding = new RectOffset(0, 0, 1, 1); _giftItemStyle = val8; GUIStyle val9 = new GUIStyle { fontSize = ScaledFont(11), fontStyle = (FontStyle)1 }; val9.normal.textColor = _textLight; val9.alignment = (TextAnchor)3; _sectionHeaderStyle = val9; GUIStyle val10 = new GUIStyle(_giftItemStyle); val10.normal.textColor = _universalColor; _universalItemStyle = val10; GUIStyle val11 = new GUIStyle(_hintStyle); val11.normal.textColor = _giftedColor; val11.fontStyle = (FontStyle)1; val11.alignment = (TextAnchor)4; _statusMessageStyle = val11; GUIStyle val12 = new GUIStyle(); val12.normal.background = _itemBackground; val12.padding = new RectOffset(ScaledInt(8f), ScaledInt(8f), ScaledInt(6f), ScaledInt(6f)); val12.margin = new RectOffset(ScaledInt(6f), ScaledInt(6f), ScaledInt(2f), ScaledInt(2f)); _itemBoxStyle = val12; GUIStyle val13 = new GUIStyle(_nameStyle); val13.normal.textColor = _giftedColor; val13.fontStyle = (FontStyle)1; val13.fontSize = ScaledFont(13); _statusGiftedStyle = val13; GUIStyle val14 = new GUIStyle(_nameStyle); val14.normal.textColor = _ungiftedColor; val14.fontStyle = (FontStyle)1; val14.fontSize = ScaledFont(13); _statusUngiftedStyle = val14; GUIStyle val15 = new GUIStyle(_nameStyle); val15.normal.textColor = new Color(_textLight.r, _textLight.g, _textLight.b, 0.5f); val15.fontStyle = (FontStyle)2; val15.fontSize = ScaledFont(13); _nameGiftedStyle = val15; GUIStyle val16 = new GUIStyle(_nameStyle); val16.normal.textColor = _textLight; val16.fontStyle = (FontStyle)1; val16.fontSize = ScaledFont(13); _nameUngiftedStyle = val16; GUIStyle val17 = new GUIStyle(_sectionHeaderStyle); val17.normal.textColor = _lovedColor; _lovedSectionStyle = val17; GUIStyle val18 = new GUIStyle(_sectionHeaderStyle); val18.normal.textColor = _likedColor; _likedSectionStyle = val18; GUIStyle val19 = new GUIStyle(_hintStyle); val19.normal.textColor = _universalColor; val19.fontStyle = (FontStyle)1; val19.fontSize = ScaledFont(9); _universalSubHeaderStyle = val19; GUIStyle val20 = new GUIStyle(_giftItemStyle); val20.normal.textColor = _lovedColor; _lovedBulletStyle = val20; GUIStyle val21 = new GUIStyle(_giftItemStyle); val21.normal.textColor = _likedColor; _likedBulletStyle = val21; GUIStyle val22 = new GUIStyle(_giftItemStyle); val22.normal.textColor = _universalColor; _universalBulletStyle = val22; } private Texture2D MakeTex(int width, int height, Color color) { //IL_000f: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown Color[] array = (Color[])(object)new Color[width * height]; for (int i = 0; i < array.Length; i++) { array[i] = color; } Texture2D val = new Texture2D(width, height); val.SetPixels(array); val.Apply(); return val; } private Texture2D MakeGradientTex(int width, int height, Color topColor, Color bottomColor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_001d: 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_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: 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 = (float)i / (float)(height - 1); Color val2 = Color.Lerp(bottomColor, topColor, num); for (int j = 0; j < width; j++) { array[i * width + j] = val2; } } val.SetPixels(array); val.Apply(); return val; } private void OnDestroy() { if (_manager != null) { _manager.OnBirthdaysUpdated -= OnBirthdaysUpdated; } } } } namespace BirthdayReminder.Integration { public class TodoIntegration { private readonly BirthdayManager _birthdayManager; private static MethodInfo _todoGetByIdMethod; private static bool _todoGetByIdLookedUp; private readonly Dictionary _birthdayTodoIds = new Dictionary(); public TodoIntegration(BirthdayManager birthdayManager) { _birthdayManager = birthdayManager; _birthdayManager.OnBirthdaysUpdated += OnBirthdaysUpdated; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[TodoIntegration] Initialized - birthday todos will sync with Sun Haven Todo"); } } public void OnGiftGiven(string npcName) { try { TodoManager todoManager = Plugin.GetTodoManager(); if (todoManager == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[TodoIntegration] OnGiftGiven: TodoManager is null, cannot complete todo for " + npcName)); } return; } if (!_birthdayTodoIds.TryGetValue(npcName, out string value)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[TodoIntegration] OnGiftGiven: No tracked todo for " + npcName)); } return; } TodoItem val = FindTodoById(todoManager, value); if (val == null) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)("[TodoIntegration] OnGiftGiven: Todo " + value + " not found for " + npcName)); } _birthdayTodoIds.Remove(npcName); } else if (!val.IsCompleted) { todoManager.ToggleComplete(value); ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)("[TodoIntegration] Completed birthday todo for " + npcName + " (direct path)")); } } else { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogInfo((object)("[TodoIntegration] Todo for " + npcName + " already completed")); } } } catch (Exception ex) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)("[TodoIntegration] Error completing todo on gift: " + ex.Message)); } } } private void OnBirthdaysUpdated() { try { TodoManager todoManager = Plugin.GetTodoManager(); if (todoManager == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)"[TodoIntegration] OnBirthdaysUpdated: TodoManager is null"); } return; } List todaysBirthdays = _birthdayManager.TodaysBirthdays; if (todaysBirthdays == null || todaysBirthdays.Count == 0) { CleanupBirthdayTodos(todoManager); return; } HashSet currentNPCs = new HashSet(todaysBirthdays.Select((BirthdayDisplayInfo b) => b.NPCName)); foreach (string item in _birthdayTodoIds.Keys.Where((string k) => !currentNPCs.Contains(k)).ToList()) { RemoveBirthdayTodo(todoManager, item); } foreach (BirthdayDisplayInfo item2 in todaysBirthdays) { if (item2.HasBeenGifted) { CompleteBirthdayTodo(todoManager, item2.NPCName); } else { EnsureBirthdayTodo(todoManager, item2); } } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("[TodoIntegration] Error syncing birthdays: " + ex.Message)); } } } private void EnsureBirthdayTodo(TodoManager todoManager, BirthdayDisplayInfo birthday) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown if (!_birthdayTodoIds.ContainsKey(birthday.NPCName)) { string text = "Give " + birthday.NPCName + " a birthday gift!"; string text2 = ((!string.IsNullOrEmpty(birthday.GiftHint)) ? birthday.GiftHint : "It's their birthday today!"); TodoItem val = new TodoItem(text, text2, (TodoPriority)2, (TodoCategory)6); todoManager.AddTodo(val); _birthdayTodoIds[birthday.NPCName] = val.Id; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[TodoIntegration] Added birthday todo for " + birthday.NPCName + " (id: " + val.Id + ")")); } } } private void CompleteBirthdayTodo(TodoManager todoManager, string npcName) { if (!_birthdayTodoIds.TryGetValue(npcName, out string value)) { return; } TodoItem val = FindTodoById(todoManager, value); if (val != null && !val.IsCompleted) { todoManager.ToggleComplete(value); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[TodoIntegration] Completed birthday todo for " + npcName + " (event path)")); } } } private static TodoItem FindTodoById(TodoManager todoManager, string todoId) { string todoId2 = todoId; if (todoManager == null || string.IsNullOrEmpty(todoId2)) { return null; } if (!_todoGetByIdLookedUp) { _todoGetByIdMethod = typeof(TodoManager).GetMethod("GetTodoById", BindingFlags.Instance | BindingFlags.Public); _todoGetByIdLookedUp = true; } if (_todoGetByIdMethod != null) { try { object? obj = _todoGetByIdMethod.Invoke(todoManager, new object[1] { todoId2 }); return (TodoItem)((obj is TodoItem) ? obj : null); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[TodoIntegration] GetTodoById reflection fallback used: " + ex.Message)); } } } return todoManager.GetAllTodos().FirstOrDefault((Func)((TodoItem t) => t.Id == todoId2)); } private void RemoveBirthdayTodo(TodoManager todoManager, string npcName) { if (_birthdayTodoIds.TryGetValue(npcName, out string value)) { todoManager.RemoveTodo(value); _birthdayTodoIds.Remove(npcName); } } private void CleanupBirthdayTodos(TodoManager todoManager) { foreach (KeyValuePair item in _birthdayTodoIds.ToList()) { todoManager.RemoveTodo(item.Value); } _birthdayTodoIds.Clear(); } public void Reset() { _birthdayTodoIds.Clear(); } public void Dispose() { if (_birthdayManager != null) { _birthdayManager.OnBirthdaysUpdated -= OnBirthdaysUpdated; } } } } namespace BirthdayReminder.Data { public static class BirthdayCache { public static readonly List UniversalLoved; public static readonly List UniversalLiked; private static readonly List _allBirthdays; public static IReadOnlyList AllBirthdays => _allBirthdays; static BirthdayCache() { UniversalLoved = new List { "Blue Rose Bouquet", "Red Rose Bouquet", "Black Diamond", "Havenite" }; UniversalLiked = new List { "BLT", "Caribbean Green Soup", "Cheeseburger", "Cheesecake", "Cinnamon Apple Pie", "Diamond", "Pizza", "Pot Pie", "Red Veggie Soup", "Shimmeroot Treat", "Spicy Ramen", "Spring Roll", "Tomato Salad" }; _allBirthdays = new List { CreateBirthday("Darius", "Spring", 1, new string[18] { "Blue Rose Bouquet", "Devilfin", "Enchanted Glorite Bar", "Enhanced Glorite Bar", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Ghost Pepper", "Glorite Bar", "Glorite Ore", "Glorite Sword", "Red Rose Bouquet", "Steak", "Sunite Sword", "Sweet and Spicy Shrimp", "Withercake" }, new string[21] { "Adamant Sword", "Armoranth", "Bone Gift", "Cinnaberry", "Copper Sword", "Darkness Essence", "Diamond", "Ghostly Great Sword", "Iron Sword", "Legendary Great Sword", "Legendary Hammer", "Lightning Hammer", "Mithril Sword", "Orchid", "Pepper", "Pepper Great Sword", "Potato Hammer", "Red Velvet Cake", "Red Velvet Cupcake", "Spicy Ramen", "Spiked Salmon" }), CreateBirthday("Lynn", "Spring", 10, new string[19] { "Anvil", "Cream Seltzer", "Creamy Beef Stew", "Creamy Mushroom Soup", "Egg Hash", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Glorite Bar", "Hot Chocolate", "Mana Anvil", "Mithril Bar", "Monster Anvil", "Pot Pie", "Stuffed Casserole", "Sunite Bar", "Withergate Anvil" }, new string[10] { "Adamant Bar", "Apple Juice", "Copper Bar", "Fizzy Seltzer", "Glass of Pure Water", "Iron Bar", "Milk", "Mithril Bar", "Pickled Veggie Salad", "Veggie Kebab" }), CreateBirthday("Nathaniel", "Spring", 13, new string[36] { "Adamant Sword", "City Guard Chest Plate", "City Guard Gloves", "City Guard Helmet", "City Guard Pants", "Copper Sword", "Cream Seltzer", "Creamy Mushroom Soup", "Dragon Mail Boots", "Dragon Mail Cape", "Dragon Mail Chest Plate", "Dragon Mail Gloves", "Dragon Mail Helmet", "Enchanted Light Boots", "Enchanted Light Chest", "Enchanted Light Gloves", "Enchanted Light Helmet", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Glorite Sword", "Hearty Pie", "Iron Sword", "Lasagna", "Legendary Great Sword", "Legendary Hammer", "Mashed Potatoes", "Mithril Sword", "Multiplate Chest", "Multiplate Helmet", "Multiplate Pants", "Pot Pie", "Steak", "Sunite Sword" }, new string[15] { "Adamant Helmet", "Apple Juice", "Armoranth", "Copper Helmet", "Copper Ore", "Ghostly Great Sword", "Iron Helmet", "Iron Ore", "Lightning Hammer", "Milk", "Mithril Helmet", "Pepper Great Sword", "Potato Hammer", "Sunite Helmet", "Veggie Kebab" }), CreateBirthday("Wesley", "Spring", 18, new string[24] { "Blue Roses Honey", "Cooled Lava Honey", "Daisy Honey", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Grapes", "Hibiscus Honey", "Honey", "Honeybrew", "Honeycomb Cake", "Honeyglazed Apple", "Lavender Honey", "Lily Honey", "Lotus Honey", "Orchid Honey", "Red Roses Honey", "Snobfish", "Sun Flower Honey", "Tulip Honey", "Walk Choy", "Walk Choy Pet" }, new string[25] { "Advanced Compost", "Advanced Earth Fertilizer", "Advanced Fire Fertilizer", "Advanced Magic Fertilizer", "Advanced Water Fertilizer", "Bubble Tea", "Compost", "Dragon Tea", "Earth Dragon Tea", "Elven Compost", "Fire Dragon Tea", "Grape Juice", "Honeysuckle", "Indiglow Tea", "Lily", "Mana Fertilizer", "Simple Earth Fertilizer", "Simple Fire Fertilizer", "Simple Magic Fertilizer", "Simple Sand Fertilizer", "Simple Water Fertilizer", "Slime Fertilizer", "Tea", "Water Dragon Tea", "Wind Chime Tea" }), CreateBirthday("Claude", "Spring", 24, new string[14] { "Apple Pie", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Red Veggie Soup", "Tomato", "Tomato Bread", "Tomato Juice", "Tomato Salad", "Tomato Soup", "Vampire Piranha", "Vampire Squid" }, new string[7] { "Apple", "Apple Juice", "Cinnamon Apple Pie", "Claude's Performance Record", "Record Player", "Red Veggie Soup", "Spaghetti" }), CreateBirthday("Liam", "Summer", 5, new string[18] { "Chef Hat (Black)", "Chef Hat (Blue)", "Chef Hat (Pink)", "Chef Hat (Red)", "Chef Hat (White)", "Coffee", "Demon Coffee", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Giant Blue Bunny Plushie", "Giant Pink Bunny Plushie", "Giant Pink Teddy Plushie", "Giant Purple Teddy Plushie", "Giant Teddy Plushie", "Hot Chocolate" }, new string[12] { "Almond Croissant", "Barley", "Cinnamon Spice Latte", "Coal", "Cookies", "Fire Crystal", "Flour", "Hearty Pie", "Log", "Scythe", "Sunflower", "Wheat" }), CreateBirthday("Shang", "Summer", 9, new string[26] { "Adamant Key", "Adamant Ring", "Adamant Sword", "Advanced Attack Potion", "Advanced Defense Potion", "Blunted Swordfish", "Earth Dragon Tea", "Fire Dragon Tea", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Glorite Sword", "Hearty Armor Pie", "Hearty Pie", "Incredible Attack Potion", "Incredible Defense Potion", "Indiglow Tea", "Mithril Sword", "Razor Stalk Tea", "Sunite Sword", "Swordfish Sashimi", "Tea", "Water Dragon Tea", "Wind Chime Tea" }, new string[25] { "Adamant Key", "Armoranth", "Attack Potion", "Carrot Juice", "Copper Key", "Copper Ring", "Copper Sword", "Defense Potion", "Dock Worker's Bandana", "Energy Smoothie", "Festive Oranges", "Glass of Pure Water", "Glorite Key", "Health Potion", "Iron Key", "Iron Ring", "Iron Sword", "Kale Juice", "Mithril Key", "Moonfish", "Rusty Key", "Sunite Key", "Tea Leaves", "Wind Chime", "Yucky Green Juice" }), CreateBirthday("Elyssia", "Summer", 11, new string[23] { "Apple Pie", "Cookies", "Cuckoo Wall Clock", "Flan", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Fried Rice", "Fruit Tart", "Grandfather Clock", "Grilled Cheese", "Omelet", "Potato Salad", "Spellberry Juice", "Spellberry Tartlets", "Sugar Apple Herbal Soup", "Sugar Apple Ice Cream", "Sugar Apple Juice", "Triceratops Plushie", "Turtle Plushie", "Wind Chime Tea" }, new string[17] { "Antler", "Crystal Fruit", "Dragon Scale", "Earth Crystal", "Earth Rune", "Hexfruit", "Mantel Clock", "Refined Glass", "Refined Metal", "Round Wall Clock", "Sand Dollar", "Scrambled Eggs", "Small Mana Tome", "Spellberry", "Sugar Apple", "Tea", "Tea Leaves" }), CreateBirthday("Karish", "Summer", 17, new string[21] { "Adamant Chest Plate", "Adamant Sword", "Crab Roll", "Enchanted Fishing Rod", "Fishing Skill Tome", "Fishing Totem", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Fried Carp", "Grilled Carp", "Grilled Crab", "Large Fishing Net", "Legendary Fish Bait", "Poke Bowl", "Roasted Tuna", "Tuna Nigiri", "Tuna Sashimi", "Very Good Fishing Rod" }, new string[22] { "Basic Fishing Rod", "Blunted Swordfish", "Carp", "Copper Chest Plate", "Copper Sword", "Ghostly Great Sword", "Handmade Bobber", "Iron Chest Plate", "Iron Sword", "Ironhead Sturgeon", "Legendary Great Sword", "Legendary Hammer", "Lightning Hammer", "Pepper Great Sword", "Potato Hammer", "Salmon", "Sea Bass", "Silver Carp", "Small Fishing Net", "Sweet Fish Bait", "Tuna", "Worm" }), CreateBirthday("Lucia", "Summer", 20, new string[12] { "Cream Seltzer", "Fire Crystal", "Fizzy Seltzer", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Large Mana Tome", "Small Mana Tome", "Sunite Ore", "Tikka Masala" }, new string[22] { "Blazeel", "Energy Smoothie", "Flame Ray", "Greenspice", "Hearth Angler", "Heat Fruit", "Hot Sauce", "Inferno Guppy", "Magma Star", "Molten Slug", "Pepper", "Pyrelus", "Scorching Squid", "Searback", "Spicy Catsup", "Spicy Ramen", "Spicy Shrimp Ramen", "Strawberry Juice", "Sweet and Spicy Shrimp", "Young Mage Hat", "Young Mage Robe", "Young Mage Skirt" }), CreateBirthday("Iris", "Summer", 22, new string[16] { "Amazing Elixir of Farming", "Bitter Seltzer", "Blue Rose Bouquet", "Blueberry Smoothie", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Grape Juice", "Lotus", "Pricklepop Pear", "Prickletot Pear", "Red Rose Bouquet", "Small Mana Tome", "Spicy Ramen" }, new string[15] { "Advanced Earth Fertilizer", "Advanced Fire Fertilizer", "Advanced Magic Fertilizer", "Amethyst", "Diamond", "Honeysuckle", "Lily", "Mana Fertilizer", "Oak Tree Seeds", "Ruby", "Sapphire", "Simple Earth Fertilizer", "Simple Fire Fertilizer", "Simple Magic Fertilizer", "Simple Sand Fertilizer" }), CreateBirthday("Catherine", "Summer", 25, new string[16] { "Berry Pie", "Blue Moon Fruit", "Blueberry Salad", "Carrot Cake", "Carrot Juice", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Glorite Watering Can", "Lavender", "Mushroom Pie", "Pumpkin", "Shimmeroot", "Sunite Watering Can" }, new string[15] { "Adamant Watering Can", "Beet", "Candy Cane", "Caribbean Green Soup", "Carrot", "Cooking Pot", "Copper Watering Can", "Earth Crystal", "Flan", "Iron Watering Can", "Mithril Watering Can", "Red Veggie Soup", "Shimmeroot", "Small Mana Tome", "Tadpole" }), CreateBirthday("Wornhardt", "Fall", 4, new string[12] { "Caribbean Green Soup", "Citrus Salad", "Cobb Salad", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Honeyglazed Apple", "Kale Juice", "Wooden Windmill", "Yucky Green Juice" }, new string[8] { "BLT", "Beginner's Apple Pie", "Blueberry Muffin", "Cheeseburger", "Coffee", "Lava Brew", "Roasted Turnip", "Trail Mix" }), CreateBirthday("Zaria", "Fall", 8, new string[21] { "Adamant Key", "Adamant Ring", "Elemental Cocktail", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Golden Water Rune", "Lemon Cake", "Lemon Meringue", "Lemonade", "Mithril Key", "Mithril Ring", "Sour Plum Jam", "Sour Plum Soda", "Sour Plum Sorbet", "Sour Plum Tea", "Water Dragon Tea", "Water Fruit", "Watermelon" }, new string[18] { "Coconut Water", "Copper Key", "Copper Ring", "Fairy Cherry Water", "Ghostly Great Sword", "Glass of Pure Water", "Iron Key", "Iron Ring", "Kiwi Berry", "Legendary Great Sword", "Lemon", "Magical Water", "Mana Water", "Pepper Great Sword", "Sour Plum", "Water Crystal", "Water Rune", "Zaria's Empty Bucket" }), CreateBirthday("Xyla", "Fall", 11, new string[12] { "Crown", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Globfish", "Heavy Crossbow", "Purrmaid", "Red Velvet Cake", "Snappy Plant", "Withercake" }, new string[8] { "Chess Board", "Fancy Hot Dog", "Loaded Hot Dog", "Plain Hot Dog", "Red Velvet Cupcake", "Refined Concrete", "Refined Glass", "Refined Plastic" }), CreateBirthday("Donovan", "Fall", 17, new string[19] { "Bark Fish", "Blue Rose Bouquet", "Bone Gift", "Bonemouth Bass", "Fancy Hot Dog", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Hardwood", "Hardwood Plank", "Ice Cream", "Meat", "Nachos", "Paper Bag Disguise", "Pizza", "Red Rose Bouquet", "Wood Plank" }, new string[11] { "Apple Core", "BLT", "Cheeseburger", "Hot Sauce", "Loaded Hot Dog", "Log", "Old Boot", "Steak", "Tin Can", "Vivi's Bone Dagger", "Waffles" }), CreateBirthday("Lucius", "Fall", 20, new string[26] { "Berry Cake", "Berry Pie", "Blue Moon Fruit", "Blueberry Cake", "Blueberry Pie", "Blueberry Smoothie", "Candelabra", "Enchanted Iron Bar", "Firefly Jar", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Magically Delicious Drink", "Magma Star", "Moon Cake", "Moon Cream", "Moon Cream Pie", "Moon Mailbox", "Moonfish", "Moonplant Cola", "Shadow Tuna", "Sky Ray", "Star Fruit", "Starfish" }, new string[16] { "Berry", "Blueberry", "Elven Steel Bar", "Elven Steel Ore", "Glow Ring", "Iron Bar", "Iron Ore", "Lantern", "Lightning in a Bottle", "Mana Bloom Vase", "Moonplant", "Night Small Painting", "Noodles", "Orchid", "Star Fruit Seeds", "Swirly Night Painting" }), CreateBirthday("Vivi", "Fall", 23, new string[21] { "Acorn", "Acorn Anchovy", "Acorn Milk", "Barrel Of Swords", "Berry Pie", "Carrot Sword", "Cinnamon Apple Pie", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Fruit Snacks", "Glorite Sword", "Gold Bar", "Health Woven Scarf", "Raspberry Pie", "Rel'Tar's Mark (Crossbow)", "Seared Acorn Anchovy", "Sunite Sword", "Trail Mix" }, new string[24] { "Adamant Sword", "Advanced Attack Potion", "Attack Potion", "Berry", "Chocolate Mousse Cake", "Copper Sword", "Creamy Mushroom Soup", "Crossbow", "Egg Crossbow", "Elven Crossbow", "Ghostly Great Sword", "Gold Ore", "Heavy Crossbow", "Incredible Attack Potion", "Iron Sword", "Legendary Great Sword", "Mithril Crossbow", "Mithril Sword", "Mushroom", "Mushroom Pie", "Mushroom Risotto", "Mushroom Stroganoff", "Pepper Great Sword", "Small Money Bag" }), CreateBirthday("Thorian", "Fall", 26, new string[18] { "Avocado Toast", "Blue Rose Cake", "Bubble Tea", "Caramel Creme Brulee", "Coffee", "Conjurmelon Bruschetta", "Eclair", "Fancy Grape Juice", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Indiglow Tea", "Mana Gem Jam", "Seared Lobster", "Souffle", "Wind Chime Tea" }, new string[31] { "Avocado", "Bitter Seltzer", "Black Cat Painting", "Blue Rose", "Caramel", "Cream Seltzer", "Dragon Tea", "Dreamy Painting", "Elegant Vanity Painting", "Fizzy Seltzer", "Lavender", "Lily", "Lotus", "Meatloaf Painting", "Moody Flower Painting", "Night Small Painting", "Orchid", "Organic Painting", "Peaceful Waterfall Painting", "Razor Stalk Tea", "Red Rose", "Spiced Cocoa", "Spooky Painting", "Sunflower Field Painting", "Sunny Day Small Painting", "Sunset Small Painting", "Swirly Night Painting", "Tea", "Tree Oval Painting", "Wild Mushrooms Painting", "Wildflowers Painting" }), CreateBirthday("Vaan", "Winter", 3, new string[13] { "Flan", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Large Mana Tome", "Leaf Sole", "Red Veggie Soup", "Sky Ray", "Small Mana Tome", "Two Peas and a Pod", "Water Crystal" }, new string[20] { "Adventurer Cape", "Black Cape", "Cape of Combat", "Cape of Exploration", "Cape of Farming", "Cape of Mining", "Caribbean Green Soup", "Grapes", "Gray Cape", "Green Cape", "Honey", "Lightning in a Bottle", "Majestic Cape", "Moonlight Cape", "Orange Cape", "Peas", "Red Cape", "Roasted Turnip", "Sunset Cape", "Zeppelin Pilot's Hat" }), CreateBirthday("Miyeon", "Winter", 6, new string[24] { "Angelfin", "Berry Cake", "Blue Rose Cake", "Blueberry Cake", "Bubble Tea", "Flower Flounder", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Honeycomb Cake", "Honeysuckle", "Hydrangea Cupcake", "Indiglow Tea", "Lemon Cake", "Orange Cream Cake", "Small Potted Bush", "Snappy Juice", "Strawberry Cake", "Strawberry Shortcake", "Sugar Cake", "Tea", "Wind Chime Tea" }, new string[22] { "Acorn Milk", "Angel Fish", "Angelfin", "Bear-y Cute Burger", "Cake Pop", "Cookies", "Earth Crystal", "Earth Dragon Tea", "Fire Dragon Tea", "Flan", "Fruit Cupcake", "Glass of Pure Water", "Hand Stitched Soccer Ball", "Large Mana Potion", "Large Mana Tome", "Magical Water", "Mana Water", "Small Mana Potion", "Small Mana Tome", "Strawberry Milk", "Tea Leaves", "Water Dragon Tea" }), CreateBirthday("Jun", "Winter", 11, new string[19] { "Berry Fruit Salad", "Blueberry Salad", "Chicken Noodle Soup", "Citrus Salad", "Cobb Salad", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Hydrangea Cupcake", "Lotus", "Painted Egg", "Pickled Veggie Salad", "Potato Salad", "Sesame Rice Ball", "Spring Roll", "Tea", "Tomato Salad" }, new string[10] { "Flower Flounder", "Fried Rice", "Lavender", "Mochi", "Painter's Easel", "Sand Dollar", "Spaghetti", "Sunflower", "Tea Leaves", "Tulip" }), CreateBirthday("Kai", "Winter", 17, new string[22] { "Egg Tarts", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Golden Fishing Rod", "Grilled Crab", "Kelp Eel", "Large Fishing Net", "Lemonade", "Lobster Bisque", "Lobster Roll", "Lobster Sushi", "Mithril Ring", "Neapolitan Ice Cream", "Poke Bowl", "Ribbon Eel", "Seared Lobster", "Seaweed Ice Cream", "Tropical Punch", "Very Good Fishing Rod" }, new string[16] { "Adamant Ring", "Advanced Fish Bait", "Basic Fish Bait", "Basic Fishing Rod", "Blazeel", "Copper Ring", "Crab", "Eel", "Egg", "Electric Eel", "Glass of Pure Water", "Iron Ring", "Lobster", "Sand Dollar", "Seaweed", "Small Fishing Net" }), CreateBirthday("Anne", "Winter", 21, new string[12] { "Cheesecake", "Diamond", "Fancy Grape Juice", "Fancy Hot Dog", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Persian Love Cake", "Shimmeroot", "Small Money Bag" }, new string[12] { "Amethyst", "Black Forest Cake", "Eclair", "Gold Bar", "Gold Ore", "Raspberry", "Red Velvet Cake", "Red Velvet Cupcake", "Ruby", "Sapphire", "Souffle", "Wheat" }), CreateBirthday("Kitty", "Winter", 23, new string[31] { "California Roll", "Cod Sashimi", "Crab Roll", "Duorado Double Roll", "Eel Roll", "Electric Eel Sushi", "Fish Taco", "Fish Tempura", "Fish and Chips", "Forgotten Bouquet of Hyacinth", "Forgotten Bouquet of Orchids", "Forgotten Bouquet of Peonies", "Forgotten Bouquet of Roses", "Forgotten Bouquet of Tulips", "Ghosthead Tuna Sashimi", "Ghoul Fish Roll", "Jack'o'fin Sashimi", "Lobster Roll", "Lobster Sushi", "Pufferfish Sashimi", "Rainbow Roll", "Red Snapper Sashimi", "Redfinned Pincher Sushi Roll", "Salmon Roll", "Sandstone Sashimi", "Spiked Salmon Sashimi", "Spring Roll", "Swordfish Sashimi", "Tiger Roll", "Tuna Sashimi", "Viperfish Sashimi" }, new string[13] { "Amethyst", "Animal Food", "Apple Pie", "Cinnamon Apple Pie", "Diamond", "Gear Maiden Wig", "Mochi", "Mochi Covered Strawberry", "Ruby", "Sandstone Fish", "Sapphire", "Wool", "XBK One Chance_ Romance Record" }) }; } private static NPCBirthday CreateBirthday(string name, string season, int day, string[] loved, string[] liked) { NPCBirthday nPCBirthday = new NPCBirthday(name, season, day); nPCBirthday.LovedGifts.AddRange(loved); nPCBirthday.LikedGifts.AddRange(liked); return nPCBirthday; } public static List GetBirthdaysForDate(string season, int day) { string season2 = season; return _allBirthdays.Where((NPCBirthday b) => b.IsBirthday(season2, day)).ToList(); } public static List GetBirthdaysForSeason(string season) { string season2 = season; return (from b in _allBirthdays where string.Equals(b.Season, season2, StringComparison.OrdinalIgnoreCase) orderby b.Day select b).ToList(); } public static NPCBirthday GetBirthday(string npcName) { string npcName2 = npcName; return _allBirthdays.FirstOrDefault((NPCBirthday b) => string.Equals(b.NPCName, npcName2, StringComparison.OrdinalIgnoreCase)); } public static List GetRandomGiftSuggestions(string npcName, int count = 3) { NPCBirthday birthday = GetBirthday(npcName); List suggestions = new List(); Random random = new Random(); if (birthday != null) { List list = new List(); if (birthday.LovedGifts.Count > 0) { list.AddRange(birthday.LovedGifts); } else if (birthday.LikedGifts.Count > 0) { list.AddRange(birthday.LikedGifts); } else { list.AddRange(UniversalLoved); } List source = list.OrderBy((string x) => random.Next()).ToList(); suggestions.AddRange(source.Take(count)); } while (suggestions.Count < count) { string text = (from u in UniversalLoved.Concat(UniversalLiked).ToList() where !suggestions.Contains(u) select u into x orderby random.Next() select x).FirstOrDefault(); if (text == null) { break; } suggestions.Add(text); } return suggestions; } } public class NPCBirthday { public string NPCName { get; set; } public string Season { get; set; } public int Day { get; set; } public List LovedGifts { get; set; } = new List(); public List LikedGifts { get; set; } = new List(); public NPCBirthday() { } public NPCBirthday(string npcName, string season, int day) { NPCName = npcName; Season = season; Day = day; } public bool IsBirthday(string currentSeason, int currentDay) { if (string.Equals(Season, currentSeason, StringComparison.OrdinalIgnoreCase)) { return Day == currentDay; } return false; } } public class GiftTrackingData { public string CharacterName { get; set; } public int Year { get; set; } public string Season { get; set; } public int Day { get; set; } public HashSet GiftedNPCs { get; set; } = new HashSet(); public GiftTrackingData() { } public GiftTrackingData(string characterName, int year, string season, int day) { CharacterName = characterName; Year = year; Season = season; Day = day; } public bool HasGifted(string npcName) { return GiftedNPCs.Contains(npcName); } public void MarkGifted(string npcName) { GiftedNPCs.Add(npcName); } public bool IsSameDay(int year, string season, int day) { if (Year == year && string.Equals(Season, season, StringComparison.OrdinalIgnoreCase)) { return Day == day; } return false; } } public class BirthdayDisplayInfo { public string NPCName { get; set; } public bool HasBeenGifted { get; set; } public string GiftHint { get; set; } public List AllLovedGifts { get; set; } = new List(); public List AllLikedGifts { get; set; } = new List(); public BirthdayDisplayInfo(string npcName, bool gifted, string giftHint = "") { NPCName = npcName; HasBeenGifted = gifted; GiftHint = giftHint; } public BirthdayDisplayInfo(string npcName, bool gifted, string giftHint, List lovedGifts, List likedGifts) { NPCName = npcName; HasBeenGifted = gifted; GiftHint = giftHint; AllLovedGifts = lovedGifts ?? new List(); AllLikedGifts = likedGifts ?? new List(); } } public class BirthdayManager { private GiftTrackingData _giftTracking; private List _todaysBirthdays = new List(); private string _currentCharacter; private FavoriteGiftStore _favoriteGiftStore; private string _cachedDateString = ""; private string _lastCheckedDateKey = ""; private float _stalenessCheckTimer; private const float STALENESS_CHECK_INTERVAL = 10f; private static readonly Random _random = new Random(); private static bool _reflectionInitialized = false; private static Type _npcManagerType; private static Type _npcGiftTableType; private static PropertyInfo _npcManagerInstanceProp; private static FieldInfo _npcsDictField; private static FieldInfo _birthDayField; private static FieldInfo _birthMonthField; private static FieldInfo _love2Field; private static FieldInfo _like2Field; private static FieldInfo _gaveGiftForDayField; private static FieldInfo _giftTableField; private static bool _useGameData = false; private string _statusMessage = ""; private float _statusMessageTimer; private const float STATUS_MESSAGE_DURATION = 3f; public List TodaysBirthdays => _todaysBirthdays; public bool HasBirthdays => _todaysBirthdays.Count > 0; public bool HasUngiftedBirthdays => _todaysBirthdays.Any((BirthdayDisplayInfo b) => !b.HasBeenGifted); public string StatusMessage => _statusMessage; public bool HasStatusMessage { get { if (!string.IsNullOrEmpty(_statusMessage)) { return _statusMessageTimer > 0f; } return false; } } public string CurrentDateFormatted => _cachedDateString; public event Action OnBirthdaysUpdated; public void UpdateStatusMessage(float deltaTime) { if (_statusMessageTimer > 0f) { _statusMessageTimer -= deltaTime; if (_statusMessageTimer <= 0f) { _statusMessage = ""; } } } public void CheckForDateChange(float deltaTime) { _stalenessCheckTimer += deltaTime; if (_stalenessCheckTimer < 10f) { return; } _stalenessCheckTimer = 0f; try { var (num, text, num2) = GetCurrentDate(); if (string.IsNullOrEmpty(text)) { return; } string text2 = $"{num}_{text}_{num2}"; if (text2 != _lastCheckedDateKey) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayManager] Date changed to {text} {num2} — refreshing birthdays"); } _lastCheckedDateKey = text2; CheckTodaysBirthdays(); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("[BirthdayManager] Date check: " + ex.Message)); } } } public void SetStatusMessage(string message) { _statusMessage = message; _statusMessageTimer = 3f; } private void InitializeReflectionCache() { if (_reflectionInitialized) { return; } _reflectionInitialized = true; try { _npcManagerType = AccessTools.TypeByName("Wish.NPCManager"); if (_npcManagerType == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)"[BirthdayManager] NPCManager type not found - using cache"); } 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"); _npcGiftTableType = AccessTools.TypeByName("Wish.NPCGiftTable"); if (_npcGiftTableType != null) { _birthDayField = AccessTools.Field(_npcGiftTableType, "birthDay"); _birthMonthField = AccessTools.Field(_npcGiftTableType, "birthMonth"); _love2Field = AccessTools.Field(_npcGiftTableType, "love2"); _like2Field = AccessTools.Field(_npcGiftTableType, "like2"); } Type type2 = AccessTools.TypeByName("Wish.NPCAI"); if (type2 != null) { _gaveGiftForDayField = AccessTools.Field(type2, "gaveGiftForDay"); _giftTableField = AccessTools.Field(type2, "giftTable"); } _useGameData = _npcManagerInstanceProp != null && _npcsDictField != null && _birthDayField != null && _birthMonthField != null && _giftTableField != null; if (_useGameData) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)"[BirthdayManager] Game data API initialized - using NPCGiftTable for birthdays"); } return; } if (_npcManagerInstanceProp == null) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)"[BirthdayManager][Reflect] NPCManager.Instance property not found - check game version"); } } if (_npcsDictField == null) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)"[BirthdayManager][Reflect] NPCManager._npcs field not found - check game version"); } } if (_birthDayField == null) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogWarning((object)"[BirthdayManager][Reflect] NPCGiftTable.birthDay field not found - check game version"); } } if (_birthMonthField == null) { ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogWarning((object)"[BirthdayManager][Reflect] NPCGiftTable.birthMonth field not found - check game version"); } } if (_giftTableField == null) { ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogWarning((object)"[BirthdayManager][Reflect] NPCAI.giftTable field not found - check game version"); } } ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogWarning((object)"[BirthdayManager] Falling back to hardcoded birthday cache (game API unavailable)"); } } catch (Exception ex) { ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogDebug((object)("[BirthdayManager] Error initializing game data API: " + ex.Message)); } _useGameData = false; } } public void ManualRefresh() { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[BirthdayManager] Manual refresh triggered"); } CheckTodaysBirthdays(); SetStatusMessage($"Refreshed! Found {_todaysBirthdays.Count} birthday(s)"); } public void CheckTodaysBirthdays() { _todaysBirthdays.Clear(); try { var (num, text, num2) = GetCurrentDate(logDate: true); if (string.IsNullOrEmpty(text)) { return; } _cachedDateString = $"{text} {num2:D2}"; _lastCheckedDateKey = $"{num}_{text}_{num2}"; string currentCharacterName = GetCurrentCharacterName(); if (_giftTracking == null || !_giftTracking.IsSameDay(num, text, num2) || _currentCharacter != currentCharacterName) { _giftTracking = new GiftTrackingData(currentCharacterName, num, text, num2); _currentCharacter = currentCharacterName; _favoriteGiftStore = FavoriteGiftStore.Load(currentCharacterName); } List nPCsWithBirthdayToday = GetNPCsWithBirthdayToday(text, num2); HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (NPCBirthday item in nPCsWithBirthdayToday) { string npcName = item.NPCName ?? ""; string text2 = NormalizeNpcName(npcName); if (string.IsNullOrEmpty(text2)) { continue; } text2 = FuzzyMatchNpcName(text2, BirthdayCache.AllBirthdays.Select((NPCBirthday b) => b.NPCName)); if (hashSet.Add(text2)) { bool flag = false; if (_gaveGiftForDayField != null && _useGameData) { flag = CheckGaveGiftForDay(npcName) || CheckGaveGiftForDay(text2); } if (!flag) { flag = _giftTracking.HasGifted(text2) || _giftTracking.HasGifted(npcName); } string text3; if (item.LovedGifts.Count > 0) { List values = TakeRandom(item.LovedGifts, 3); text3 = "Loves: " + string.Join(", ", values); } else { List randomGiftSuggestions = BirthdayCache.GetRandomGiftSuggestions(item.NPCName); text3 = ((randomGiftSuggestions.Count > 0) ? ("Loves: " + string.Join(", ", randomGiftSuggestions)) : ""); } if (_favoriteGiftStore != null && _favoriteGiftStore.TryGetFavorite(text2, out string giftName)) { text3 = (string.IsNullOrWhiteSpace(text3) ? ("Favorite: " + giftName) : ("Favorite: " + giftName + " | " + text3)); } _todaysBirthdays.Add(new BirthdayDisplayInfo(text2, flag, text3, item.LovedGifts.ToList(), item.LikedGifts.ToList())); } } ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayManager] Found {_todaysBirthdays.Count} birthdays on {text} {num2}"); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("[BirthdayManager] Error checking birthdays: " + ex.Message)); } } this.OnBirthdaysUpdated?.Invoke(); } private static List TakeRandom(IList source, int count) { if (source == null || source.Count == 0) { return new List(); } List list = new List(source); if (list.Count <= count) { return list; } for (int num = list.Count - 1; num > 0; num--) { int index = _random.Next(num + 1); string value = list[num]; list[num] = list[index]; list[index] = value; } list.RemoveRange(count, list.Count - count); return list; } public void MarkGifted(string npcName) { string npcName2 = npcName; if (_giftTracking == null) { return; } npcName2 = NormalizeNpcName(npcName2); if (!string.IsNullOrEmpty(npcName2)) { _giftTracking.MarkGifted(npcName2); BirthdayDisplayInfo birthdayDisplayInfo = _todaysBirthdays.FirstOrDefault((BirthdayDisplayInfo b) => b.NPCName == npcName2); if (birthdayDisplayInfo != null) { birthdayDisplayInfo.HasBeenGifted = true; } this.OnBirthdaysUpdated?.Invoke(); } } public void TryRecordLovedGift(string npcName, object giftedItem) { if (giftedItem == null || _favoriteGiftStore == null || _todaysBirthdays == null) { return; } string normalizedName = NormalizeNpcName(npcName); if (string.IsNullOrEmpty(normalizedName)) { return; } BirthdayDisplayInfo birthdayDisplayInfo = _todaysBirthdays.FirstOrDefault((BirthdayDisplayInfo b) => string.Equals(b.NPCName, normalizedName, StringComparison.OrdinalIgnoreCase)); if (birthdayDisplayInfo == null || birthdayDisplayInfo.AllLovedGifts == null || birthdayDisplayInfo.AllLovedGifts.Count == 0) { return; } string giftedName = GetItemName(giftedItem); if (!string.IsNullOrWhiteSpace(giftedName) && birthdayDisplayInfo.AllLovedGifts.Any((string g) => !string.IsNullOrWhiteSpace(g) && string.Equals(g.Trim(), giftedName.Trim(), StringComparison.OrdinalIgnoreCase))) { _favoriteGiftStore.SetFavorite(normalizedName, giftedName); _favoriteGiftStore.Save(); ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[BirthdayManager] Recorded favorite gift for " + normalizedName + ": " + giftedName)); } } } private 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 string FuzzyMatchNpcName(string rawName, IEnumerable knownNames) { if (string.IsNullOrWhiteSpace(rawName)) { return rawName; } string normalized = NormalizeNpcName(rawName); string text = knownNames.FirstOrDefault((string k) => string.Equals(k, normalized, StringComparison.OrdinalIgnoreCase)); if (text != null) { return text; } string text2 = knownNames.FirstOrDefault((string k) => k.IndexOf(normalized, StringComparison.OrdinalIgnoreCase) >= 0 || normalized.IndexOf(k, StringComparison.OrdinalIgnoreCase) >= 0); if (text2 != null) { return text2; } string text3 = null; int num = int.MaxValue; foreach (string knownName in knownNames) { int num2 = LevenshteinDistance(normalized, knownName); if (num2 < num) { num = num2; text3 = knownName; } } int num3 = ((((text3 != null) ? Math.Min(normalized.Length, text3.Length) : normalized.Length) <= 4) ? 1 : 2); if (num > num3 || text3 == null) { return normalized; } return text3; } private static int LevenshteinDistance(string a, string b) { if (string.IsNullOrEmpty(a)) { return b?.Length ?? 0; } if (string.IsNullOrEmpty(b)) { return a.Length; } string text = a.ToLowerInvariant(); string text2 = b.ToLowerInvariant(); int[,] array = new int[text.Length + 1, text2.Length + 1]; for (int i = 0; i <= text.Length; i++) { array[i, 0] = i; } for (int j = 0; j <= text2.Length; j++) { array[0, j] = j; } for (int k = 1; k <= text.Length; k++) { for (int l = 1; l <= text2.Length; l++) { array[k, l] = ((text[k - 1] == text2[l - 1]) ? array[k - 1, l - 1] : (1 + Math.Min(array[k - 1, l - 1], Math.Min(array[k - 1, l], array[k, l - 1])))); } } return array[text.Length, text2.Length]; } public void ResetForNewCharacter(string newCharacterName) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[BirthdayManager] Resetting for new character: " + newCharacterName)); } _currentCharacter = newCharacterName; _giftTracking = null; _favoriteGiftStore = FavoriteGiftStore.Load(newCharacterName); _todaysBirthdays.Clear(); this.OnBirthdaysUpdated?.Invoke(); } public void DebugAddTestBirthday(string npcName, string giftHint = "") { string npcName2 = npcName; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[DEBUG] Adding test birthday for: " + npcName2)); } if (_todaysBirthdays.Any((BirthdayDisplayInfo b) => b.NPCName == npcName2)) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[DEBUG] " + npcName2 + " already has a birthday entry")); } return; } _todaysBirthdays.Add(new BirthdayDisplayInfo(npcName2, gifted: false, giftHint)); if (_giftTracking == null) { (int year, string season, int day) currentDate = GetCurrentDate(); int item = currentDate.year; string item2 = currentDate.season; int item3 = currentDate.day; _giftTracking = new GiftTrackingData("Debug", item, item2, item3); } this.OnBirthdaysUpdated?.Invoke(); } public void DebugClearBirthdays() { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[DEBUG] Clearing all birthdays"); } _todaysBirthdays.Clear(); _giftTracking = null; this.OnBirthdaysUpdated?.Invoke(); } public void DebugLogState() { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[DEBUG] === Birthday Manager State ==="); } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)("[DEBUG] Current Character: " + (_currentCharacter ?? "None"))); } ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)$"[DEBUG] Birthdays Today: {_todaysBirthdays.Count}"); } foreach (BirthdayDisplayInfo todaysBirthday in _todaysBirthdays) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogInfo((object)$"[DEBUG] - {todaysBirthday.NPCName} (Gifted: {todaysBirthday.HasBeenGifted})"); } } if (_giftTracking != null) { ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogInfo((object)$"[DEBUG] Gift Tracking: {_giftTracking.Season} {_giftTracking.Day}, Year {_giftTracking.Year}"); } ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)("[DEBUG] Gifted NPCs: " + string.Join(", ", _giftTracking.GiftedNPCs))); } } ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)"[DEBUG] === End State ==="); } } public void DebugLoadAllBirthdays() { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"[DEBUG] Loading ALL NPC birthdays from cache..."); } _todaysBirthdays.Clear(); foreach (NPCBirthday allBirthday in BirthdayCache.AllBirthdays) { string npcName = $"{allBirthday.NPCName} ({allBirthday.Season} {allBirthday.Day})"; _todaysBirthdays.Add(new BirthdayDisplayInfo(npcName, gifted: false)); } ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[DEBUG] Loaded {_todaysBirthdays.Count} NPC birthdays from cache"); } this.OnBirthdaysUpdated?.Invoke(); } public void DebugDumpNPCInfo(string targetNpcName = "Lynn") { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("[DEBUG] ========== DUMPING NPC INFO: " + targetNpcName + " ==========")); } try { Type type = AccessTools.TypeByName("Wish.NPCManager"); if (type == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"[DEBUG] NPCManager type not found"); } return; } object singletonInstance = GetSingletonInstance(type); if (singletonInstance == null) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogWarning((object)"[DEBUG] NPCManager instance not found"); } return; } object obj = (AccessTools.Field(type, "_npcs") ?? AccessTools.Field(type, "npcs"))?.GetValue(singletonInstance); if (obj == null) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogWarning((object)"[DEBUG] NPCs dictionary not found"); } } else { if (!(obj.GetType().GetProperty("Values")?.GetValue(obj) is IEnumerable enumerable)) { return; } foreach (object item in enumerable) { if (item == null) { continue; } Type type2 = item.GetType(); string text = (AccessTools.Property(type2, "NPCName") ?? AccessTools.Property(type2, "npcName") ?? AccessTools.Property(type2, "Name"))?.GetValue(item)?.ToString(); if (string.IsNullOrEmpty(text) || !text.Equals(targetNpcName, StringComparison.OrdinalIgnoreCase)) { continue; } ManualLogSource log5 = Plugin.Log; if (log5 != null) { log5.LogInfo((object)("[DEBUG] Found " + text + "! Type: " + type2.FullName)); } ManualLogSource log6 = Plugin.Log; if (log6 != null) { log6.LogInfo((object)"[DEBUG] --- PROPERTIES ---"); } PropertyInfo[] properties = type2.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (PropertyInfo propertyInfo in properties) { try { string text2 = propertyInfo.GetValue(item)?.ToString() ?? "null"; if (text2.Length > 100) { text2 = text2.Substring(0, 100) + "..."; } ManualLogSource log7 = Plugin.Log; if (log7 != null) { log7.LogInfo((object)("[DEBUG] " + propertyInfo.Name + " (" + propertyInfo.PropertyType.Name + ") = " + text2)); } } catch (Exception ex) { ManualLogSource log8 = Plugin.Log; if (log8 != null) { log8.LogInfo((object)("[DEBUG] " + propertyInfo.Name + " (" + propertyInfo.PropertyType.Name + ") = ERROR: " + ex.Message)); } } } ManualLogSource log9 = Plugin.Log; if (log9 != null) { log9.LogInfo((object)"[DEBUG] --- FIELDS ---"); } FieldInfo[] fields = type2.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { try { string text3 = fieldInfo.GetValue(item)?.ToString() ?? "null"; if (text3.Length > 100) { text3 = text3.Substring(0, 100) + "..."; } ManualLogSource log10 = Plugin.Log; if (log10 != null) { log10.LogInfo((object)("[DEBUG] " + fieldInfo.Name + " (" + fieldInfo.FieldType.Name + ") = " + text3)); } } catch (Exception ex2) { ManualLogSource log11 = Plugin.Log; if (log11 != null) { log11.LogInfo((object)("[DEBUG] " + fieldInfo.Name + " (" + fieldInfo.FieldType.Name + ") = ERROR: " + ex2.Message)); } } } FieldInfo fieldInfo2 = AccessTools.Field(type2, "giftTable"); if (fieldInfo2 != null) { object value = fieldInfo2.GetValue(item); ManualLogSource log12 = Plugin.Log; if (log12 != null) { log12.LogInfo((object)("[DEBUG] giftTable (field) = " + (value?.GetType().Name ?? "null"))); } } ManualLogSource log13 = Plugin.Log; if (log13 != null) { log13.LogInfo((object)("[DEBUG] ========== END " + text + " ==========")); } return; } ManualLogSource log14 = Plugin.Log; if (log14 != null) { log14.LogWarning((object)("[DEBUG] NPC '" + targetNpcName + "' not found in NPCManager")); } } } catch (Exception ex3) { ManualLogSource log15 = Plugin.Log; if (log15 != null) { log15.LogError((object)("[DEBUG] Error dumping NPC info: " + ex3.Message)); } } } private (int year, string season, int day) GetCurrentDate(bool logDate = false) { try { Type type = AccessTools.TypeByName("Wish.GameSave"); if (type != null) { object obj = AccessTools.Property(type, "Instance")?.GetValue(null); if (obj != null && AccessTools.Property(type, "CurrentSave")?.GetValue(obj) == null) { return (1, "Spring", 1); } } Type type2 = AccessTools.TypeByName("Wish.DayCycle"); if (type2 != null) { PropertyInfo propertyInfo = AccessTools.Property(type2, "Year"); PropertyInfo propertyInfo2 = AccessTools.Property(type2, "MonthDay"); int num = ((!(propertyInfo != null)) ? 1 : ((int)propertyInfo.GetValue(null))); int num2 = ((!(propertyInfo2 != null)) ? 1 : ((int)propertyInfo2.GetValue(null))); object singletonInstance = GetSingletonInstance(type2); string text = "Spring"; if (singletonInstance != null) { PropertyInfo propertyInfo3 = AccessTools.Property(type2, "Season"); if (propertyInfo3 != null) { text = propertyInfo3.GetValue(singletonInstance)?.ToString() ?? "Spring"; } } if (logDate) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayManager] Got date from DayCycle: Year {num}, {text} {num2}"); } } return (num, text, num2); } } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)("[BirthdayManager] Date unavailable (may be on LoadScreen): " + ex.Message)); } } return (1, "Spring", 1); } private object GetSingletonInstance(Type targetType) { try { Type type = AccessTools.TypeByName("Wish.SingletonBehaviour`1"); if (type != null) { return AccessTools.Property(type.MakeGenericType(targetType), "Instance")?.GetValue(null); } return AccessTools.Property(targetType, "Instance")?.GetValue(null); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[BirthdayManager] GetSingletonInstance: " + ex.Message)); } return null; } } private string GetCurrentCharacterName() { try { Type type = AccessTools.TypeByName("Wish.GameSave"); if (type != null) { object singletonInstance = GetSingletonInstance(type); if (singletonInstance != null) { return (AccessTools.Property(type, "CurrentCharacter")?.GetValue(singletonInstance))?.ToString() ?? "Unknown"; } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[BirthdayManager] Error getting character name: " + ex.Message)); } } return "Unknown"; } private List GetNPCsWithBirthdayToday(string season, int day) { InitializeReflectionCache(); if (_useGameData) { List birthdaysFromGameData = GetBirthdaysFromGameData(season, day); if (birthdaysFromGameData.Count > 0) { foreach (NPCBirthday item in birthdaysFromGameData) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)$"[BirthdayManager] Found birthday (game data): {item.NPCName} ({item.Season} {item.Day})"); } } return birthdaysFromGameData; } } List birthdaysForDate = BirthdayCache.GetBirthdaysForDate(season, day); foreach (NPCBirthday item2 in birthdaysForDate) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogInfo((object)$"[BirthdayManager] Found birthday (cache): {item2.NPCName} ({item2.Season} {item2.Day})"); } } return birthdaysForDate; } private List GetBirthdaysFromGameData(string season, int day) { List list = new List(); try { object obj = _npcManagerInstanceProp?.GetValue(null); if (obj == null) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)"[BirthdayManager] NPCManager instance is null"); } return list; } object obj2 = _npcsDictField?.GetValue(obj); if (obj2 == null) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogDebug((object)"[BirthdayManager] NPCs dictionary is null"); } return list; } if (!(obj2.GetType().GetProperty("Values")?.GetValue(obj2) is IEnumerable enumerable)) { return list; } foreach (object item in enumerable) { if (item == null) { continue; } try { Type type = item.GetType(); string text = (AccessTools.Property(type, "NPCName") ?? AccessTools.Property(type, "ActualNPCName") ?? AccessTools.Property(type, "OriginalName") ?? AccessTools.Property(type, "npcName") ?? AccessTools.Property(type, "Name"))?.GetValue(item)?.ToString(); if (string.IsNullOrEmpty(text)) { continue; } object obj3 = _giftTableField?.GetValue(item); if (obj3 != null) { int num = 0; object obj4 = null; if (_birthDayField != null) { num = (int)_birthDayField.GetValue(obj3); } if (_birthMonthField != null) { obj4 = _birthMonthField.GetValue(obj3); } string text2 = obj4?.ToString() ?? ""; if (num == day && string.Equals(text2, season, StringComparison.OrdinalIgnoreCase)) { NPCBirthday nPCBirthday = new NPCBirthday(text, text2, num); GetGiftsFromGameData(obj3, nPCBirthday); list.Add(nPCBirthday); } } } catch (Exception ex) { ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogDebug((object)("[BirthdayManager] Error checking NPC: " + ex.Message)); } } } } catch (Exception ex2) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogDebug((object)("[BirthdayManager] Error getting birthdays from game: " + ex2.Message)); } } return list; } private void GetGiftsFromGameData(object giftTable, NPCBirthday birthday) { try { if (_love2Field != null && _love2Field.GetValue(giftTable) is IList list) { foreach (object item in list) { string itemNameFromId = GetItemNameFromId(item); if (!string.IsNullOrEmpty(itemNameFromId)) { birthday.LovedGifts.Add(itemNameFromId); } } } if (!(_like2Field != null) || !(_like2Field.GetValue(giftTable) is IList list2)) { return; } foreach (object item2 in list2) { string itemNameFromId2 = GetItemNameFromId(item2); if (!string.IsNullOrEmpty(itemNameFromId2)) { birthday.LikedGifts.Add(itemNameFromId2); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[BirthdayManager] Error getting gifts from game data: " + ex.Message)); } } } private string GetItemNameFromId(object itemIdOrObj) { try { int num2; if (itemIdOrObj is int num) { num2 = num; } else { if (itemIdOrObj == null) { return null; } Type type = itemIdOrObj.GetType(); FieldInfo field = type.GetField("id", BindingFlags.Instance | BindingFlags.Public); PropertyInfo propertyInfo = ((field == null) ? (type.GetProperty("id") ?? type.GetProperty("ID")) : null); if (field != null) { num2 = (int)field.GetValue(itemIdOrObj); } else if (propertyInfo != null) { object value = propertyInfo.GetValue(itemIdOrObj); num2 = ((value is int num3) ? num3 : Convert.ToInt32(value)); } else { num2 = Convert.ToInt32(itemIdOrObj); } } Type type2 = AccessTools.TypeByName("Wish.ItemInfoDatabase"); if (type2 != null) { object obj = type2.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)?.GetValue(null); if (obj != null) { object obj2 = AccessTools.Field(type2, "allItemSellInfos")?.GetValue(obj); if (obj2 != null) { MethodInfo method = obj2.GetType().GetMethod("ContainsKey", new Type[1] { typeof(int) }); MethodInfo method2 = obj2.GetType().GetMethod("get_Item", new Type[1] { typeof(int) }); if (method != null && method2 != null && (bool)method.Invoke(obj2, new object[1] { num2 })) { object obj3 = method2.Invoke(obj2, new object[1] { num2 }); if (obj3 != null) { FieldInfo field2 = obj3.GetType().GetField("name", BindingFlags.Instance | BindingFlags.Public); PropertyInfo propertyInfo2 = ((field2 == null) ? (obj3.GetType().GetProperty("name") ?? obj3.GetType().GetProperty("Name")) : null); if (field2 != null) { return field2.GetValue(obj3) as string; } if (propertyInfo2 != null) { return propertyInfo2.GetValue(obj3) as string; } } } } } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[BirthdayManager] Error getting item name: " + ex.Message)); } } return null; } private bool CheckGaveGiftForDay(string npcName) { try { if (_gaveGiftForDayField == null) { return false; } object obj = _npcManagerInstanceProp?.GetValue(null); if (obj == null) { return false; } object obj2 = _npcsDictField?.GetValue(obj); if (obj2 == null) { return false; } MethodInfo method = obj2.GetType().GetMethod("TryGetValue"); if (method != null) { object[] array = new object[2] { npcName, null }; if ((bool)method.Invoke(obj2, array) && array[1] != null) { object obj3 = array[1]; return (bool)_gaveGiftForDayField.GetValue(obj3); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogDebug((object)("[BirthdayManager] Error checking gift status for " + npcName + ": " + ex.Message)); } } return false; } private void GetNPCGiftPreferences(object npc, Type npcType, NPCBirthday birthday) { try { PropertyInfo propertyInfo = AccessTools.Property(npcType, "LovedGifts") ?? AccessTools.Property(npcType, "lovedGifts"); PropertyInfo propertyInfo2 = AccessTools.Property(npcType, "LikedGifts") ?? AccessTools.Property(npcType, "likedGifts"); if (propertyInfo != null && propertyInfo.GetValue(npc) is IEnumerable enumerable) { foreach (object item in enumerable) { string itemName = GetItemName(item); if (!string.IsNullOrEmpty(itemName)) { birthday.LovedGifts.Add(itemName); } } } if (!(propertyInfo2 != null) || !(propertyInfo2.GetValue(npc) is IEnumerable enumerable2)) { return; } foreach (object item2 in enumerable2) { string itemName2 = GetItemName(item2); if (!string.IsNullOrEmpty(itemName2)) { birthday.LikedGifts.Add(itemName2); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[BirthdayManager] Error getting gift preferences: " + ex.Message)); } } } private string GetItemName(object item) { if (item == null) { return null; } if (item is string result) { return result; } if (item is int num) { string itemNameFromId = GetItemNameFromId(num); if (string.IsNullOrEmpty(itemNameFromId)) { return $"Item #{num}"; } return itemNameFromId; } return (AccessTools.Property(item.GetType(), "name") ?? AccessTools.Property(item.GetType(), "Name"))?.GetValue(item)?.ToString(); } private string BuildGiftHint(NPCBirthday npc) { List list = new List(); if (npc.LovedGifts.Count > 0) { string text = string.Join(", ", npc.LovedGifts.Take(3)); list.Add("Loves: " + text); } if (npc.LikedGifts.Count > 0 && list.Count == 0) { string text2 = string.Join(", ", npc.LikedGifts.Take(3)); list.Add("Likes: " + text2); } if (list.Count <= 0) { return ""; } return list[0]; } } internal sealed class FavoriteGiftStore { private readonly string _characterName; private readonly Dictionary _favoritesByNpc; private FavoriteGiftStore(string characterName) { _characterName = (string.IsNullOrWhiteSpace(characterName) ? "Unknown" : characterName); _favoritesByNpc = new Dictionary(StringComparer.OrdinalIgnoreCase); } public static FavoriteGiftStore Load(string characterName) { FavoriteGiftStore favoriteGiftStore = new FavoriteGiftStore(characterName); string path = GetPath(characterName); try { if (!File.Exists(path)) { return favoriteGiftStore; } string[] array = File.ReadAllLines(path); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text)) { continue; } int num = text.IndexOf('\t'); if (num > 0 && num < text.Length - 1) { string text2 = text.Substring(0, num); string value = text.Substring(num + 1); if (!string.IsNullOrWhiteSpace(text2) && !string.IsNullOrWhiteSpace(value)) { favoriteGiftStore._favoritesByNpc[text2] = value; } } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[Favorites] Load failed for '" + characterName + "': " + ex.Message)); } } return favoriteGiftStore; } public bool TryGetFavorite(string npcName, out string giftName) { giftName = null; if (string.IsNullOrWhiteSpace(npcName)) { return false; } if (_favoritesByNpc.TryGetValue(npcName, out giftName)) { return !string.IsNullOrWhiteSpace(giftName); } return false; } public void SetFavorite(string npcName, string giftName) { if (!string.IsNullOrWhiteSpace(npcName) && !string.IsNullOrWhiteSpace(giftName)) { _favoritesByNpc[npcName] = giftName.Trim(); } } public void Save() { string path = GetPath(_characterName); string directoryName = Path.GetDirectoryName(path); try { if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } string text = path + ".tmp"; StringBuilder stringBuilder = new StringBuilder(); foreach (KeyValuePair item in _favoritesByNpc) { stringBuilder.Append(item.Key); stringBuilder.Append('\t'); stringBuilder.Append(item.Value); stringBuilder.AppendLine(); } try { File.WriteAllText(text, stringBuilder.ToString()); if (File.Exists(path)) { File.Delete(path); } File.Move(text, path); } finally { if (File.Exists(text)) { File.Delete(text); } } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("[Favorites] Save failed for '" + _characterName + "': " + ex.Message)); } } } private static string GetPath(string characterName) { string text = SanitizeFileName(characterName); return Path.Combine(Paths.ConfigPath, "BirthdayReminder", "Data", text + "_favorites.txt"); } private static string SanitizeFileName(string value) { if (string.IsNullOrWhiteSpace(value)) { return "Unknown"; } char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { value = value.Replace(oldChar, '_'); } return value.Trim(); } } }