using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using HutongGames.PlayMaker; using HutongGames.PlayMaker.Actions; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using QuestPlaymakerActions; using Silksong.DataManager; using Silksong.UnityHelper.Extensions; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("QuestMod")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("2.1.2.0")] [assembly: AssemblyInformationalVersion("2.1.2+a8f3b852f1aa407b1d15070abe70adfde721d8c2")] [assembly: AssemblyProduct("QuestMod")] [assembly: AssemblyTitle("QuestMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.1.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] [Microsoft.CodeAnalysis.Embedded] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace Microsoft.CodeAnalysis { [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace QuestMod { public struct QuestInfo { public string Name; public string DisplayName; public bool IsAccepted; public bool IsCompleted; } public struct ChainInfo { public string ChainName; public string DisplayName; public string[] Steps; public int CurrentStep; public int TotalSteps; public bool IsFullyComplete; } public static class QuestAcceptance { public static string? LastCompletionRefusal { get; internal set; } public static string GetExclusionConflict(string questName) { if (!QuestRegistry.MutuallyExclusiveQuests.TryGetValue(questName, out string value)) { return null; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null || !runtimeData.Contains(value)) { return null; } object qd = runtimeData[value]; if (QuestDataAccess.IsAccepted(qd) || QuestDataAccess.IsCompleted(qd)) { return value; } return null; } public static bool IsSequentialStageEncountered(string questName, int stageIndex) { if (!QuestRegistry.SequentialStagePdPatterns.TryGetValue(questName, out string value)) { return true; } if (PlayerData.instance == null) { return false; } string name = value.Replace("{stage}", (stageIndex + 1).ToString()); FieldInfo field = typeof(PlayerData).GetField(name); if (field == null) { return false; } object value2 = field.GetValue(PlayerData.instance); bool flag = default(bool); int num; if (value2 is bool) { flag = (bool)value2; num = 1; } else { num = 0; } return (byte)((uint)num & (flag ? 1u : 0u)) != 0; } public static bool IsChainPrereqMet(string questName) { foreach (string[] value in QuestRegistry.ChainRegistry.Values) { for (int i = 0; i < value.Length; i++) { if (value[i] != questName) { continue; } if (i == 0) { return true; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return false; } for (int j = 0; j < i; j++) { if (!runtimeData.Contains(value[j])) { return false; } if (!QuestDataAccess.IsCompleted(runtimeData[value[j]])) { return false; } } return true; } } return true; } public static string GetDisplayName(string internalName) { if (QuestRegistry.DisplayNames.TryGetValue(internalName, out string value)) { return value; } return internalName; } public static bool IsActuallyAvailable(string questName, out string reason) { reason = ""; if (QuestModPlugin.IsPureWishes) { return true; } if (!IsChainPrereqMet(questName)) { reason = "chain prereq unmet"; return false; } if (GetExclusionConflict(questName) != null) { reason = "mutually-exclusive twin active"; return false; } QuestRequirements.ExtraConditionResult extraConditionResult = QuestRequirements.EvaluateAvailableConditions(questName); if (!extraConditionResult.Pass) { reason = "availableConditions: " + (extraConditionResult.Reason ?? "(no reason)"); return false; } return true; } public static bool IsActuallyAvailable(string questName) { string reason; return IsActuallyAvailable(questName, out reason); } public static void Initialize() { QuestModPlugin.Log.LogInfo((object)"QuestAcceptance initialized"); } public static void InjectAndAcceptAllQuests() { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.Log.LogInfo((object)"InjectAndAcceptAllQuests: skipped -- legacy save, safety override not set"); return; } if (PlayerData.instance == null) { QuestModPlugin.Log.LogWarning((object)"InjectAndAcceptAllQuests: PlayerData not available yet"); return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { QuestModPlugin.Log.LogWarning((object)"InjectAndAcceptAllQuests: RuntimeData not available yet"); return; } FullQuestBase[] array = Resources.FindObjectsOfTypeAll(); int num = 0; int num2 = 0; FullQuestBase[] array2 = array; for (int i = 0; i < array2.Length; i++) { string name = ((Object)array2[i]).name; if (string.IsNullOrEmpty(name)) { continue; } if (QuestRegistry.ExcludedQuests.Contains(name)) { QuestModPlugin.Log.LogInfo((object)(" SKIP [" + name + "]: excluded")); num2++; } else { if (runtimeData.Contains(name)) { continue; } if (!QuestModPlugin.IsQuestDiscovered(name)) { QuestModPlugin.Log.LogInfo((object)(" SKIP [" + name + "]: not discovered")); num2++; continue; } if (!IsActuallyAvailable(name, out string reason)) { QuestModPlugin.Log.LogInfo((object)(" SKIP [" + name + "]: " + reason)); num2++; continue; } object anyValue = GetAnyValue(runtimeData); if (anyValue != null) { object value = QuestDataAccess.SetFields(anyValue, seen: true, accepted: true, completed: false, wasEver: false); runtimeData[name] = value; num++; } } } QuestModPlugin.Log.LogInfo((object)$"Injected {num} new quests into RuntimeData (skipped {num2} excluded, total ScriptableObjects: {array.Length})"); AcceptAllQuests(); } public static void AutoAcceptFlaggedQuests() { if (PlayerData.instance == null) { return; } if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.LogDebugInfo("AutoAcceptFlaggedQuests: skipped -- legacy save, safety override not set"); return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return; } int num = 0; HashSet seen = new HashSet(); foreach (string item in QuestPolicyStore.AutoAcceptNames()) { if (TryAutoAcceptOne(item, runtimeData, seen)) { num++; } } if (QuestModPlugin.AutoAcceptAllAvailable && QuestModPlugin.WishesMode != 0) { FullQuestBase[] array = Resources.FindObjectsOfTypeAll(); foreach (FullQuestBase val in array) { if (!((Object)(object)val == (Object)null) && TryAutoAcceptOne(((Object)val).name, runtimeData, seen)) { num++; } } } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"Auto-accepted {num} flagged quests"); QuestModToast.Show($"Auto-accepted {num} quest" + ((num == 1) ? "" : "s")); } } private static bool TryAutoAcceptOne(string name, IDictionary rt, HashSet seen) { if (string.IsNullOrEmpty(name)) { return false; } if (!seen.Add(name)) { return false; } if (QuestRegistry.ExcludedQuests.Contains(name)) { return false; } if (!IsActuallyAvailable(name)) { return false; } if (rt.Contains(name)) { object qd = rt[name]; if (QuestDataAccess.IsAccepted(qd) || QuestDataAccess.IsCompleted(qd)) { return false; } } AcceptQuest(name); return true; } public static void ForceAcceptAllQuests() { ForceAllQuestsOp(complete: false); } public static void ForceCompleteAllQuests() { ForceAllQuestsOp(complete: true); } private static void ForceAllQuestsOp(bool complete) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.Log.LogInfo((object)$"ForceAllQuestsOp(complete={complete}): skipped -- legacy save, safety override not set"); } else { if (PlayerData.instance == null) { return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return; } bool blackThreadWorld = PlayerData.instance.blackThreadWorld; FullQuestBase[] array = Resources.FindObjectsOfTypeAll(); int num = 0; FullQuestBase[] array2 = array; for (int i = 0; i < array2.Length; i++) { string name = ((Object)array2[i]).name; if (!string.IsNullOrEmpty(name) && !QuestRegistry.ExcludedQuests.Contains(name) && !runtimeData.Contains(name)) { object anyValue = GetAnyValue(runtimeData); if (anyValue != null) { object value = QuestDataAccess.SetFields(anyValue, seen: true, accepted: true, completed: false, wasEver: false); runtimeData[name] = value; num++; } } } ModifyAllQuests(runtimeData, complete, respectGates: false); PlayerData.instance.blackThreadWorld = blackThreadWorld; string arg = (complete ? "completed" : "accepted"); QuestModPlugin.Log.LogInfo((object)$"Force {arg} ALL quests (injected {num} new, total ScriptableObjects: {array.Length}), act state preserved"); } } public static void AcceptAllQuests() { if (PlayerData.instance != null) { IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData != null) { ModifyAllQuests(runtimeData, complete: false); } } } public static List GetQuestList() { List list = new List(); if (PlayerData.instance == null) { return list; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); HashSet hashSet = new HashSet(); if (runtimeData != null) { foreach (DictionaryEntry item in runtimeData) { if (item.Key is string text) { hashSet.Add(text); list.Add(new QuestInfo { Name = text, DisplayName = GetDisplayName(text), IsAccepted = QuestDataAccess.IsAccepted(item.Value), IsCompleted = QuestDataAccess.IsCompleted(item.Value) }); } } } FullQuestBase[] array = Resources.FindObjectsOfTypeAll(); foreach (FullQuestBase val in array) { if (!string.IsNullOrEmpty(((Object)val).name) && !hashSet.Contains(((Object)val).name)) { list.Add(new QuestInfo { Name = ((Object)val).name, DisplayName = GetDisplayName(((Object)val).name), IsAccepted = false, IsCompleted = false }); } } list.Sort((QuestInfo a, QuestInfo b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); return list; } public static void AcceptQuest(string name) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = name + ": legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("AcceptQuest(" + name + "): skipped (safety gate)")); return; } IDictionary dictionary; object qd; (dictionary, qd) = EnsureQuestEntry(name); if (dictionary != null) { qd = QuestDataAccess.SetFields(qd, seen: true, accepted: true, QuestDataAccess.IsCompleted(qd), QuestDataAccess.IsCompleted(qd)); dictionary[name] = qd; QuestModPlugin.LogDebugInfo("Accepted: " + name); } } public static void CompleteQuest(string name) { CompleteQuest(name, skipExtraConditions: false); } internal static void CompleteQuest(string name, bool skipExtraConditions) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = name + ": legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("CompleteQuest(" + name + "): skipped (safety gate)")); return; } if (!skipExtraConditions && QuestModPlugin.IsCustomRequirementsEnabled) { QuestRequirements.ExtraConditionResult extraConditionResult = QuestRequirements.EvaluateExtraConditions(name); if (!extraConditionResult.Pass) { LastCompletionRefusal = name + ": " + extraConditionResult.Reason; QuestModPlugin.LogDebugInfo("CompleteQuest refused: " + LastCompletionRefusal); return; } } IDictionary dictionary; object qd; (dictionary, qd) = EnsureQuestEntry(name); if (dictionary != null) { qd = QuestDataAccess.SetFields(qd, seen: true, accepted: true, completed: true, wasEver: true); dictionary[name] = qd; QuestModPlugin.LogDebugInfo("Completed: " + name); } } public static bool RemoteComplete(string name) { return RemoteComplete(name, new HashSet()); } private static bool RemoteComplete(string name, HashSet visited) { if (visited.Contains(name)) { return false; } if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = name + ": legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("RemoteComplete(" + name + "): skipped -- legacy save, safety override not set")); return false; } visited.Add(name); if (QuestModPlugin.IsCustomRequirementsEnabled) { QuestRequirements.ExtraConditionResult extraConditionResult = QuestRequirements.EvaluateExtraConditions(name); if (!extraConditionResult.Pass) { LastCompletionRefusal = name + ": " + extraConditionResult.Reason; QuestModPlugin.LogDebugInfo("RemoteComplete refused: " + LastCompletionRefusal); return false; } } bool flag = false; try { Type type = ReflectionCache.GetType("QuestManager"); Type type2 = ReflectionCache.GetType("FullQuestBase"); if (type != null && type2 != null) { object obj = AccessTools.Method(type, "GetQuest", new Type[1] { typeof(string) }, (Type[])null)?.Invoke(null, new object[1] { name }); if (obj != null) { flag |= TryDeductTargets(obj, type2, name); flag |= TryGrantReward(obj, type2, name); flag |= TryAwardAchievement(obj, type2, name); TryCascadeMarkCompleted(obj, type2, visited); try { AccessTools.Method(type, "ShowQuestCompleted", new Type[2] { type2, typeof(Action) }, (Type[])null)?.Invoke(null, new object[2] { obj, null }); } catch { } } } } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete: side effect path threw for " + name + ": " + ex.Message)); } CompleteQuest(name, skipExtraConditions: true); return flag; } private static bool IsInventoryDeductibleCounter(object counter) { if (counter == null) { return false; } Type type = counter.GetType(); while (type != null && type != typeof(object)) { if (type.Name == "CollectableItem") { return true; } type = type.BaseType; } QuestModPlugin.LogDebugInfo("IsInventoryDeductibleCounter: type '" + counter.GetType().Name + "' not a CollectableItem, skip"); return false; } private static bool TryDeductTargets(object quest, Type fqbType, string questName) { try { FieldInfo fieldInfo = null; Type type = fqbType; while (type != null && fieldInfo == null && type != typeof(object)) { fieldInfo = type.GetField("targets", BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } if (!(fieldInfo?.GetValue(quest) is Array array)) { return false; } bool result = false; for (int i = 0; i < array.Length; i++) { object value = array.GetValue(i); if (value == null) { continue; } FieldInfo? field = value.GetType().GetField("Counter"); FieldInfo field2 = value.GetType().GetField("Count"); object obj = field?.GetValue(value); int num = 0; if (field2?.GetValue(value) is int num2) { num = num2; } if (obj == null || num <= 0) { continue; } if (!IsInventoryDeductibleCounter(obj)) { QuestModPlugin.LogDebugInfo($"RemoteComplete {questName}: target {i} counter {obj.GetType().Name} is not inventory-backed; skipping deduct"); continue; } MethodInfo methodInfo = AccessTools.Method(obj.GetType(), "Consume", new Type[2] { typeof(int), typeof(bool) }, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(obj, new object[2] { num, false }); QuestModPlugin.Log.LogInfo((object)$"RemoteComplete {questName}: deducted {num} via {obj.GetType().Name}.Consume(int, bool)"); result = true; continue; } MethodInfo methodInfo2 = AccessTools.Method(obj.GetType(), "Take", new Type[2] { typeof(int), typeof(bool) }, (Type[])null); if (methodInfo2 != null) { methodInfo2.Invoke(obj, new object[2] { num, false }); QuestModPlugin.Log.LogInfo((object)$"RemoteComplete {questName}: deducted {num} via {obj.GetType().Name}.Take(int, bool)"); result = true; } else { QuestModPlugin.LogDebugInfo($"RemoteComplete {questName}: counter {obj.GetType().Name} has no Consume/Take; skipping target {i}"); } } return result; } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete " + questName + ": deduction threw: " + ex.Message)); return false; } } private static bool TryGrantReward(object quest, Type fqbType, string questName) { try { FieldInfo field = fqbType.GetField("rewardItem"); FieldInfo field2 = fqbType.GetField("rewardCount"); if (field == null || field2 == null) { return false; } object value = field.GetValue(quest); int num = 0; if (field2.GetValue(quest) is int num2) { num = num2; } if (value == null || num <= 0) { return false; } Type type = value.GetType(); string[] array = new string[2] { "GetMultiple", "Get" }; foreach (string text in array) { MethodInfo methodInfo = AccessTools.Method(type, text, new Type[2] { typeof(int), typeof(bool) }, (Type[])null); if (methodInfo != null) { methodInfo.Invoke(value, new object[2] { num, true }); QuestModPlugin.Log.LogInfo((object)$"RemoteComplete {questName}: granted {num} via {type.Name}.{text}(int, bool)"); return true; } } MethodInfo methodInfo2 = AccessTools.Method(type, "Get", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo2 != null) { for (int j = 0; j < num; j++) { methodInfo2.Invoke(value, new object[1] { true }); } QuestModPlugin.Log.LogInfo((object)$"RemoteComplete {questName}: granted {num} via {num} calls to {type.Name}.Get(bool)"); return true; } QuestModPlugin.Log.LogWarning((object)("RemoteComplete " + questName + ": no Get/GetMultiple method found on " + type.Name)); return false; } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete " + questName + ": grant threw: " + ex.Message)); return false; } } private static bool TryAwardAchievement(object quest, Type fqbType, string questName) { try { string text = fqbType.GetField("awardAchievementOnComplete")?.GetValue(quest) as string; if (string.IsNullOrEmpty(text)) { return false; } Type type = ReflectionCache.GetType("AchievementHandler") ?? ReflectionCache.GetType("Platform") ?? ReflectionCache.GetType("GameManager"); if (type == null) { return false; } string[] array = new string[3] { "AwardAchievement", "AwardAchievementToPlayer", "UnlockAchievement" }; foreach (string text2 in array) { MethodInfo methodInfo = AccessTools.Method(type, text2, new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null) { continue; } if (methodInfo.IsStatic) { methodInfo.Invoke(null, new object[1] { text }); } else { object obj = AccessTools.Property(type, "Instance")?.GetValue(null); if (obj == null) { continue; } methodInfo.Invoke(obj, new object[1] { text }); } QuestModPlugin.Log.LogInfo((object)("RemoteComplete " + questName + ": awarded achievement " + text + " via " + type.Name + "." + text2)); return true; } QuestModPlugin.Log.LogInfo((object)("RemoteComplete " + questName + ": achievement " + text + " skipped (no handler)")); return false; } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete " + questName + ": achievement threw: " + ex.Message)); return false; } } private static void TryCascadeMarkCompleted(object quest, Type fqbType, HashSet visited) { try { if (!(fqbType.GetField("markCompleted")?.GetValue(quest) is Array array)) { return; } foreach (object item in array) { if (item != null) { string text = item.GetType().GetProperty("name", BindingFlags.Instance | BindingFlags.Public)?.GetValue(item)?.ToString(); if (!string.IsNullOrEmpty(text) && visited.Add(text)) { CompleteQuest(text, skipExtraConditions: true); } } } } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete cascade threw: " + ex.Message)); } } public static void UnacceptQuest(string name) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = name + ": legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("UnacceptQuest(" + name + "): skipped (safety gate)")); } else if (PlayerData.instance != null) { IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData != null && runtimeData.Contains(name)) { object qd = runtimeData[name]; qd = QuestDataAccess.SetFields(qd, seen: true, accepted: false, completed: false, QuestDataAccess.IsCompleted(qd)); runtimeData[name] = qd; QuestModPlugin.LogDebugInfo("Unaccepted: " + name); } } } public static void UncompleteQuest(string name) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = name + ": legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("UncompleteQuest(" + name + "): skipped (safety gate)")); } else if (PlayerData.instance != null) { IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData != null && runtimeData.Contains(name)) { object qd = runtimeData[name]; qd = QuestDataAccess.SetFields(qd, seen: true, accepted: true, completed: false, wasEver: true); runtimeData[name] = qd; QuestModPlugin.LogDebugInfo("Uncompleted: " + name); } } } public static void CompleteAllQuests() { if (PlayerData.instance != null) { IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData != null) { ModifyAllQuests(runtimeData, complete: true); } } } private static (IDictionary rt, object qd) EnsureQuestEntry(string name) { if (PlayerData.instance == null) { return (null, null); } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return (null, null); } if (runtimeData.Contains(name)) { return (runtimeData, runtimeData[name]); } object anyValue = GetAnyValue(runtimeData); if (anyValue == null) { return (null, null); } object item = (runtimeData[name] = QuestDataAccess.SetFields(anyValue, seen: false, accepted: false, completed: false, wasEver: false)); QuestModPlugin.LogDebugInfo("Injected into RuntimeData: " + name); return (runtimeData, item); } private static void ModifyAllQuests(IDictionary rt, bool complete, bool respectGates = true) { int num = 0; int num2 = 0; List list = new List(); foreach (object key in rt.Keys) { list.Add(key); } foreach (string item in list) { object qd = rt[item]; bool flag = QuestDataAccess.IsAccepted(qd); bool flag2 = QuestDataAccess.IsCompleted(qd); string reason; if (respectGates && QuestRegistry.ExcludedQuests.Contains(item)) { QuestModPlugin.LogDebugInfo(" SKIP [" + item + "]: excluded"); num2++; } else if (respectGates && !flag && !flag2 && !IsActuallyAvailable(item, out reason)) { QuestModPlugin.LogDebugInfo(" SKIP [" + item + "]: " + reason); num2++; } else { qd = QuestDataAccess.SetFields(qd, seen: true, accepted: true, complete || flag2, complete || flag2); rt[item] = qd; num++; } } string text2 = (complete ? "Completed" : "Accepted"); QuestModPlugin.Log.LogInfo((object)string.Format("{0} {1} quests (skipped {2}, gates={3})", text2, num, num2, respectGates ? "respected" : "raw")); } public static bool IsChainStep(string name) { return QuestRegistry.ChainStepNames.Contains(name); } public static List GetChainList() { List list = new List(); IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); foreach (KeyValuePair item in QuestRegistry.ChainRegistry) { string key = item.Key; string[] value = item.Value; int num = -1; for (int num2 = value.Length - 1; num2 >= 0; num2--) { if (runtimeData != null && runtimeData.Contains(value[num2])) { object qd = runtimeData[value[num2]]; if (QuestDataAccess.IsCompleted(qd)) { num = num2; break; } if (QuestDataAccess.IsAccepted(qd)) { num = num2; break; } } } string value2; string displayName = (QuestRegistry.ChainDisplayNames.TryGetValue(key, out value2) ? value2 : key); bool isFullyComplete = num == value.Length - 1 && runtimeData != null && runtimeData.Contains(value[num]) && QuestDataAccess.IsCompleted(runtimeData[value[num]]); list.Add(new ChainInfo { ChainName = key, DisplayName = displayName, Steps = value, CurrentStep = num, TotalSteps = value.Length, IsFullyComplete = isFullyComplete }); } return list; } public static void AdvanceChain(string chainName) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = "chain '" + chainName + "': legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("AdvanceChain(" + chainName + "): skipped (safety gate)")); } else { if (!QuestRegistry.ChainRegistry.TryGetValue(chainName, out string[] value)) { return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return; } int num = -1; for (int num2 = value.Length - 1; num2 >= 0; num2--) { if (runtimeData.Contains(value[num2])) { object qd = runtimeData[value[num2]]; if (QuestDataAccess.IsAccepted(qd) && !QuestDataAccess.IsCompleted(qd)) { num = num2; break; } if (QuestDataAccess.IsCompleted(qd)) { num = num2; break; } } } if (num >= 0 && !IsStepCompleted(runtimeData, value[num])) { CompleteQuest(value[num]); QuestModPlugin.Log.LogInfo((object)$"Chain '{chainName}': completed step {num + 1}/{value.Length} ({value[num]})"); } int num3 = num + 1; if (num3 < value.Length) { AcceptQuest(value[num3]); QuestModPlugin.Log.LogInfo((object)$"Chain '{chainName}': accepted step {num3 + 1}/{value.Length} ({value[num3]})"); } } } public static void RewindChain(string chainName) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { LastCompletionRefusal = "chain '" + chainName + "': legacy save (safety override not set)"; QuestModPlugin.Log.LogInfo((object)("RewindChain(" + chainName + "): skipped (safety gate)")); } else { if (!QuestRegistry.ChainRegistry.TryGetValue(chainName, out string[] value)) { return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return; } int num = -1; for (int num2 = value.Length - 1; num2 >= 0; num2--) { if (runtimeData.Contains(value[num2])) { object qd = runtimeData[value[num2]]; if (QuestDataAccess.IsAccepted(qd) || QuestDataAccess.IsCompleted(qd)) { num = num2; break; } } } if (num >= 0) { if (!IsStepCompleted(runtimeData, value[num])) { UnacceptQuest(value[num]); QuestModPlugin.Log.LogInfo((object)$"Chain '{chainName}': unaccepted step {num + 1}/{value.Length} ({value[num]})"); } else { UncompleteQuest(value[num]); QuestModPlugin.Log.LogInfo((object)$"Chain '{chainName}': uncompleted step {num + 1}/{value.Length} ({value[num]})"); } } } } private static bool IsStepCompleted(IDictionary rt, string name) { if (!rt.Contains(name)) { return false; } return QuestDataAccess.IsCompleted(rt[name]); } private static object GetAnyValue(IDictionary dict) { IDictionaryEnumerator dictionaryEnumerator = dict.GetEnumerator(); try { if (dictionaryEnumerator.MoveNext()) { return ((DictionaryEntry)dictionaryEnumerator.Current).Value; } } finally { IDisposable disposable = dictionaryEnumerator as IDisposable; if (disposable != null) { disposable.Dispose(); } } return null; } } public struct QuestTargetInfo { public string CounterName; public string DisplayName; public int CurrentCount; public int OriginalCount; public int TargetIndex; } public struct QuestOverrideInfo { public string QuestName; public string QuestTypeName; public int CompletedCount; public List Targets; } public static class QuestCompletionOverrides { private static FieldInfo? targetsBackingField; private static FieldInfo? countField; private static Dictionary originalCounts = new Dictionary(); private static Dictionary overrideCounts = new Dictionary(); private static FullQuestBase[]? cachedQuests; public static bool IsInitialized { get; private set; } public static int GetMaxCap(string questName, int targetIndex) { if (QuestRegistry.MaxCaps.TryGetValue(questName, out int[] value) && targetIndex < value.Length) { return value[targetIndex]; } return -1; } public static int ClampCount(string questName, int targetIndex, int value) { if (value < 0) { value = 0; } if (QuestModPlugin.DevRemoveLimits.Value) { return value; } int maxCap = GetMaxCap(questName, targetIndex); if (maxCap > 0 && value > maxCap) { value = maxCap; } return value; } public static int GetQoLCount(int originalCount) { return (originalCount + 1) / 2; } public static int GetFarmableCount(int originalCount) { int num = (originalCount + 1) / 2; if (num > 1 && num % 2 != 0) { num--; } return num; } public static void ApplyPresetAll(string preset) { if (!IsInitialized) { return; } if (cachedQuests == null) { CacheQuests(); } if (cachedQuests == null || countField == null) { return; } FullQuestBase[] array = cachedQuests; for (int i = 0; i < array.Length; i++) { string text = ((Object)array[i]).name ?? ""; if (QuestRegistry.QuestCategories.ContainsKey(text) && originalCounts.ContainsKey(text)) { int[] array2 = originalCounts[text]; for (int j = 0; j < array2.Length; j++) { SetTargetCount(text, j, preset switch { "set1" => 1, "qol" => GetQoLCount(array2[j]), "farmable" => (!QuestRegistry.FarmableExcluded.Contains(text)) ? GetFarmableCount(array2[j]) : array2[j], _ => array2[j], }); } } } QuestModPlugin.LogDebugInfo("Applied preset '" + preset + "' to all quests"); } public static string? GetCategory(string questName) { if (QuestRegistry.QuestCategories.TryGetValue(questName, out string value)) { return value; } return null; } public static List GetQuestsByCategory(string category) { List allQuestsWithTargets = GetAllQuestsWithTargets(); List list = new List(); foreach (QuestOverrideInfo item in allQuestsWithTargets) { if (GetCategory(item.QuestName) == category) { list.Add(item); } } return list; } public static void Initialize() { Type type = typeof(FullQuestBase); while (type != null && type != typeof(Object)) { foreach (FieldInfo declaredField in AccessTools.GetDeclaredFields(type)) { if (declaredField.FieldType.IsArray && declaredField.FieldType.GetElementType()?.Name == "QuestTarget") { targetsBackingField = declaredField; countField = ReflectionCache.GetField(declaredField.FieldType.GetElementType(), "Count"); QuestModPlugin.Log.LogInfo((object)("Found targets backing field: " + declaredField.Name + " on " + type.Name)); break; } } if (targetsBackingField != null) { break; } type = type.BaseType; } if (targetsBackingField == null || countField == null) { QuestModPlugin.Log.LogWarning((object)"QuestCompletionOverrides: Could not find targets backing field"); return; } IsInitialized = true; QuestModPlugin.Log.LogInfo((object)"QuestCompletionOverrides initialized"); } internal static Array GetTargetsArray(FullQuestBase quest) { return targetsBackingField?.GetValue(quest) as Array; } public static void ResetSlotState() { if (cachedQuests != null && countField != null) { int num = 0; FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if ((Object)(object)val == (Object)null) { continue; } string key = ((Object)val).name ?? ""; if (!originalCounts.TryGetValue(key, out int[] value)) { continue; } Array targetsArray = GetTargetsArray(val); if (targetsArray == null) { continue; } int num2 = Math.Min(targetsArray.Length, value.Length); for (int j = 0; j < num2; j++) { object value2 = targetsArray.GetValue(j); if (value2 != null) { countField.SetValue(value2, value[j]); if (value2.GetType().IsValueType) { targetsArray.SetValue(value2, j); } num++; } } } ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogInfo((object)$"QuestCompletionOverrides.ResetSlotState: restored {num} target counts on title return"); } } cachedQuests = null; originalCounts.Clear(); overrideCounts.Clear(); } public static void CacheQuests() { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { return; } FullQuestBase[] array = Resources.FindObjectsOfTypeAll(); if (array == null || array.Length == 0) { ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogDebug((object)"CacheQuests: FullQuestBase array empty, will retry next call"); } return; } cachedQuests = array; QuestModPlugin.Log.LogInfo((object)$"Cached {cachedQuests.Length} FullQuestBase objects"); originalCounts.Clear(); FullQuestBase[] array2 = cachedQuests; foreach (FullQuestBase obj in array2) { string key = ((Object)obj).name ?? ""; IReadOnlyList targets = obj.Targets; if (targets != null && targets.Count != 0) { int[] array3 = new int[targets.Count]; for (int j = 0; j < targets.Count; j++) { array3[j] = targets[j].Count; } originalCounts[key] = array3; } } } public static List GetAllQuestsWithTargets() { //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (!IsInitialized) { return list; } if (cachedQuests == null) { CacheQuests(); } if (cachedQuests == null || countField == null) { return list; } FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { string text = ((Object)val).name ?? ""; if (!QuestRegistry.QuestCategories.ContainsKey(text) || !QuestModPlugin.IsQuestDiscovered(text)) { continue; } IReadOnlyList targets = val.Targets; if (targets == null || targets.Count == 0) { continue; } QuestOverrideInfo questOverrideInfo = default(QuestOverrideInfo); questOverrideInfo.QuestName = text; questOverrideInfo.QuestTypeName = ((object)val).GetType().Name; questOverrideInfo.CompletedCount = 0; questOverrideInfo.Targets = new List(); QuestOverrideInfo item = questOverrideInfo; IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData != null && runtimeData.Contains(text)) { object qd = runtimeData[text]; item.CompletedCount = QuestDataAccess.GetCompletedCount(qd); } for (int j = 0; j < targets.Count; j++) { QuestTarget val2 = targets[j]; int count = val2.Count; string text2 = ((object)val2.Counter)?.ToString() ?? "?"; string displayName = text2; int originalCount = count; if (originalCounts.ContainsKey(text) && j < originalCounts[text].Length) { originalCount = originalCounts[text][j]; } item.Targets.Add(new QuestTargetInfo { CounterName = text2, DisplayName = displayName, CurrentCount = count, OriginalCount = originalCount, TargetIndex = j }); } list.Add(item); } return list; } public static bool SetTargetCount(string questName, int targetIndex, int newCount) { if (!IsInitialized) { return false; } if (cachedQuests == null) { CacheQuests(); } if (cachedQuests == null || countField == null) { return false; } newCount = ClampCount(questName, targetIndex, newCount); FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if (((Object)val).name != questName) { continue; } Array targetsArray = GetTargetsArray(val); if (targetsArray == null || targetIndex >= targetsArray.Length) { return false; } object value = targetsArray.GetValue(targetIndex); countField.SetValue(value, newCount); targetsArray.SetValue(value, targetIndex); if (!overrideCounts.ContainsKey(questName)) { int[] array2 = new int[targetsArray.Length]; for (int j = 0; j < targetsArray.Length; j++) { object value2 = targetsArray.GetValue(j); array2[j] = (int)countField.GetValue(value2); } overrideCounts[questName] = array2; } else { overrideCounts[questName][targetIndex] = newCount; } QuestModSaveData saveData = QuestModPlugin.Instance.SaveData; if (saveData != null) { saveData.QuestTargetOverrides[$"{questName}:{targetIndex}"] = newCount; } QuestModPlugin.LogDebugInfo($"Set {questName} target[{targetIndex}] count to {newCount}"); if (QuestRegistry.SharedTargetQuests.TryGetValue(questName, out string value3)) { FullQuestBase[] array3 = cachedQuests; foreach (FullQuestBase val2 in array3) { if (((Object)val2).name != value3) { continue; } Array targetsArray2 = GetTargetsArray(val2); if (targetsArray2 == null || targetIndex >= targetsArray2.Length) { break; } object value4 = targetsArray2.GetValue(targetIndex); countField.SetValue(value4, newCount); targetsArray2.SetValue(value4, targetIndex); if (!overrideCounts.ContainsKey(value3)) { int[] array4 = new int[targetsArray2.Length]; for (int l = 0; l < targetsArray2.Length; l++) { object value5 = targetsArray2.GetValue(l); array4[l] = (int)countField.GetValue(value5); } overrideCounts[value3] = array4; } else { overrideCounts[value3][targetIndex] = newCount; } QuestModSaveData saveData2 = QuestModPlugin.Instance.SaveData; if (saveData2 != null) { saveData2.QuestTargetOverrides[$"{value3}:{targetIndex}"] = newCount; } break; } } return true; } return false; } public static bool SetTargetCountTransient(string questName, int targetIndex, int newCount) { if (!IsInitialized) { return false; } if (cachedQuests == null) { CacheQuests(); } if (cachedQuests == null || countField == null) { return false; } newCount = ClampCount(questName, targetIndex, newCount); FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if (!(((Object)val).name != questName)) { Array targetsArray = GetTargetsArray(val); if (targetsArray == null || targetIndex >= targetsArray.Length) { return false; } object value = targetsArray.GetValue(targetIndex); countField.SetValue(value, newCount); targetsArray.SetValue(value, targetIndex); QuestModPlugin.LogDebugInfo($"[Transient] Set {questName} target[{targetIndex}] count to {newCount}"); return true; } } return false; } public static void SetAllTargetCounts(string questName, int newCount) { if (!IsInitialized) { return; } if (cachedQuests == null) { CacheQuests(); } if (cachedQuests == null || countField == null) { return; } FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if (((Object)val).name != questName) { continue; } Array targetsArray = GetTargetsArray(val); if (targetsArray != null) { for (int j = 0; j < targetsArray.Length; j++) { object value = targetsArray.GetValue(j); countField.SetValue(value, newCount); targetsArray.SetValue(value, j); } QuestModPlugin.LogDebugInfo($"Set all {targetsArray.Length} targets for {questName} to {newCount}"); } break; } } public static void ResetToOriginal(string questName) { if (!IsInitialized || !originalCounts.ContainsKey(questName) || cachedQuests == null || countField == null) { return; } FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if (((Object)val).name != questName) { continue; } Array targetsArray = GetTargetsArray(val); if (targetsArray == null) { break; } int[] array2 = originalCounts[questName]; for (int j = 0; j < targetsArray.Length && j < array2.Length; j++) { object value = targetsArray.GetValue(j); countField.SetValue(value, array2[j]); targetsArray.SetValue(value, j); } overrideCounts.Remove(questName); QuestModSaveData saveData = QuestModPlugin.Instance.SaveData; if (saveData != null) { List list = new List(); foreach (string key in saveData.QuestTargetOverrides.Keys) { if (key.StartsWith(questName + ":")) { list.Add(key); } } foreach (string item in list) { saveData.QuestTargetOverrides.Remove(item); } } QuestModPlugin.LogDebugInfo("Reset " + questName + " to original counts"); break; } } public static void ResetAll() { foreach (string item in new List(originalCounts.Keys)) { ResetToOriginal(item); } } public static void ApplySavedOverrides() { QuestModSaveData saveData = QuestModPlugin.Instance.SaveData; if (saveData == null || saveData.QuestTargetOverrides.Count == 0) { return; } if (cachedQuests == null) { CacheQuests(); } int num = 0; foreach (KeyValuePair questTargetOverride in saveData.QuestTargetOverrides) { string[] array = questTargetOverride.Key.Split(':'); if (array.Length == 2) { string questName = array[0]; if (int.TryParse(array[1], out var result) && SetTargetCount(questName, result, questTargetOverride.Value)) { num++; } } } QuestModPlugin.Log.LogInfo((object)$"Applied {num} saved quest target overrides"); if (QuestModPlugin.IsCustomRequirementsEnabled) { QuestRequirements.ApplyActivePreset(); } } public static bool[] GetChecklistStatus(string questName) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (!QuestRegistry.ChecklistQuests.ContainsKey(questName)) { return new bool[0]; } if (!IsInitialized || cachedQuests == null) { return new bool[QuestRegistry.ChecklistQuests[questName].Length]; } FullQuestBase[] array = cachedQuests; foreach (FullQuestBase val in array) { if (!(((Object)val).name != questName)) { IReadOnlyList targets = val.Targets; if (targets == null) { return new bool[QuestRegistry.ChecklistQuests[questName].Length]; } bool[] array2 = new bool[targets.Count]; for (int j = 0; j < targets.Count; j++) { array2[j] = targets[j].Count == 0; } return array2; } } return new bool[QuestRegistry.ChecklistQuests[questName].Length]; } public static void ToggleChecklistTarget(string questName, int index, bool done) { SetTargetCount(questName, index, (!done) ? 1 : 0); if (!QuestRegistry.SequentialQuests.Contains(questName)) { return; } string[] array = QuestRegistry.ChecklistQuests[questName]; if (done) { for (int i = 0; i < index; i++) { SetTargetCount(questName, i, 0); } } else { for (int j = index + 1; j < array.Length; j++) { SetTargetCount(questName, j, 1); } } } } public static class SilkSoulOverrides { public struct PointEntry { public string QuestName; public float Value; public float DefaultValue; public int EntryIndex; } private static Type groupType; private static FieldInfo f_target; private static PropertyInfo p_currentValue; private static FieldInfo f_requiredCompleteTotalGroups; private static object cachedGroup; private static bool resolved; private static FieldInfo f_entries; private static FieldInfo f_entryQuest; private static FieldInfo f_entryValue; private static Array cachedEntries; private static bool entriesResolved; private static int? thresholdOverride; private static Dictionary pointOverrides = new Dictionary(); public static bool HasEntries { get { if (cachedEntries != null && f_entryQuest != null) { return f_entryValue != null; } return false; } } public static bool TryResolve() { if (resolved) { return cachedGroup != null; } resolved = true; groupType = ReflectionCache.GetType("QuestCompleteTotalGroup"); if (groupType == null) { return false; } f_target = ReflectionCache.GetField(groupType, "target"); p_currentValue = ReflectionCache.GetProperty(groupType, "CurrentValueCount"); Type type = ReflectionCache.GetType("FullQuestBase"); if (type != null) { f_requiredCompleteTotalGroups = ReflectionCache.GetField(type, "requiredCompleteTotalGroups"); } Type type2 = ReflectionCache.GetType("QuestManager"); if (type2 == null) { return false; } MethodInfo methodInfo = AccessTools.Method(type2, "GetQuest", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo == null) { return false; } object obj = methodInfo.Invoke(null, new object[1] { "Soul Snare Pre" }); if (obj == null) { return false; } if (f_requiredCompleteTotalGroups != null && f_requiredCompleteTotalGroups.GetValue(obj) is Array array && array.Length > 0) { cachedGroup = array.GetValue(0); QuestModPlugin.Log.LogInfo((object)"SilkSoul: resolved CompleteTotalGroup"); TryResolveEntries(); return true; } QuestModPlugin.Log.LogWarning((object)"SilkSoul: could not resolve group from Soul Snare Pre"); return false; } private static void TryResolveEntries() { if (entriesResolved || cachedGroup == null) { return; } entriesResolved = true; foreach (FieldInfo declaredField in AccessTools.GetDeclaredFields(groupType)) { if (!declaredField.FieldType.IsArray || !(declaredField.GetValue(cachedGroup) is Array array) || array.Length == 0) { continue; } Type type = array.GetValue(0).GetType(); FieldInfo fieldInfo = null; FieldInfo fieldInfo2 = null; foreach (FieldInfo declaredField2 in AccessTools.GetDeclaredFields(type)) { if (fieldInfo == null && (declaredField2.FieldType.Name.Contains("Quest") || declaredField2.FieldType.Name.Contains("FullQuestBase"))) { fieldInfo = declaredField2; } else if (fieldInfo2 == null && declaredField2.FieldType == typeof(float)) { fieldInfo2 = declaredField2; } } if (fieldInfo2 == null) { foreach (FieldInfo declaredField3 in AccessTools.GetDeclaredFields(type)) { if (declaredField3.FieldType == typeof(int)) { fieldInfo2 = declaredField3; break; } } } if (fieldInfo != null && fieldInfo2 != null) { f_entries = declaredField; f_entryQuest = fieldInfo; f_entryValue = fieldInfo2; cachedEntries = array; QuestModPlugin.Log.LogInfo((object)$"SilkSoul: resolved {array.Length} entries via field '{declaredField.Name}' (quest={fieldInfo.Name}, value={fieldInfo2.Name})"); return; } } QuestModPlugin.Log.LogInfo((object)"SilkSoul: no entry array found. Per-quest overrides not available"); } public static List GetPointEntries() { List list = new List(); if (!HasEntries) { return list; } for (int i = 0; i < cachedEntries.Length; i++) { object value = cachedEntries.GetValue(i); object? value2 = f_entryQuest.GetValue(value); Object val = (Object)((value2 is Object) ? value2 : null); if (!(val == (Object)null)) { string name = val.name; float num = ((!(f_entryValue.FieldType == typeof(float))) ? ((float)(int)f_entryValue.GetValue(value)) : ((float)f_entryValue.GetValue(value))); float defaultValue = (QuestRegistry.SilkSoulPointValues.ContainsKey(name) ? QuestRegistry.SilkSoulPointValues[name] : num); list.Add(new PointEntry { QuestName = name, Value = num, DefaultValue = defaultValue, EntryIndex = i }); } } return list; } public static bool SetPointValue(int entryIndex, float value) { if (!HasEntries || entryIndex < 0 || entryIndex >= cachedEntries.Length) { return false; } if (value < 0f) { value = 0f; } if (value > 1000000f) { value = 1000000f; } object value2 = cachedEntries.GetValue(entryIndex); object? value3 = f_entryQuest.GetValue(value2); Object val = (Object)((value3 is Object) ? value3 : null); string text = ((val != (Object)null) ? val.name : ""); object value4; if (f_entryValue.FieldType == typeof(float)) { value4 = value; } else { int num = Mathf.RoundToInt(value); value4 = num; value = num; } ReflectionCache.WriteToArray(cachedEntries, entryIndex, value2, f_entryValue, value4); pointOverrides[text] = value; QuestModPlugin.LogDebugInfo($"SilkSoul: set {text} value to {value}"); return true; } public static void ResetAllPointValues() { if (!HasEntries) { return; } for (int i = 0; i < cachedEntries.Length; i++) { object value = cachedEntries.GetValue(i); object? value2 = f_entryQuest.GetValue(value); Object val = (Object)((value2 is Object) ? value2 : null); if (!(val == (Object)null)) { string name = val.name; if (QuestRegistry.SilkSoulPointValues.ContainsKey(name)) { float num = QuestRegistry.SilkSoulPointValues[name]; object value3 = ((f_entryValue.FieldType == typeof(float)) ? ((object)num) : ((object)(int)num)); ReflectionCache.WriteToArray(cachedEntries, i, value, f_entryValue, value3); } } } pointOverrides.Clear(); } public static int GetThreshold() { if (thresholdOverride.HasValue) { return thresholdOverride.Value; } return ReflectionCache.Read(cachedGroup, f_target, QuestRegistry.DefaultThreshold); } public static void SetThreshold(int value) { if (value < 0) { value = 0; } if (value > 100) { value = 100; } thresholdOverride = value; ReflectionCache.Write(cachedGroup, f_target, value); QuestModPlugin.LogDebugInfo($"SilkSoul: threshold set to {value}"); } public static void ResetThreshold() { thresholdOverride = null; ReflectionCache.Write(cachedGroup, f_target, QuestRegistry.DefaultThreshold); } public static int GetCurrentPoints() { return ReflectionCache.Read(cachedGroup, p_currentValue, -1); } public static void Reset() { try { if (cachedGroup != null && f_target != null) { ReflectionCache.Write(cachedGroup, f_target, QuestRegistry.DefaultThreshold); } if (HasEntries) { for (int i = 0; i < cachedEntries.Length; i++) { object value = cachedEntries.GetValue(i); object? value2 = f_entryQuest.GetValue(value); Object val = (Object)((value2 is Object) ? value2 : null); if (!(val == (Object)null) && QuestRegistry.SilkSoulPointValues.TryGetValue(val.name, out var value3)) { object value4 = ((f_entryValue.FieldType == typeof(float)) ? ((object)value3) : ((object)Mathf.RoundToInt(value3))); ReflectionCache.WriteToArray(cachedEntries, i, value, f_entryValue, value4); } } } } catch (Exception ex) { ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogWarning((object)("SilkSoul.Reset restore failed: " + ex.Message)); } } cachedGroup = null; cachedEntries = null; resolved = false; entriesResolved = false; thresholdOverride = null; pointOverrides.Clear(); } } internal static class QuestDataAccess { private static readonly FieldInfo compField; private static readonly FieldInfo rtField; private static MemberInfo hasBeenSeenMember; private static MemberInfo isAcceptedMember; private static MemberInfo isCompletedMember; private static MemberInfo wasEverCompletedMember; private static MemberInfo completedCountMember; private static bool membersResolved; static QuestDataAccess() { compField = ReflectionCache.GetField(typeof(PlayerData), "QuestCompletionData"); if (!(compField != null)) { return; } rtField = ReflectionCache.GetField(compField.FieldType, "RuntimeData"); if (rtField == null) { Type baseType = compField.FieldType.BaseType; while (baseType != null && rtField == null) { rtField = baseType.GetField("RuntimeData", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); baseType = baseType.BaseType; } } } private static void ResolveMembers(Type runtimeType) { if (!membersResolved) { hasBeenSeenMember = ReflectionCache.FindMember(runtimeType, "HasBeenSeen"); isAcceptedMember = ReflectionCache.FindMember(runtimeType, "IsAccepted"); isCompletedMember = ReflectionCache.FindMember(runtimeType, "IsCompleted"); wasEverCompletedMember = ReflectionCache.FindMember(runtimeType, "WasEverCompleted"); completedCountMember = ReflectionCache.FindMember(runtimeType, "CompletedCount"); membersResolved = true; QuestModPlugin.LogDebugInfo("QuestDataAccess resolved members on runtime type " + runtimeType.FullName + ":"); QuestModPlugin.LogDebugInfo(" HasBeenSeen=" + ReflectionCache.MemberTag(hasBeenSeenMember) + ", IsAccepted=" + ReflectionCache.MemberTag(isAcceptedMember) + ", IsCompleted=" + ReflectionCache.MemberTag(isCompletedMember) + ", WasEverCompleted=" + ReflectionCache.MemberTag(wasEverCompletedMember) + ", CompletedCount=" + ReflectionCache.MemberTag(completedCountMember)); } } internal static IDictionary GetRuntimeData() { if (compField == null) { return null; } if (rtField == null) { return null; } if (PlayerData.instance == null) { return null; } object value = compField.GetValue(PlayerData.instance); if (value == null) { return null; } return rtField.GetValue(value) as IDictionary; } internal static bool IsAccepted(object qd) { if (qd == null) { return false; } if (!membersResolved) { ResolveMembers(qd.GetType()); } return (bool)(ReflectionCache.ReadMember(isAcceptedMember, qd) ?? ((object)false)); } internal static bool IsCompleted(object qd) { if (qd == null) { return false; } if (!membersResolved) { ResolveMembers(qd.GetType()); } return (bool)(ReflectionCache.ReadMember(isCompletedMember, qd) ?? ((object)false)); } internal static bool HasBeenSeen(object qd) { if (qd == null) { return false; } if (!membersResolved) { ResolveMembers(qd.GetType()); } return (bool)(ReflectionCache.ReadMember(hasBeenSeenMember, qd) ?? ((object)false)); } internal static int GetCompletedCount(object qd) { if (qd == null) { return 0; } if (!membersResolved) { ResolveMembers(qd.GetType()); } return (int)(ReflectionCache.ReadMember(completedCountMember, qd) ?? ((object)0)); } internal static object SetFields(object qd, bool seen, bool accepted, bool completed, bool wasEver) { if (qd == null) { return qd; } if (!membersResolved) { ResolveMembers(qd.GetType()); } ReflectionCache.WriteMember(hasBeenSeenMember, qd, seen); ReflectionCache.WriteMember(isAcceptedMember, qd, accepted); ReflectionCache.WriteMember(isCompletedMember, qd, completed); ReflectionCache.WriteMember(wasEverCompletedMember, qd, wasEver); return qd; } } public enum AllWishesMode { Disabled, Pure, Adjusted } public class QuestPolicy { public bool Available { get; set; } public bool AutoAccept { get; set; } } public class QuestModSaveData { public const int CurrentSchemaVersion = 2; public int SchemaVersion { get; set; } public HashSet InjectedQuests { get; set; } = new HashSet(); public HashSet CompletedQuests { get; set; } = new HashSet(); public Dictionary QuestTargetOverrides { get; set; } = new Dictionary(); public bool AllQuestsAvailable { get; set; } public AllWishesMode AllWishesMode { get; set; } public bool AllQuestsAccepted { get; set; } public bool AutoAcceptAllAvailable { get; set; } public Dictionary QuestPolicies { get; set; } = new Dictionary(); public GranularPrereqs Prereqs { get; set; } = new GranularPrereqs(); public Dictionary WishLocationOverrides { get; set; } = new Dictionary(); public HashSet WishLocationTriggersFired { get; set; } = new HashSet(); public bool QuestModInitialized { get; set; } public bool OverrideSafetyForThisSave { get; set; } public string ActiveDslPreset { get; set; } = "vanilla"; public bool? EnableCustomRequirements { get; set; } public bool? EnableFullRemoteComplete { get; set; } } public class GranularPrereqs { public bool BypassFleatopia { get; set; } public bool BypassMandatoryWishes { get; set; } public bool BypassFaydownCloak { get; set; } public bool BypassNeedolin { get; set; } public bool BypassBonebottomQuestBoard { get; set; } public bool BypassAllWishwalls { get; set; } } public static class QuestPolicyStore { [CompilerGenerated] private sealed class d__8 : IEnumerable, IEnumerable, IEnumerator, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private Dictionary.Enumerator <>7__wrap1; string IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__8(int <>1__state) { this.<>1__state = <>1__state; <>l__initialThreadId = Environment.CurrentManagedThreadId; } [DebuggerHidden] void IDisposable.Dispose() { int num = <>1__state; if (num == -3 || num == 1) { try { } finally { <>m__Finally1(); } } <>7__wrap1 = default(Dictionary.Enumerator); <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; Dictionary map = Map; if (map == null) { return false; } <>7__wrap1 = map.GetEnumerator(); <>1__state = -3; break; } case 1: <>1__state = -3; break; } while (<>7__wrap1.MoveNext()) { KeyValuePair current = <>7__wrap1.Current; if (current.Value != null && current.Value.AutoAccept) { <>2__current = current.Key; <>1__state = 1; return true; } } <>m__Finally1(); <>7__wrap1 = default(Dictionary.Enumerator); return false; } catch { //try-fault ((IDisposable)this).Dispose(); throw; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } private void <>m__Finally1() { <>1__state = -1; ((IDisposable)<>7__wrap1).Dispose(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new d__8(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)this).GetEnumerator(); } } public static Dictionary Map { get { QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData == null) { return null; } if (questModSaveData.QuestPolicies == null) { questModSaveData.QuestPolicies = new Dictionary(); } return questModSaveData.QuestPolicies; } } public static QuestPolicy Get(string questName) { Dictionary map = Map; if (map == null || string.IsNullOrEmpty(questName)) { return null; } if (!map.TryGetValue(questName, out var value)) { return null; } return value; } public static QuestPolicy GetOrCreate(string questName) { Dictionary map = Map; if (map == null || string.IsNullOrEmpty(questName)) { return null; } if (!map.TryGetValue(questName, out var value)) { value = (map[questName] = new QuestPolicy()); } return value; } public static bool IsAvailable(string questName) { if (QuestModPlugin.AllQuestsAvailable) { return true; } return Get(questName)?.Available ?? false; } public static bool IsAutoAccept(string questName) { if (QuestModPlugin.AllQuestsAccepted) { return true; } return Get(questName)?.AutoAccept ?? false; } public static void SetAvailable(string questName, bool value) { QuestPolicy orCreate = GetOrCreate(questName); if (orCreate != null) { orCreate.Available = value; if (!value) { orCreate.AutoAccept = false; } } } public static void SetAutoAccept(string questName, bool value) { QuestPolicy orCreate = GetOrCreate(questName); if (orCreate != null) { orCreate.AutoAccept = value; if (value) { orCreate.Available = true; } } } [IteratorStateMachine(typeof(d__8))] public static IEnumerable AutoAcceptNames() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__8(-2); } } public static class QuestRegistry { public static HashSet ExcludedQuests { get; private set; } = new HashSet(); public static Dictionary ChainRegistry { get; private set; } = new Dictionary(); public static Dictionary ChainDisplayNames { get; private set; } = new Dictionary(); public static HashSet ChainStepNames { get; private set; } = new HashSet(); public static Dictionary MutuallyExclusiveQuests { get; private set; } = new Dictionary(); public static Dictionary SharedTargetQuests { get; private set; } = new Dictionary(); public static Dictionary DisplayNames { get; private set; } = new Dictionary(); public static Dictionary QuestCategories { get; private set; } = new Dictionary(); public static string[] Categories { get; private set; } = Array.Empty(); public static Dictionary MaxCaps { get; private set; } = new Dictionary(); public static Dictionary ChecklistQuests { get; private set; } = new Dictionary(); public static HashSet SequentialQuests { get; private set; } = new HashSet(); public static Dictionary SequentialStagePdPatterns { get; private set; } = new Dictionary(); public static HashSet FarmableExcluded { get; private set; } = new HashSet(); public static int DefaultThreshold { get; private set; } = 17; public static Dictionary SilkSoulPointValues { get; private set; } = new Dictionary(); public static string[] SilkSoulRequiredQuests { get; private set; } = Array.Empty(); public static bool IsLoaded { get; private set; } public static void Load() { if (IsLoaded) { return; } Assembly executingAssembly = Assembly.GetExecutingAssembly(); string text = "QuestMod.Data.QuestCapabilities.json"; using Stream stream = executingAssembly.GetManifestResourceStream(text); if (stream == null) { QuestModPlugin.Log.LogError((object)("Failed to load embedded resource: " + text)); return; } using StreamReader streamReader = new StreamReader(stream); JObject root = JObject.Parse(streamReader.ReadToEnd()); LoadExcluded(root); LoadFarmableExclude(root); LoadMutuallyExclusive(root); LoadSharedTargets(root); LoadSilkSoul(root); LoadMaxCaps(root); LoadChecklist(root); LoadChains(root); LoadCategories(root); IsLoaded = true; QuestModPlugin.Log.LogInfo((object)$"QuestRegistry loaded: {DisplayNames.Count} quests, {ChainRegistry.Count} chains, {ChecklistQuests.Count} checklists, {MaxCaps.Count} max caps"); } private static void LoadExcluded(JObject root) { JToken obj = root["excluded"]; JArray val = (JArray)(object)((obj is JArray) ? obj : null); if (val == null) { return; } foreach (JToken item in val) { string text = Extensions.Value((IEnumerable)item); if (text != null) { ExcludedQuests.Add(text); } } } private static void LoadFarmableExclude(JObject root) { JToken obj = root["farmableExclude"]; JArray val = (JArray)(object)((obj is JArray) ? obj : null); if (val == null) { return; } foreach (JToken item in val) { string text = Extensions.Value((IEnumerable)item); if (text != null) { FarmableExcluded.Add(text); } } } private static void LoadMutuallyExclusive(JObject root) { JToken obj = root["mutuallyExclusive"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } foreach (KeyValuePair item in val) { JToken value = item.Value; string text = ((value != null) ? Extensions.Value((IEnumerable)value) : null); if (text != null) { MutuallyExclusiveQuests[item.Key] = text; } } } private static void LoadSharedTargets(JObject root) { JToken obj = root["sharedTargets"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } foreach (KeyValuePair item in val) { JToken value = item.Value; string text = ((value != null) ? Extensions.Value((IEnumerable)value) : null); if (text != null) { SharedTargetQuests[item.Key] = text; } } } private static void LoadSilkSoul(JObject root) { JToken obj = root["silkSoul"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } JToken val2 = val["defaultThreshold"]; if (val2 != null) { DefaultThreshold = Extensions.Value((IEnumerable)val2); } JToken obj2 = val["requiredQuests"]; JArray val3 = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (val3 != null) { List list = new List(); foreach (JToken item in val3) { string text = Extensions.Value((IEnumerable)item); if (text != null) { list.Add(text); } } SilkSoulRequiredQuests = list.ToArray(); } JToken obj3 = val["pointValues"]; JObject val4 = (JObject)(object)((obj3 is JObject) ? obj3 : null); if (val4 == null) { return; } foreach (KeyValuePair item2 in val4) { SilkSoulPointValues[item2.Key] = Extensions.Value((IEnumerable)item2.Value); } } private static void LoadMaxCaps(JObject root) { JToken obj = root["maxCaps"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } foreach (KeyValuePair item in val) { JToken value = item.Value; JArray val2 = (JArray)(object)((value is JArray) ? value : null); if (val2 == null) { continue; } List list = new List(); foreach (JToken item2 in val2) { list.Add(Extensions.Value((IEnumerable)item2)); } MaxCaps[item.Key] = list.ToArray(); } } private static void LoadChecklist(JObject root) { JToken obj = root["checklist"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val != null) { foreach (KeyValuePair item in val) { JToken value = item.Value; JArray val2 = (JArray)(object)((value is JArray) ? value : null); if (val2 == null) { continue; } List list = new List(); foreach (JToken item2 in val2) { string text = Extensions.Value((IEnumerable)item2); if (text != null) { list.Add(text); } } ChecklistQuests[item.Key] = list.ToArray(); } } JToken obj2 = root["sequentialQuests"]; JArray val3 = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (val3 != null) { foreach (JToken item3 in val3) { string text2 = Extensions.Value((IEnumerable)item3); if (text2 != null) { SequentialQuests.Add(text2); } } } JToken obj3 = root["sequentialStagePdPatterns"]; JObject val4 = (JObject)(object)((obj3 is JObject) ? obj3 : null); if (val4 == null) { return; } foreach (KeyValuePair item4 in val4) { if (!item4.Key.StartsWith("_")) { JToken value2 = item4.Value; string value3 = ((value2 != null) ? Extensions.Value((IEnumerable)value2) : null); if (!string.IsNullOrEmpty(value3)) { SequentialStagePdPatterns[item4.Key] = value3; } } } } private static void LoadChains(JObject root) { JToken obj = root["chains"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } foreach (KeyValuePair item in val) { string key = item.Key; JToken value = item.Value; JObject val2 = (JObject)(object)((value is JObject) ? value : null); if (val2 == null) { continue; } JToken obj2 = val2["display"]; string text = ((obj2 != null) ? Extensions.Value((IEnumerable)obj2) : null); if (text != null) { ChainDisplayNames[key] = text; } JToken obj3 = val2["steps"]; JArray val3 = (JArray)(object)((obj3 is JArray) ? obj3 : null); if (val3 == null) { continue; } List list = new List(); foreach (JToken item2 in val3) { string text2 = Extensions.Value((IEnumerable)item2); if (text2 != null) { list.Add(text2); ChainStepNames.Add(text2); } } ChainRegistry[key] = list.ToArray(); } } private static void LoadCategories(JObject root) { JToken obj = root["categories"]; JObject val = (JObject)(object)((obj is JObject) ? obj : null); if (val == null) { return; } List list = new List(); foreach (KeyValuePair item in val) { string key = item.Key; if (key == "Main Story") { continue; } list.Add(key); JToken value = item.Value; JObject val2 = (JObject)(object)((value is JObject) ? value : null); if (val2 == null) { continue; } foreach (KeyValuePair item2 in val2) { string key2 = item2.Key; JToken value2 = item2.Value; LoadQuest(key2, (JObject?)(object)((value2 is JObject) ? value2 : null), key); } } JToken obj2 = val["Main Story"]; JObject val3 = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val3 != null) { foreach (KeyValuePair item3 in val3) { string key3 = item3.Key; JToken value3 = item3.Value; LoadQuest(key3, (JObject?)(object)((value3 is JObject) ? value3 : null), null); } } Categories = list.ToArray(); } private static void LoadQuest(string questName, JObject? questObj, string? categoryName) { if (categoryName != null) { QuestCategories[questName] = categoryName; } if (questObj != null) { JToken obj = questObj["display"]; string text = ((obj != null) ? Extensions.Value((IEnumerable)obj) : null); if (text != null) { DisplayNames[questName] = text; } } } } public static class QuestRequirements { public sealed class Preset { public string Name = ""; public string Description = ""; public List Rules = new List(); } public sealed class Rule { public MatchExpr? Match; public int? Set; public float? Scale; public string Round = "ceil"; public int? Min; public int? Max; } public sealed class MatchExpr { public HashSet? QuestId; public HashSet? Category; public HashSet? AnyTag; public HashSet? AllTag; public MatchExpr? Not; public bool IsEmpty { get { if ((QuestId == null || QuestId.Count == 0) && (Category == null || Category.Count == 0) && (AnyTag == null || AnyTag.Count == 0) && (AllTag == null || AllTag.Count == 0)) { return Not == null; } return false; } } } public sealed class PerQuestEntry { public Dictionary TargetCounts = new Dictionary(); public List ExtraConditions = new List(); public List AvailableConditions = new List(); } public sealed class ExtraCondition { public string Kind = ""; public string Field = ""; public string Op = "=="; public JToken? Value; public string Quest = ""; public HashSet? AnyTag; public int Count; } public struct ExtraConditionResult { public bool Pass; public string? Reason; } public const string UserFileName = "QuestRequirements.user.json"; private const int MaxMatchDepth = 16; public static Dictionary> Tags { get; private set; } = new Dictionary>(); public static HashSet PlayerDataWhitelist { get; private set; } = new HashSet(); public static Dictionary Presets { get; private set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public static Dictionary PerQuest { get; private set; } = new Dictionary(); public static bool IsLoaded { get; private set; } public static int TagsVersion { get; private set; } public static string ActivePresetName { get; private set; } = "vanilla"; public static void AddTag(string questName, string tag) { if (!string.IsNullOrWhiteSpace(questName) && !string.IsNullOrWhiteSpace(tag)) { if (!Tags.ContainsKey(questName)) { Tags[questName] = new HashSet(StringComparer.OrdinalIgnoreCase); } if (Tags[questName].Add(tag.Trim())) { TagsVersion++; SaveTagsToUserOverlay(); } } } public static void RemoveTag(string questName, string tag) { if (Tags.TryGetValue(questName, out HashSet value) && value.Remove(tag)) { if (value.Count == 0) { Tags.Remove(questName); } TagsVersion++; SaveTagsToUserOverlay(); } } public static string ExportTagsJson() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown JObject val = new JObject(); foreach (KeyValuePair> tag in Tags) { if (tag.Value.Count == 0) { continue; } JArray val2 = new JArray(); foreach (string item in tag.Value) { val2.Add(JToken.op_Implicit(item)); } val[tag.Key] = (JToken)(object)val2; } return ((JToken)val).ToString((Formatting)0, Array.Empty()); } public static void ImportTagsJson(string json) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 if (string.IsNullOrEmpty(json)) { return; } JObject obj = JObject.Parse(json); Tags.Clear(); foreach (JProperty item in obj.Properties()) { JToken value = item.Value; JArray val = (JArray)(object)((value is JArray) ? value : null); if (val == null) { continue; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (JToken item2 in val) { if ((int)item2.Type == 8) { hashSet.Add(Extensions.Value((IEnumerable)item2)); } } Tags[item.Name] = hashSet; } TagsVersion++; SaveTagsToUserOverlay(); } public static void ClearAllTagOverrides() { try { string path = Path.Combine(Path.Combine(Paths.ConfigPath, "QuestMod"), "QuestRequirements.user.json"); if (File.Exists(path)) { JObject val; try { val = JObject.Parse(File.ReadAllText(path)); } catch { return; } val.Remove("tags"); File.WriteAllText(path, ((JToken)val).ToString((Formatting)1, Array.Empty())); } Reload(); QuestModPlugin.Log.LogInfo((object)"Cleared all tag overrides; reverted to embedded baseline."); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("ClearAllTagOverrides failed: " + ex.Message)); } } public static HashSet GetAllUniqueTags() { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair> tag in Tags) { foreach (string item in tag.Value) { hashSet.Add(item); } } return hashSet; } private static void SaveTagsToUserOverlay() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0051: 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_0068: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown try { string text = Path.Combine(Paths.ConfigPath, "QuestMod"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, "QuestRequirements.user.json"); JObject val; if (File.Exists(text2)) { try { val = JObject.Parse(File.ReadAllText(text2)); } catch { val = new JObject { ["version"] = JToken.op_Implicit(1) }; } } else { val = new JObject { ["version"] = JToken.op_Implicit(1) }; } JObject val2 = new JObject(); foreach (KeyValuePair> tag in Tags) { if (tag.Value.Count == 0) { continue; } JArray val3 = new JArray(); foreach (string item in tag.Value) { val3.Add(JToken.op_Implicit(item)); } val2[tag.Key] = (JToken)(object)val3; } val["tags"] = (JToken)(object)val2; string text3 = text2 + ".tmp"; File.WriteAllText(text3, ((JToken)val).ToString((Formatting)1, Array.Empty())); if (File.Exists(text2)) { File.Delete(text2); } File.Move(text3, text2); QuestModPlugin.LogDebugInfo("Saved tags to " + text2); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("Failed to save tags to user overlay: " + ex.Message)); } } public static void Reload() { IsLoaded = false; Load(); } public static void Load() { if (!IsLoaded) { JObject val = LoadEmbedded(); JObject val2 = LoadUserOverlay(); ParseRoot((JObject)((val2 == null) ? ((object)val) : ((object)MergeObjects(val, val2)))); IsLoaded = true; QuestModPlugin.Log.LogInfo((object)($"QuestRequirements loaded: {Tags.Count} tagged ids, {Presets.Count} presets, " + string.Format("{0} per-quest overrides (overlay={1})", PerQuest.Count, (val2 != null) ? "yes" : "no"))); } } private static JObject LoadEmbedded() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("QuestMod.Data.QuestRequirements.json"); if (stream == null) { QuestModPlugin.Log.LogError((object)"Embedded QuestRequirements.json missing"); return new JObject(); } using StreamReader streamReader = new StreamReader(stream); return JObject.Parse(streamReader.ReadToEnd()); } private static JObject? LoadUserOverlay() { try { string text = Path.Combine(Paths.ConfigPath, "QuestMod"); Directory.CreateDirectory(text); string path = Path.Combine(text, "QuestRequirements.user.json"); if (!File.Exists(path)) { File.WriteAllText(path, "{\n \"_doc\": \"See docs/CustomRequirements.md. This file overlays the embedded baseline.\",\n \"version\": 1\n}\n"); return null; } return JObject.Parse(File.ReadAllText(path)); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("Failed to load user QuestRequirements overlay: " + ex.Message)); return null; } } private static JObject MergeObjects(JObject baseline, JObject overlay) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown JObject val = (JObject)((JToken)baseline).DeepClone(); foreach (JProperty item in overlay.Properties()) { JToken value = item.Value; JObject val2 = (JObject)(object)((value is JObject) ? value : null); if (val2 != null) { JToken obj = val[item.Name]; JObject val3 = (JObject)(object)((obj is JObject) ? obj : null); if (val3 != null) { val[item.Name] = (JToken)(object)MergeObjects(val3, val2); continue; } } val[item.Name] = item.Value.DeepClone(); } return val; } private static void ParseRoot(JObject root) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Invalid comparison between Unknown and I4 //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Invalid comparison between Unknown and I4 Tags.Clear(); TagsVersion++; PlayerDataWhitelist.Clear(); Presets.Clear(); PerQuest.Clear(); JToken obj = root["playerDataWhitelist"]; JArray val = (JArray)(object)((obj is JArray) ? obj : null); if (val != null) { foreach (JToken item in val) { if ((int)item.Type == 8) { PlayerDataWhitelist.Add(Extensions.Value((IEnumerable)item)); } } } JToken obj2 = root["tags"]; JObject val2 = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val2 != null) { foreach (JProperty item2 in val2.Properties()) { JToken value = item2.Value; JArray val3 = (JArray)(object)((value is JArray) ? value : null); if (val3 == null) { continue; } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (JToken item3 in val3) { if ((int)item3.Type == 8) { hashSet.Add(Extensions.Value((IEnumerable)item3)); } } Tags[item2.Name] = hashSet; } } JToken obj3 = root["presets"]; JObject val4 = (JObject)(object)((obj3 is JObject) ? obj3 : null); if (val4 != null) { foreach (JProperty item4 in val4.Properties()) { JToken value2 = item4.Value; JObject val5 = (JObject)(object)((value2 is JObject) ? value2 : null); if (val5 != null) { Presets[item4.Name] = ParsePreset(item4.Name, val5); } } } if (!Presets.ContainsKey("vanilla")) { Presets["vanilla"] = new Preset { Name = "vanilla", Description = "no changes" }; } JToken obj4 = root["perQuest"]; JObject val6 = (JObject)(object)((obj4 is JObject) ? obj4 : null); if (val6 == null) { return; } foreach (JProperty item5 in val6.Properties()) { JToken value3 = item5.Value; JObject val7 = (JObject)(object)((value3 is JObject) ? value3 : null); if (val7 != null) { PerQuest[item5.Name] = ParsePerQuest(val7); } } } private static Preset ParsePreset(string name, JObject obj) { Preset obj2 = new Preset { Name = name }; JToken obj3 = obj["description"]; obj2.Description = ((obj3 != null) ? Extensions.Value((IEnumerable)obj3) : null) ?? ""; Preset preset = obj2; JToken obj4 = obj["rules"]; JArray val = (JArray)(object)((obj4 is JArray) ? obj4 : null); if (val != null) { foreach (JToken item in val) { JObject val2 = (JObject)(object)((item is JObject) ? item : null); if (val2 != null) { preset.Rules.Add(ParseRule(val2)); } } } return preset; } private static Rule ParseRule(JObject obj) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Invalid comparison between Unknown and I4 //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Invalid comparison between Unknown and I4 //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Invalid comparison between Unknown and I4 //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Invalid comparison between Unknown and I4 Rule rule = new Rule(); JToken obj2 = obj["match"]; JObject val = (JObject)(object)((obj2 is JObject) ? obj2 : null); rule.Match = ((val != null) ? ParseMatch(val) : null); JToken obj3 = obj["round"]; rule.Round = ((obj3 != null) ? Extensions.Value((IEnumerable)obj3) : null) ?? "ceil"; Rule rule2 = rule; JToken obj4 = obj["set"]; if (obj4 != null && (int)obj4.Type == 6) { rule2.Set = Extensions.Value((IEnumerable)obj["set"]); } JToken obj5 = obj["scale"]; if (obj5 == null || (int)obj5.Type != 7) { JToken obj6 = obj["scale"]; if (obj6 == null || (int)obj6.Type != 6) { goto IL_00d9; } } rule2.Scale = Extensions.Value((IEnumerable)obj["scale"]); goto IL_00d9; IL_00d9: JToken obj7 = obj["min"]; if (obj7 != null && (int)obj7.Type == 6) { rule2.Min = Extensions.Value((IEnumerable)obj["min"]); } JToken obj8 = obj["max"]; if (obj8 != null && (int)obj8.Type == 6) { rule2.Max = Extensions.Value((IEnumerable)obj["max"]); } return rule2; } private static MatchExpr ParseMatch(JObject obj) { return ParseMatch(obj, 0); } private static MatchExpr ParseMatch(JObject obj, int depth) { if (depth >= 16) { QuestModPlugin.Log.LogWarning((object)$"QuestRequirements: match expression nested deeper than {16}; treating inner 'not' as empty."); return new MatchExpr(); } MatchExpr matchExpr = new MatchExpr(); matchExpr.QuestId = ReadStringSet(obj["questId"]); matchExpr.Category = ReadStringSet(obj["category"]); matchExpr.AnyTag = ReadStringSet(obj["anyTag"]); matchExpr.AllTag = ReadStringSet(obj["allTag"]); JToken obj2 = obj["not"]; JObject val = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val != null) { matchExpr.Not = ParseMatch(val, depth + 1); } return matchExpr; } private static HashSet? ReadStringSet(JToken? t) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Invalid comparison between Unknown and I4 JArray val = (JArray)(object)((t is JArray) ? t : null); if (val != null) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); { foreach (JToken item in val) { if ((int)item.Type == 8) { hashSet.Add(Extensions.Value((IEnumerable)item)); } } return hashSet; } } return null; } private static PerQuestEntry ParsePerQuest(JObject obj) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Invalid comparison between Unknown and I4 PerQuestEntry perQuestEntry = new PerQuestEntry(); JToken obj2 = obj["targets"]; JObject val = (JObject)(object)((obj2 is JObject) ? obj2 : null); if (val != null) { foreach (JProperty item in val.Properties()) { if (!int.TryParse(item.Name, out var result)) { continue; } JToken value = item.Value; JObject val2 = (JObject)(object)((value is JObject) ? value : null); if (val2 != null) { JToken obj3 = val2["count"]; if (obj3 != null && (int)obj3.Type == 6) { perQuestEntry.TargetCounts[result] = Extensions.Value((IEnumerable)val2["count"]); } } } } JToken obj4 = obj["extraConditions"]; JArray val3 = (JArray)(object)((obj4 is JArray) ? obj4 : null); if (val3 != null) { foreach (JToken item2 in val3) { JObject val4 = (JObject)(object)((item2 is JObject) ? item2 : null); if (val4 != null) { perQuestEntry.ExtraConditions.Add(ParseExtraCondition(val4)); } } } JToken obj5 = obj["availableConditions"]; JArray val5 = (JArray)(object)((obj5 is JArray) ? obj5 : null); if (val5 != null) { foreach (JToken item3 in val5) { JObject val6 = (JObject)(object)((item3 is JObject) ? item3 : null); if (val6 != null) { perQuestEntry.AvailableConditions.Add(ParseExtraCondition(val6)); } } } return perQuestEntry; } private static ExtraCondition ParseExtraCondition(JObject co) { ExtraCondition extraCondition = new ExtraCondition(); JToken obj = co["kind"]; extraCondition.Kind = ((obj != null) ? Extensions.Value((IEnumerable)obj) : null) ?? ""; JToken obj2 = co["field"]; extraCondition.Field = ((obj2 != null) ? Extensions.Value((IEnumerable)obj2) : null) ?? ""; JToken obj3 = co["op"]; extraCondition.Op = ((obj3 != null) ? Extensions.Value((IEnumerable)obj3) : null) ?? "=="; extraCondition.Value = co["value"]; JToken obj4 = co["quest"]; extraCondition.Quest = ((obj4 != null) ? Extensions.Value((IEnumerable)obj4) : null) ?? ""; extraCondition.AnyTag = ReadStringSet(co["anyTag"]); JToken obj5 = co["count"]; extraCondition.Count = ((obj5 != null) ? Extensions.Value((IEnumerable)obj5) : 0); return extraCondition; } public static bool MatchQuest(MatchExpr? expr, string questName) { return MatchQuest(expr, questName, 0); } private static bool MatchQuest(MatchExpr? expr, string questName, int depth) { if (expr == null || expr.IsEmpty) { return true; } if (depth >= 16) { return true; } if (expr.QuestId != null && expr.QuestId.Count > 0 && !expr.QuestId.Contains(questName)) { return false; } if (expr.Category != null && expr.Category.Count > 0) { string category = QuestCompletionOverrides.GetCategory(questName); if (category == null || !expr.Category.Contains(category)) { return false; } } if (expr.AnyTag != null && expr.AnyTag.Count > 0) { if (!Tags.TryGetValue(questName, out HashSet value)) { return false; } bool flag = false; foreach (string item in expr.AnyTag) { if (value.Contains(item)) { flag = true; break; } } if (!flag) { return false; } } if (expr.AllTag != null && expr.AllTag.Count > 0) { if (!Tags.TryGetValue(questName, out HashSet value2)) { return false; } foreach (string item2 in expr.AllTag) { if (!value2.Contains(item2)) { return false; } } } if (expr.Not != null && MatchQuest(expr.Not, questName, depth + 1)) { return false; } return true; } public static void SetActivePreset(string name) { if (string.IsNullOrEmpty(name)) { name = "vanilla"; } if (!Presets.ContainsKey(name)) { ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogWarning((object)("SetActivePreset: unknown preset '" + name + "', falling back to vanilla")); } name = "vanilla"; } ActivePresetName = name; QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData != null && questModSaveData.ActiveDslPreset != name) { questModSaveData.ActiveDslPreset = name; } if (QuestCompletionOverrides.IsInitialized) { ApplyActivePreset(); } } public static string[] GetPresetNames() { List list = new List(Presets.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); return list.ToArray(); } public static void ApplyActivePreset() { if (!IsLoaded || !QuestCompletionOverrides.IsInitialized) { return; } Dictionary dictionary = (QuestModPlugin.Instance?.SaveData)?.QuestTargetOverrides ?? new Dictionary(); Preset value = null; if (!Presets.TryGetValue(ActivePresetName, out value)) { value = (Presets.TryGetValue("vanilla", out Preset value2) ? value2 : null); } int num = 0; int num2 = 0; foreach (QuestOverrideInfo allQuestsWithTarget in QuestCompletionOverrides.GetAllQuestsWithTargets()) { string questName = allQuestsWithTarget.QuestName; for (int i = 0; i < allQuestsWithTarget.Targets.Count; i++) { string key = $"{questName}:{i}"; bool num3 = dictionary.ContainsKey(key); int originalCount = allQuestsWithTarget.Targets[i].OriginalCount; int num4 = allQuestsWithTarget.Targets[i].CurrentCount; bool flag = false; if (!num3 && value != null) { foreach (Rule rule in value.Rules) { if (MatchQuest(rule.Match, questName)) { int num5 = num4; if (rule.Set.HasValue) { num5 = rule.Set.Value; } if (rule.Scale.HasValue) { num5 = ApplyRound((float)originalCount * rule.Scale.Value, rule.Round); } if (rule.Min.HasValue && num5 < rule.Min.Value) { num5 = rule.Min.Value; } if (rule.Max.HasValue && num5 > rule.Max.Value) { num5 = rule.Max.Value; } if (num5 != num4) { num4 = num5; flag = true; } } } } if (PerQuest.TryGetValue(questName, out PerQuestEntry value3) && value3.TargetCounts.TryGetValue(i, out var value4)) { if (value4 != num4) { num4 = value4; flag = true; num2++; } } else if (flag) { num++; } if (flag) { QuestCompletionOverrides.SetTargetCountTransient(questName, i, num4); } } } QuestModPlugin.Log.LogInfo((object)("QuestRequirements: applied preset '" + ActivePresetName + "' " + $"(preset={num}, perQuest={num2})")); } private static int ApplyRound(float v, string mode) { if (!(mode == "floor")) { if (mode == "nearest") { return (int)Math.Round(v, MidpointRounding.AwayFromZero); } return (int)Math.Ceiling(v); } return (int)Math.Floor(v); } public static ExtraConditionResult EvaluateExtraConditions(string questName) { if (!IsLoaded) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } if (!PerQuest.TryGetValue(questName, out PerQuestEntry value)) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } if (value.ExtraConditions.Count == 0) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } foreach (ExtraCondition extraCondition in value.ExtraConditions) { if (!EvalCondition(extraCondition, out string reason)) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = false; result.Reason = reason; return result; } } ExtraConditionResult result2 = default(ExtraConditionResult); result2.Pass = true; return result2; } public static ExtraConditionResult EvaluateAvailableConditions(string questName) { if (!IsLoaded) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } if (!PerQuest.TryGetValue(questName, out PerQuestEntry value)) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } if (value.AvailableConditions.Count == 0) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = true; return result; } foreach (ExtraCondition availableCondition in value.AvailableConditions) { if (!EvalCondition(availableCondition, out string reason)) { ExtraConditionResult result = default(ExtraConditionResult); result.Pass = false; result.Reason = reason; return result; } } ExtraConditionResult result2 = default(ExtraConditionResult); result2.Pass = true; return result2; } private static bool EvalCondition(ExtraCondition c, out string reason) { reason = ""; switch (c.Kind) { case "playerData": return EvalPlayerData(c, out reason); case "questCompleted": { IDictionary runtimeData2 = QuestDataAccess.GetRuntimeData(); if (runtimeData2 == null || !runtimeData2.Contains(c.Quest)) { reason = "requires quest '" + c.Quest + "' completed (not in runtime data)"; return false; } if (!QuestDataAccess.IsCompleted(runtimeData2[c.Quest])) { reason = "requires quest '" + c.Quest + "' completed"; return false; } return true; } case "tagAccepted": { IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null || c.AnyTag == null) { reason = "tagAccepted: missing data"; return false; } int num = 0; foreach (KeyValuePair> tag in Tags) { bool flag = false; foreach (string item in c.AnyTag) { if (tag.Value.Contains(item)) { flag = true; break; } } if (flag && runtimeData.Contains(tag.Key) && QuestDataAccess.IsAccepted(runtimeData[tag.Key])) { num++; } } if (num < c.Count) { reason = string.Format("requires {0} accepted with tag(s) {1} (have {2})", c.Count, string.Join(",", c.AnyTag), num); return false; } return true; } default: reason = "unknown condition kind '" + c.Kind + "'"; return false; } } private static bool EvalPlayerData(ExtraCondition c, out string reason) { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Invalid comparison between Unknown and I4 reason = ""; if (PlayerData.instance == null) { reason = "no PlayerData"; return false; } if (!PlayerDataWhitelist.Contains(c.Field)) { reason = "playerData field '" + c.Field + "' not in whitelist"; return false; } FieldInfo field = typeof(PlayerData).GetField(c.Field, BindingFlags.Instance | BindingFlags.Public); if (field == null) { reason = "playerData field '" + c.Field + "' not found"; return false; } object value = field.GetValue(PlayerData.instance); if (value == null) { reason = "playerData '" + c.Field + "' is null"; return false; } if (value is bool flag) { if (c.Value == null || (int)c.Value.Type != 9) { reason = "playerData '" + c.Field + "' is bool but condition value is not boolean"; return false; } if (c.Op != "==" && c.Op != "!=" && !string.IsNullOrEmpty(c.Op)) { reason = "playerData '" + c.Field + "' is bool but op '" + c.Op + "' is not == or !="; return false; } bool flag2 = Extensions.Value((IEnumerable)c.Value); bool flag3 = ((c.Op == "!=") ? (flag != flag2) : (flag == flag2)); if (!flag3) { reason = $"{c.Field} ({flag}) {c.Op} {flag2} failed"; } return flag3; } double num = Convert.ToDouble(value); double num2; try { JToken? value2 = c.Value; num2 = ((value2 != null) ? value2.ToObject() : 0.0); } catch { reason = "playerData condition '" + c.Field + "' value not numeric"; return false; } bool flag4 = c.Op switch { ">=" => num >= num2, ">" => num > num2, "<=" => num <= num2, "<" => num < num2, "!=" => Math.Abs(num - num2) > 0.0001, _ => Math.Abs(num - num2) <= 0.0001, }; if (!flag4) { reason = $"{c.Field} ({num}) {c.Op} {num2} failed"; } return flag4; } } public class QuestGUI : MonoBehaviour { private struct SaveDataSnapshot { public AllWishesMode Mode; public bool AllAccepted; public bool Initialized; public bool Override; public string Preset; public bool? EnableCustom; public bool? EnableRC; public int PolicyCount; public int InjectedCount; public int CompletedCount; public int TargetOverrideCount; public int WishOverrideCount; public bool BypassFleatopia; public bool BypassMandatory; public bool BypassFaydown; public bool BypassNeedolin; public bool BypassBoneboard; public bool BypassAllWalls; } private bool show; private Vector2 questScroll; private Vector2 overrideScroll; private Rect rect = new Rect(20f, 20f, 580f, 580f); private int tab; private string[] tabs; private Action[] tabDrawers; private int categoryTab; private Dictionary countInputs = new Dictionary(); private Dictionary> cachedByCategory = new Dictionary>(); private float multiplier = 1f; private string multiplierText = "1.0"; private Vector2 checklistScroll; private List cachedQuestList; private bool questListDirty = true; private Vector2 silkSoulScroll; private string thresholdInput = ""; private Dictionary pointInputs = new Dictionary(); private string _uiOpenSnapshotSaveData; private string _uiOpenSnapshotTags; private SaveDataSnapshot? _scalarSnapshot; private int _snapshotTagsVersion = -1; private bool _cachedSilkSoulTab; private const string GourmandQuestId = "Great Gourmand"; private Vector2 deliveryScroll; private string gourmandDecayInput = ""; private List cachedChainList; private float _bulkResetArmedAt = -1f; private const float BulkResetArmWindow = 4f; private string _questFilter = ""; private Vector2 _tagsScroll; private string _tagInput = ""; private string _tagsFilter = ""; private int _tagsCategoryTab; private float _tagsResetArmedAt = -1f; private const float TagsResetArmWindow = 4f; private List undoSnapshot; private string undoSaveDataSnapshot; private Vector2 _toolsScroll; private float _safetyOverrideArmedAt = -1f; private const float SafetyOverrideArmWindow = 4f; private float _importArmedAt = -1f; private const float ImportArmWindow = 4f; private string _lastIoMessage = ""; private float _resetAllArmedAt = -1f; private const float ResetAllArmWindow = 4f; private int presetSelected = -1; private void DrawChecklistTab() { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!QuestCompletionOverrides.IsInitialized) { GUILayout.Label("Not initialized - load a save first", Array.Empty()); return; } checklistScroll = GUILayout.BeginScrollView(checklistScroll, Array.Empty()); foreach (KeyValuePair checklistQuest in QuestRegistry.ChecklistQuests) { string key = checklistQuest.Key; string[] value = checklistQuest.Value; bool flag = QuestRegistry.SequentialQuests.Contains(key); bool[] checklistStatus = QuestCompletionOverrides.GetChecklistStatus(key); int num = 0; for (int i = 0; i < checklistStatus.Length; i++) { if (checklistStatus[i]) { num++; } } GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); string text = (flag ? " (sequential)" : ""); GUILayout.Label($"{key}{text} ({num}/{value.Length})", QuestGUISkin.SectionHeader, Array.Empty()); for (int j = 0; j < value.Length && j < checklistStatus.Length; j++) { bool flag2 = checklistStatus[j]; bool flag3 = true; if (flag && !flag2) { int num2 = -1; for (int k = 0; k < checklistStatus.Length; k++) { if (!checklistStatus[k]) { num2 = k; break; } } flag3 = j == num2; } if (flag && flag3 && !QuestModPlugin.IsPureWishes && !QuestAcceptance.IsSequentialStageEncountered(key, j)) { flag3 = false; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(12f); string text2 = (flag2 ? "■" : "□") + " " + value[j]; if (flag3) { if (GUILayout.Button(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })) { int index = j; QuestCompletionOverrides.ToggleChecklistTarget(key, index, !flag2); } } else { GUI.enabled = false; GUILayout.Button(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUI.enabled = true; } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.Space(3f); } GUILayout.EndScrollView(); } private void Update() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) KeyboardShortcut value = QuestModPlugin.GuiToggleKey.Value; if (!((KeyboardShortcut)(ref value)).IsDown()) { return; } if (show) { OnGuiClosing(); } show = !show; if (show) { OnGuiOpened(); questListDirty = true; if (tab == 1) { RefreshOverrides(); } } } private static SaveDataSnapshot? CaptureScalarSnapshot() { QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData == null) { return null; } SaveDataSnapshot value = default(SaveDataSnapshot); value.Mode = questModSaveData.AllWishesMode; value.AllAccepted = questModSaveData.AllQuestsAccepted; value.Initialized = questModSaveData.QuestModInitialized; value.Override = questModSaveData.OverrideSafetyForThisSave; value.Preset = questModSaveData.ActiveDslPreset ?? "vanilla"; value.EnableCustom = questModSaveData.EnableCustomRequirements; value.EnableRC = questModSaveData.EnableFullRemoteComplete; value.PolicyCount = questModSaveData.QuestPolicies?.Count ?? 0; value.InjectedCount = questModSaveData.InjectedQuests?.Count ?? 0; value.CompletedCount = questModSaveData.CompletedQuests?.Count ?? 0; value.TargetOverrideCount = questModSaveData.QuestTargetOverrides?.Count ?? 0; value.WishOverrideCount = questModSaveData.WishLocationOverrides?.Count ?? 0; value.BypassFleatopia = questModSaveData.Prereqs?.BypassFleatopia ?? false; value.BypassMandatory = questModSaveData.Prereqs?.BypassMandatoryWishes ?? false; value.BypassFaydown = questModSaveData.Prereqs?.BypassFaydownCloak ?? false; value.BypassNeedolin = questModSaveData.Prereqs?.BypassNeedolin ?? false; value.BypassBoneboard = questModSaveData.Prereqs?.BypassBonebottomQuestBoard ?? false; value.BypassAllWalls = questModSaveData.Prereqs?.BypassAllWishwalls ?? false; return value; } private static bool ScalarSnapshotEquals(SaveDataSnapshot a, SaveDataSnapshot b) { if (a.Mode == b.Mode && a.AllAccepted == b.AllAccepted && a.Initialized == b.Initialized && a.Override == b.Override && a.Preset == b.Preset && a.EnableCustom == b.EnableCustom && a.EnableRC == b.EnableRC && a.PolicyCount == b.PolicyCount && a.InjectedCount == b.InjectedCount && a.CompletedCount == b.CompletedCount && a.TargetOverrideCount == b.TargetOverrideCount && a.WishOverrideCount == b.WishOverrideCount && a.BypassFleatopia == b.BypassFleatopia && a.BypassMandatory == b.BypassMandatory && a.BypassFaydown == b.BypassFaydown && a.BypassNeedolin == b.BypassNeedolin && a.BypassBoneboard == b.BypassBoneboard) { return a.BypassAllWalls == b.BypassAllWalls; } return false; } private void OnGuiOpened() { try { _uiOpenSnapshotSaveData = QuestModPlugin.ExportSaveDataToJson(); _uiOpenSnapshotTags = (QuestRequirements.IsLoaded ? QuestRequirements.ExportTagsJson() : null); _snapshotTagsVersion = QuestRequirements.TagsVersion; _scalarSnapshot = CaptureScalarSnapshot(); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("OnGuiOpened snapshot failed: " + ex.Message)); } } private void OnGuiClosing() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) try { bool num = ScalarDirty(); bool flag = _uiOpenSnapshotTags != null && QuestRequirements.IsLoaded && !string.Equals(QuestRequirements.ExportTagsJson(), _uiOpenSnapshotTags, StringComparison.Ordinal); if (num && _uiOpenSnapshotSaveData != null) { QuestModPlugin.ImportSaveDataFromJson(_uiOpenSnapshotSaveData); QuestModToast.Show("Discarded SaveData edits", new Color(0.9f, 0.7f, 0.4f), 3f); } if (flag) { QuestRequirements.ImportTagsJson(_uiOpenSnapshotTags); QuestModToast.Show("Discarded tag edits", new Color(0.9f, 0.7f, 0.4f), 3f); } if (num || flag) { QuestModPlugin.Log.LogInfo((object)"UI closed without explicit save: reverted to snapshot"); } } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("OnGuiClosing revert failed: " + ex.Message)); } } internal void MarkSaveExplicit() { //IL_0086: Unknown result type (might be due to invalid IL or missing references) try { _uiOpenSnapshotSaveData = QuestModPlugin.ExportSaveDataToJson(); _uiOpenSnapshotTags = (QuestRequirements.IsLoaded ? QuestRequirements.ExportTagsJson() : null); _snapshotTagsVersion = QuestRequirements.TagsVersion; _scalarSnapshot = CaptureScalarSnapshot(); } catch { } _safetyOverrideArmedAt = -1f; _resetAllArmedAt = -1f; _importArmedAt = -1f; _bulkResetArmedAt = -1f; _tagsResetArmedAt = -1f; QuestModToast.Show("Changes saved", new Color(0.5f, 0.9f, 0.5f), 2.5f); } internal void ReplaceOpenSnapshot() { try { _uiOpenSnapshotSaveData = QuestModPlugin.ExportSaveDataToJson(); _uiOpenSnapshotTags = (QuestRequirements.IsLoaded ? QuestRequirements.ExportTagsJson() : null); _snapshotTagsVersion = QuestRequirements.TagsVersion; _scalarSnapshot = CaptureScalarSnapshot(); } catch { } } internal bool HasUnsavedChanges() { try { if (!_scalarSnapshot.HasValue && QuestModPlugin.Instance?.SaveData != null) { _uiOpenSnapshotSaveData = QuestModPlugin.ExportSaveDataToJson(); _uiOpenSnapshotTags = (QuestRequirements.IsLoaded ? QuestRequirements.ExportTagsJson() : null); _snapshotTagsVersion = QuestRequirements.TagsVersion; _scalarSnapshot = CaptureScalarSnapshot(); } if (ScalarDirty()) { return true; } if (_uiOpenSnapshotTags != null && QuestRequirements.IsLoaded) { if (QuestRequirements.TagsVersion == _snapshotTagsVersion) { return false; } if (!string.Equals(QuestRequirements.ExportTagsJson(), _uiOpenSnapshotTags, StringComparison.Ordinal)) { return true; } _snapshotTagsVersion = QuestRequirements.TagsVersion; } } catch { } return false; } private bool ScalarDirty() { if (!_scalarSnapshot.HasValue) { return false; } SaveDataSnapshot? saveDataSnapshot = CaptureScalarSnapshot(); if (!saveDataSnapshot.HasValue) { return false; } if (!ScalarSnapshotEquals(_scalarSnapshot.Value, saveDataSnapshot.Value)) { return true; } if (!string.IsNullOrEmpty(_uiOpenSnapshotSaveData)) { try { if (!string.Equals(QuestModPlugin.ExportSaveDataToJson(), _uiOpenSnapshotSaveData, StringComparison.Ordinal)) { return true; } } catch { } } return false; } private float GetGuiScale() { float value = QuestModPlugin.GuiScale.Value; if (value > 0f) { return Mathf.Round(value * 20f) / 20f; } float dpi = Screen.dpi; if (dpi <= 0f) { return 1f; } return Mathf.Round(Mathf.Clamp(dpi / 96f, 0.5f, 3f) * 20f) / 20f; } private void OnGUI() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) QuestModToast.DrawAll(); if (show) { float guiScale = GetGuiScale(); Matrix4x4 matrix = GUI.matrix; GUI.matrix = Matrix4x4.TRS(Vector3.zero, Quaternion.identity, new Vector3(guiScale, guiScale, 1f)); GUI.skin = QuestGUISkin.Get(); KeyboardShortcut value = QuestModPlugin.GuiToggleKey.Value; KeyCode mainKey = ((KeyboardShortcut)(ref value)).MainKey; string text = ((object)(KeyCode)(ref mainKey)).ToString(); Version version = typeof(QuestModPlugin).Assembly.GetName().Version; rect = GUI.Window(12345, rect, new WindowFunction(Draw), $"Quest Manager ({text}) v{version.Major}.{version.Minor}.{version.Build}"); GUI.matrix = matrix; } } private void RefreshOverrides() { cachedByCategory.Clear(); string[] categories = QuestRegistry.Categories; foreach (string text in categories) { cachedByCategory[text] = QuestCompletionOverrides.GetQuestsByCategory(text); } } private void Draw(int id) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Invalid comparison between Unknown and I4 //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_010f: Expected O, but got Unknown //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01af: Unknown result type (might be due to invalid IL or missing references) if ((int)Event.current.type == 8) { GUI.tooltip = ""; } RebuildTabs(); GUILayout.BeginHorizontal(Array.Empty()); for (int i = 0; i < tabs.Length; i++) { GUIStyle val = ((i == tab) ? GUI.skin.GetStyle("ToolbarButtonActive") : GUI.skin.GetStyle("ToolbarButton")); if (GUILayout.Button(tabs[i], val, Array.Empty())) { if (i != tab && tabs[i] == "Targets") { RefreshOverrides(); } tab = i; } } GUILayout.EndHorizontal(); if (tab >= tabs.Length) { tab = 0; } GUILayout.Space(6f); tabDrawers[tab](); if ((int)Event.current.type == 7 && !string.IsNullOrEmpty(GUI.tooltip)) { Vector2 mousePosition = Event.current.mousePosition; GUIContent val2 = new GUIContent(GUI.tooltip); Vector2 val3 = QuestGUISkin.TooltipStyle.CalcSize(val2); val3.x = Mathf.Min(val3.x, 280f); val3.y = QuestGUISkin.TooltipStyle.CalcHeight(val2, val3.x); float num = Mathf.Min(mousePosition.x + 12f, ((Rect)(ref rect)).width - val3.x - 10f); float num2 = Mathf.Min(mousePosition.y + 18f, ((Rect)(ref rect)).height - val3.y - 10f); GUI.Label(new Rect(num, num2, val3.x, val3.y), val2, QuestGUISkin.TooltipStyle); } GUI.DragWindow(); } private void RebuildTabs() { bool value = QuestModPlugin.EnableSilkSoulTab.Value; if (tabs == null || value != _cachedSilkSoulTab) { _cachedSilkSoulTab = value; List list = new List { "Quests", "Targets", "Delivery", "Checklist" }; List list2 = new List { DrawQuestsTab, DrawCompletionTab, DrawDeliveryTab, DrawChecklistTab }; if (value) { list.Add("Silk & Soul"); list2.Add(DrawSilkSoulTab); } list.Add("Tags"); list2.Add(DrawTagsTab); list.Add("Tools"); list2.Add(DrawToolsTab); tabs = list.ToArray(); tabDrawers = list2.ToArray(); } } private void DrawDeliveryTab() { //IL_0027: 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_0036: Unknown result type (might be due to invalid IL or missing references) if (questListDirty) { cachedQuestList = QuestAcceptance.GetQuestList(); cachedChainList = QuestAcceptance.GetChainList(); questListDirty = false; } deliveryScroll = GUILayout.BeginScrollView(deliveryScroll, Array.Empty()); DrawDeliveryProtectionSection(); GUILayout.Space(6f); DrawGourmandTimerSection(); GUILayout.Space(8f); DrawDeliveryQuestList(); GUILayout.EndScrollView(); } private void DrawDeliveryProtectionSection() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown GUILayout.Label("Delivery Items", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); bool value = QuestModPlugin.QuestItemInvincible.Value; bool flag = GUILayout.Toggle(value, new GUIContent(" Quest Item Invincibility", "Prevent delivery quest items (Courier Supplies, etc.) from being destroyed by enemy attacks."), Array.Empty()); if (flag != value) { QuestModPlugin.QuestItemInvincible.Value = flag; } GUILayout.EndVertical(); } private void DrawGourmandTimerSection() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Expected O, but got Unknown //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown GUILayout.Label("Great Gourmand: Courier's Rasher Timer", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); bool value = QuestModPlugin.GourmandStopDecay.Value; bool flag = GUILayout.Toggle(value, new GUIContent(" Stop Rasher Decay", "Freeze the Courier's Rasher timer so it never decays while carried."), Array.Empty()); if (flag != value) { QuestModPlugin.GourmandStopDecay.Value = flag; } GUILayout.Space(4f); float value2 = QuestModPlugin.GourmandDecaySeconds.Value; if (gourmandDecayInput == "") { gourmandDecayInput = value2.ToString("0.#"); } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Decay (s):", "Seconds per segment before a durability tick is lost (default 47)."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(90f) }); if (GUILayout.Button("−", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { float value3 = Mathf.Max(1f, value2 - 1f); QuestModPlugin.GourmandDecaySeconds.Value = value3; gourmandDecayInput = value3.ToString("0.#"); } gourmandDecayInput = GUILayout.TextField(gourmandDecayInput, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }); if (GUILayout.Button("+", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { float value4 = Mathf.Min(600f, value2 + 1f); QuestModPlugin.GourmandDecaySeconds.Value = value4; gourmandDecayInput = value4.ToString("0.#"); } if (GUILayout.Button("Set", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(36f) }) && float.TryParse(gourmandDecayInput, out var result)) { QuestModPlugin.GourmandDecaySeconds.Value = Mathf.Clamp(result, 1f, 600f); } if (GUILayout.Button("Reset", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) })) { QuestModPlugin.GourmandDecaySeconds.Value = 47f; gourmandDecayInput = "47"; } if (Mathf.Abs(value2 - 47f) > 0.01f) { GUILayout.Label("(default: 47)", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Label($" Total carry budget: {value2 * 8f:0.#}s (8 × {value2:0.#}s)", Array.Empty()); GUILayout.EndVertical(); } private void DrawDeliveryQuestList() { //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Expected O, but got Unknown if (cachedQuestList == null) { return; } GUILayout.Label("Delivery Quests", QuestGUISkin.SectionHeader, Array.Empty()); IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); List list = new List(); foreach (KeyValuePair questCategory in QuestRegistry.QuestCategories) { if (questCategory.Value == "Delivery") { list.Add(questCategory.Key); } } list.Add("Great Gourmand"); list.Sort(StringComparer.OrdinalIgnoreCase); Dictionary dictionary = new Dictionary(); foreach (QuestInfo cachedQuest in cachedQuestList) { dictionary[cachedQuest.Name] = cachedQuest; } int num = 0; foreach (string item in list) { if (!dictionary.TryGetValue(item, out var value)) { QuestInfo questInfo = default(QuestInfo); questInfo.Name = item; questInfo.DisplayName = QuestAcceptance.GetDisplayName(item); questInfo.IsAccepted = false; questInfo.IsCompleted = false; value = questInfo; } if (!QuestModPlugin.OnlyDiscoveredQuests.Value || runtimeData == null || runtimeData.Contains(item)) { num++; string obj = (value.IsCompleted ? "✓" : (value.IsAccepted ? "◐" : "○")); string text = (QuestModPlugin.ShowQuestDisplayNames.Value ? value.DisplayName : value.Name); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent(obj + " " + text, value.Name), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(260f) }); GUILayout.FlexibleSpace(); if (!value.IsAccepted && GUILayout.Button("Accept", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { QuestAcceptance.AcceptQuest(value.Name); questListDirty = true; } if (value.IsAccepted && !value.IsCompleted && GUILayout.Button("Drop", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) })) { QuestAcceptance.UnacceptQuest(value.Name); questListDirty = true; } if (value.IsAccepted && !value.IsCompleted && GUILayout.Button("Complete", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) })) { QuestAcceptance.CompleteQuest(value.Name); questListDirty = true; } if (value.IsCompleted && GUILayout.Button("Undo", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) })) { QuestAcceptance.UncompleteQuest(value.Name); questListDirty = true; } GUILayout.EndHorizontal(); } } if (num == 0) { GUILayout.Label(" (no delivery quests discovered yet, visit a courier or quest board)", Array.Empty()); } } private bool QuestFilterMatches(string name, string display) { if (string.IsNullOrEmpty(_questFilter)) { return true; } if (name == null || name.IndexOf(_questFilter, StringComparison.OrdinalIgnoreCase) < 0) { if (display == null) { return false; } return display.IndexOf(_questFilter, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private void DrawQuestsTab() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Expected O, but got Unknown //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Expected O, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0449: Expected O, but got Unknown //IL_060c: Unknown result type (might be due to invalid IL or missing references) if (questListDirty) { cachedQuestList = QuestAcceptance.GetQuestList(); cachedChainList = QuestAcceptance.GetChainList(); questListDirty = false; } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Filter:", "Type to filter chains and quests by name (case insensitive)"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); _questFilter = GUILayout.TextField(_questFilter ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(160f) }); if (!string.IsNullOrEmpty(_questFilter) && GUILayout.Button("×", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(25f) })) { _questFilter = ""; } GUILayout.EndHorizontal(); GUILayout.Space(4f); questScroll = GUILayout.BeginScrollView(questScroll, Array.Empty()); DrawBulkPolicyReset(); if (cachedChainList != null) { GUILayout.Label("Chains", QuestGUISkin.SectionHeader, Array.Empty()); foreach (ChainInfo cachedChain in cachedChainList) { if (QuestFilterMatches(cachedChain.ChainName, cachedChain.DisplayName)) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); string text; string text2; if (cachedChain.IsFullyComplete) { text = "Complete"; text2 = "✓"; } else if (cachedChain.CurrentStep < 0) { text = "Not started"; text2 = "○"; } else { string displayName = QuestAcceptance.GetDisplayName(cachedChain.Steps[cachedChain.CurrentStep]); text = $"Step {cachedChain.CurrentStep + 1}/{cachedChain.TotalSteps}: {displayName}"; text2 = "◐"; } string text3 = (QuestModPlugin.ShowQuestDisplayNames.Value ? cachedChain.DisplayName : cachedChain.ChainName); GUILayout.Label(new GUIContent(text2 + " " + text3, cachedChain.ChainName), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUILayout.Label(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.FlexibleSpace(); GUI.enabled = cachedChain.CurrentStep >= 0; if (GUILayout.Button("◀", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(25f) })) { QuestAcceptance.RewindChain(cachedChain.ChainName); questListDirty = true; } GUI.enabled = !cachedChain.IsFullyComplete; if (GUILayout.Button("▶", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(25f) })) { QuestAcceptance.AdvanceChain(cachedChain.ChainName); questListDirty = true; } GUI.enabled = true; GUILayout.EndHorizontal(); GUILayout.EndVertical(); } } } if (cachedQuestList != null) { GUILayout.Space(6f); GUILayout.Label("Quests", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.FlexibleSpace(); GUILayout.Label(new GUIContent("Avail", "Make this quest available regardless of chain/act prerequisites."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); GUILayout.Label(new GUIContent("Auto", "Auto-accept this quest on first scene load each session."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); GUILayout.Space(180f); GUILayout.EndHorizontal(); foreach (QuestInfo cachedQuest in cachedQuestList) { if (QuestAcceptance.IsChainStep(cachedQuest.Name) || !QuestFilterMatches(cachedQuest.Name, cachedQuest.DisplayName)) { continue; } string obj = (cachedQuest.IsCompleted ? "✓" : (cachedQuest.IsAccepted ? "◐" : "○")); GUILayout.BeginHorizontal(Array.Empty()); string text4 = (QuestModPlugin.ShowQuestDisplayNames.Value ? cachedQuest.DisplayName : cachedQuest.Name); GUILayout.Label(new GUIContent(obj + " " + text4, cachedQuest.Name), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(180f) }); GUILayout.FlexibleSpace(); QuestPolicy questPolicy = QuestPolicyStore.Get(cachedQuest.Name); bool flag = questPolicy?.Available ?? false; bool flag2 = questPolicy?.AutoAccept ?? false; bool flag3 = GUILayout.Toggle(flag, GUIContent.none, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); if (flag3 != flag) { QuestPolicyStore.SetAvailable(cachedQuest.Name, flag3); } GUI.enabled = flag3; bool flag4 = GUILayout.Toggle(flag2, GUIContent.none, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) }); if (flag4 != flag2) { QuestPolicyStore.SetAutoAccept(cachedQuest.Name, flag4); } GUI.enabled = true; if (!cachedQuest.IsAccepted && GUILayout.Button("Accept", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { QuestAcceptance.AcceptQuest(cachedQuest.Name); questListDirty = true; } if (cachedQuest.IsAccepted && !cachedQuest.IsCompleted && GUILayout.Button("Drop", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(45f) })) { QuestAcceptance.UnacceptQuest(cachedQuest.Name); questListDirty = true; } if (cachedQuest.IsAccepted && !cachedQuest.IsCompleted && GUILayout.Button("Complete", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) })) { QuestAcceptance.LastCompletionRefusal = null; bool flag5; if (QuestModPlugin.IsFullRemoteCompleteEnabled) { flag5 = QuestAcceptance.RemoteComplete(cachedQuest.Name); } else { QuestAcceptance.CompleteQuest(cachedQuest.Name); flag5 = string.IsNullOrEmpty(QuestAcceptance.LastCompletionRefusal); } if (!flag5 && !string.IsNullOrEmpty(QuestAcceptance.LastCompletionRefusal)) { QuestModToast.Show("Refused: " + QuestAcceptance.LastCompletionRefusal, new Color(1f, 0.6f, 0.4f), 4f); } questListDirty = true; } if (cachedQuest.IsCompleted && GUILayout.Button("Undo", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) })) { QuestAcceptance.UncompleteQuest(cachedQuest.Name); questListDirty = true; } GUILayout.EndHorizontal(); } } GUILayout.EndScrollView(); } private void DrawBulkPolicyReset() { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) Dictionary map = QuestPolicyStore.Map; int num = map?.Count ?? 0; if (num == 0 && _bulkResetArmedAt < 0f) { return; } GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label("Per-quest policies", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.Label("Per-quest Available + AutoAccept toggles. " + num + " entries on this save.", Array.Empty()); if (!(_bulkResetArmedAt > 0f) || !(Time.realtimeSinceStartup - _bulkResetArmedAt < 4f)) { if (num > 0 && GUILayout.Button("Clear all per-quest policies...", Array.Empty())) { _bulkResetArmedAt = Time.realtimeSinceStartup; } } else { float num2 = 4f - (Time.realtimeSinceStartup - _bulkResetArmedAt); GUI.color = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button($"Confirm: clear {num} policies [{num2:0.0}s]", Array.Empty())) { map?.Clear(); _bulkResetArmedAt = -1f; questListDirty = true; ReplaceOpenSnapshot(); QuestModPlugin.Log.LogInfo((object)("Cleared all per-quest policies (" + num + ")")); } GUI.color = Color.white; if (GUILayout.Button("Cancel", Array.Empty())) { _bulkResetArmedAt = -1f; } } GUILayout.EndVertical(); GUILayout.Space(6f); } private void DrawSilkSoulTab() { //IL_004a: 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_005b: 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_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) //IL_032d: Unknown result type (might be due to invalid IL or missing references) //IL_03a1: 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_05f7: Unknown result type (might be due to invalid IL or missing references) //IL_05fc: Unknown result type (might be due to invalid IL or missing references) //IL_060d: Unknown result type (might be due to invalid IL or missing references) //IL_0659: Unknown result type (might be due to invalid IL or missing references) //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_06bb: Unknown result type (might be due to invalid IL or missing references) //IL_06c0: Unknown result type (might be due to invalid IL or missing references) //IL_06cc: Unknown result type (might be due to invalid IL or missing references) //IL_06c5: Unknown result type (might be due to invalid IL or missing references) //IL_04da: Unknown result type (might be due to invalid IL or missing references) //IL_0512: Unknown result type (might be due to invalid IL or missing references) //IL_078b: Unknown result type (might be due to invalid IL or missing references) //IL_0790: Unknown result type (might be due to invalid IL or missing references) //IL_0705: Unknown result type (might be due to invalid IL or missing references) //IL_079c: Unknown result type (might be due to invalid IL or missing references) //IL_0795: Unknown result type (might be due to invalid IL or missing references) //IL_082d: Unknown result type (might be due to invalid IL or missing references) if (!SilkSoulOverrides.TryResolve()) { GUILayout.Label("Silk & Soul data not available (load a save first)", Array.Empty()); return; } int currentPoints = SilkSoulOverrides.GetCurrentPoints(); int threshold = SilkSoulOverrides.GetThreshold(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Quest Points:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }); Color color = GUI.color; GUI.color = ((currentPoints >= threshold) ? Color.green : Color.white); GUILayout.Label($"{currentPoints} / {threshold}", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) }); GUI.color = color; GUILayout.EndHorizontal(); GUILayout.Space(3f); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Threshold:", "Number of points needed to unlock Silk & Soul quest"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) }); if (GUILayout.Button("−", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { SilkSoulOverrides.SetThreshold(threshold - 1); thresholdInput = SilkSoulOverrides.GetThreshold().ToString(); } if (thresholdInput == "") { thresholdInput = threshold.ToString(); } thresholdInput = GUILayout.TextField(thresholdInput, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); if (GUILayout.Button("+", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { SilkSoulOverrides.SetThreshold(threshold + 1); thresholdInput = SilkSoulOverrides.GetThreshold().ToString(); } if (GUILayout.Button("Set", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(32f) }) && int.TryParse(thresholdInput, out var result)) { SilkSoulOverrides.SetThreshold(result); thresholdInput = SilkSoulOverrides.GetThreshold().ToString(); } if (GUILayout.Button("Reset", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) })) { SilkSoulOverrides.ResetThreshold(); thresholdInput = QuestRegistry.DefaultThreshold.ToString(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); if (threshold != QuestRegistry.DefaultThreshold) { GUILayout.Label($" (default: {QuestRegistry.DefaultThreshold})", Array.Empty()); } GUILayout.Space(4f); if (SilkSoulOverrides.HasEntries) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Reset All Values", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(120f) })) { SilkSoulOverrides.ResetAllPointValues(); pointInputs.Clear(); } if (GUILayout.Button("Set All to 0", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(100f) })) { foreach (SilkSoulOverrides.PointEntry pointEntry in SilkSoulOverrides.GetPointEntries()) { SilkSoulOverrides.SetPointValue(pointEntry.EntryIndex, 0f); pointInputs[pointEntry.QuestName] = "0"; } } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } GUILayout.Space(5f); silkSoulScroll = GUILayout.BeginScrollView(silkSoulScroll, Array.Empty()); IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (SilkSoulOverrides.HasEntries) { List pointEntries = SilkSoulOverrides.GetPointEntries(); GUILayout.Label("Quest Point Values (editable)", QuestGUISkin.SectionHeader, Array.Empty()); foreach (SilkSoulOverrides.PointEntry item in pointEntries) { GUILayout.BeginHorizontal(Array.Empty()); bool num = runtimeData != null && runtimeData.Contains(item.QuestName) && QuestDataAccess.IsCompleted(runtimeData[item.QuestName]); Color color2 = GUI.color; GUI.color = (num ? Color.green : Color.white); string displayName = QuestAcceptance.GetDisplayName(item.QuestName); GUILayout.Label(num ? "✓" : "✗", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(14f) }); GUILayout.Label(displayName, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(200f) }); GUI.color = color2; string questName = item.QuestName; if (!pointInputs.ContainsKey(questName)) { pointInputs[questName] = item.Value.ToString("0.##"); } pointInputs[questName] = GUILayout.TextField(pointInputs[questName], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); if (GUILayout.Button("Set", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(32f) }) && float.TryParse(pointInputs[questName], out var result2)) { SilkSoulOverrides.SetPointValue(item.EntryIndex, result2); } if (!(Math.Abs(item.Value - item.DefaultValue) < 0.01f)) { GUI.color = Color.yellow; GUILayout.Label($"({item.DefaultValue:0.##})", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); GUI.color = color2; } GUILayout.EndHorizontal(); } } else { int num2 = 0; int num3 = QuestRegistry.SilkSoulRequiredQuests.Length; string[] silkSoulRequiredQuests = QuestRegistry.SilkSoulRequiredQuests; foreach (string key in silkSoulRequiredQuests) { if (runtimeData != null && runtimeData.Contains(key) && QuestDataAccess.IsCompleted(runtimeData[key])) { num2++; } } int num4 = 0; int count = QuestRegistry.SilkSoulPointValues.Count; foreach (KeyValuePair silkSoulPointValue in QuestRegistry.SilkSoulPointValues) { if (runtimeData != null && runtimeData.Contains(silkSoulPointValue.Key) && QuestDataAccess.IsCompleted(runtimeData[silkSoulPointValue.Key])) { num4++; } } Color color3 = GUI.color; GUI.color = new Color(0.7f, 0.8f, 0.95f); GUILayout.Label($"Required: {num2}/{num3} | Optional: {num4}/{count}", Array.Empty()); GUI.color = color3; GUILayout.Space(3f); GUILayout.Label("Required Quests", QuestGUISkin.SectionHeader, Array.Empty()); silkSoulRequiredQuests = QuestRegistry.SilkSoulRequiredQuests; foreach (string text in silkSoulRequiredQuests) { GUILayout.BeginHorizontal(Array.Empty()); bool num5 = runtimeData != null && runtimeData.Contains(text) && QuestDataAccess.IsCompleted(runtimeData[text]); Color color4 = GUI.color; GUI.color = (num5 ? Color.green : Color.white); string displayName2 = QuestAcceptance.GetDisplayName(text); GUILayout.Label(num5 ? (" ✓ " + displayName2) : (" ✗ " + displayName2), Array.Empty()); GUI.color = color4; GUILayout.EndHorizontal(); } GUILayout.Space(5f); GUILayout.Label("Optional Quests (contribute points)", QuestGUISkin.SectionHeader, Array.Empty()); foreach (KeyValuePair silkSoulPointValue2 in QuestRegistry.SilkSoulPointValues) { GUILayout.BeginHorizontal(Array.Empty()); bool num6 = runtimeData != null && runtimeData.Contains(silkSoulPointValue2.Key) && QuestDataAccess.IsCompleted(runtimeData[silkSoulPointValue2.Key]); Color color5 = GUI.color; GUI.color = (num6 ? Color.green : Color.white); string displayName3 = QuestAcceptance.GetDisplayName(silkSoulPointValue2.Key); string text2 = ((silkSoulPointValue2.Value == 1f) ? "1 pt" : "0.5 pt"); GUILayout.Label(num6 ? (" ✓ " + displayName3) : (" ✗ " + displayName3), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(250f) }); GUILayout.Label("[" + text2 + "]", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }); GUI.color = color5; GUILayout.EndHorizontal(); } } GUILayout.EndScrollView(); } private static Color TagColor(string tag) { //IL_0008: 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) if (string.IsNullOrEmpty(tag)) { return Color.gray; } uint num = 2166136261u; foreach (char c in tag) { num ^= c; num *= 16777619; } return Color.HSVToRGB((float)(num % 360) / 360f, 0.55f, 0.85f); } private void DrawTagsTab() { //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) if (!QuestRequirements.IsLoaded) { GUILayout.Label("Custom requirements not loaded yet.", Array.Empty()); return; } GUILayout.Label("Tags drive bulk presets and conditions. Edits persist to QuestRequirements.user.json.", Array.Empty()); DrawTagsResetRow(); List list = (from t in QuestRequirements.GetAllUniqueTags() orderby t select t).ToList(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("New tag:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _tagInput = GUILayout.TextField(_tagInput ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(120f) }); GUILayout.Label(new GUIContent("(type, then click [+ new] on any quest below)", "The text in this box is what the [+ new] button on each quest adds. Existing tags get a one click button per quest."), Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Filter:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); _tagsFilter = GUILayout.TextField(_tagsFilter ?? "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.MinWidth(120f) }); if (GUILayout.Button("Clear", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) })) { _tagsFilter = ""; } GUILayout.FlexibleSpace(); GUILayout.Label($"{list.Count} unique tag(s)", Array.Empty()); GUILayout.EndHorizontal(); if (QuestRegistry.Categories != null && QuestRegistry.Categories.Length != 0) { GUILayout.BeginHorizontal(Array.Empty()); for (int i = 0; i < QuestRegistry.Categories.Length; i++) { GUIStyle val = ((i == _tagsCategoryTab) ? GUI.skin.GetStyle("ToolbarButtonActive") : GUI.skin.GetStyle("ToolbarButton")); if (GUILayout.Button(QuestRegistry.Categories[i], val, Array.Empty())) { _tagsCategoryTab = i; } } GUILayout.EndHorizontal(); } string activeCategory = ((QuestRegistry.Categories != null && _tagsCategoryTab < QuestRegistry.Categories.Length) ? QuestRegistry.Categories[_tagsCategoryTab] : null); _tagsScroll = GUILayout.BeginScrollView(_tagsScroll, Array.Empty()); string value; foreach (string item in (from q in QuestRegistry.DisplayNames.Keys where activeCategory == null || (QuestRegistry.QuestCategories.TryGetValue(q, out value) && value == activeCategory) where string.IsNullOrEmpty(_tagsFilter) || q.IndexOf(_tagsFilter, StringComparison.OrdinalIgnoreCase) >= 0 orderby q select q).ToList()) { DrawQuestTagRow(item, list); } GUILayout.EndScrollView(); } private void DrawQuestTagRow(string quest, List allTags) { //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: 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_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: 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_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.Label(QuestModPlugin.ShowQuestDisplayNames.Value ? QuestAcceptance.GetDisplayName(quest) : quest, QuestGUISkin.SectionHeader, Array.Empty()); QuestRequirements.Tags.TryGetValue(quest, out HashSet value); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Tags:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); if (value == null || value.Count == 0) { GUI.color = new Color(0.7f, 0.7f, 0.7f); GUILayout.Label("(none)", Array.Empty()); GUI.color = Color.white; } else { foreach (string item in value.OrderBy((string t) => t).ToList()) { Color backgroundColor = GUI.backgroundColor; GUI.backgroundColor = TagColor(item); if (GUILayout.Button(item + " x", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { QuestRequirements.RemoveTag(quest, item); } GUI.backgroundColor = backgroundColor; } } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Add:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); int num = 0; foreach (string allTag in allTags) { if (value == null || !value.Contains(allTag)) { if (num++ >= 8) { break; } Color backgroundColor2 = GUI.backgroundColor; GUI.backgroundColor = Color.Lerp(TagColor(allTag), Color.white, 0.45f); if (GUILayout.Button("+ " + allTag, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { QuestRequirements.AddTag(quest, allTag); } GUI.backgroundColor = backgroundColor2; } } GUI.enabled = !string.IsNullOrWhiteSpace(_tagInput) && (value == null || !value.Contains(_tagInput.Trim())); if (GUILayout.Button(string.IsNullOrWhiteSpace(_tagInput) ? "+ new" : ("+ new (" + _tagInput.Trim() + ")"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { QuestRequirements.AddTag(quest, _tagInput.Trim()); } GUI.enabled = true; GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.EndVertical(); GUILayout.Space(2f); } private void DrawTagsResetRow() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown //IL_00f2: 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) bool num = _tagsResetArmedAt > 0f && Time.realtimeSinceStartup - _tagsResetArmedAt < 4f; GUILayout.BeginHorizontal(Array.Empty()); if (!num) { if (GUILayout.Button(new GUIContent("Reset all tag overrides...", "Wipe the user overlay's tags section and reload from embedded baseline. Permanent."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { _tagsResetArmedAt = Time.realtimeSinceStartup; } } else { float num2 = 4f - (Time.realtimeSinceStartup - _tagsResetArmedAt); GUI.color = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button($"Confirm: wipe overrides [{num2:0.0}s]", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { QuestRequirements.ClearAllTagOverrides(); _tagsResetArmedAt = -1f; ReplaceOpenSnapshot(); QuestModToast.Show("Tag overrides cleared", new Color(0.9f, 0.7f, 0.4f), 2.5f); } GUI.color = Color.white; if (GUILayout.Button("Cancel", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(false) })) { _tagsResetArmedAt = -1f; } } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUILayout.Space(4f); } private void DrawCompletionTab() { //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Expected O, but got Unknown //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Expected O, but got Unknown //IL_04dc: Unknown result type (might be due to invalid IL or missing references) //IL_04e6: Unknown result type (might be due to invalid IL or missing references) //IL_04eb: Unknown result type (might be due to invalid IL or missing references) //IL_0495: Unknown result type (might be due to invalid IL or missing references) //IL_04a9: Unknown result type (might be due to invalid IL or missing references) //IL_05a0: Unknown result type (might be due to invalid IL or missing references) //IL_05bd: Expected O, but got Unknown //IL_05bd: Unknown result type (might be due to invalid IL or missing references) //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_05d6: Unknown result type (might be due to invalid IL or missing references) //IL_05cf: Unknown result type (might be due to invalid IL or missing references) //IL_05fa: Unknown result type (might be due to invalid IL or missing references) bool value = QuestModPlugin.EnableCompletionOverrides.Value; if (!QuestCompletionOverrides.IsInitialized) { GUILayout.Label("Not initialized - load a save first", Array.Empty()); return; } int num = categoryTab; GUILayout.BeginHorizontal(Array.Empty()); for (int i = 0; i < QuestRegistry.Categories.Length; i++) { GUIStyle val = ((i == categoryTab) ? GUI.skin.GetStyle("ToolbarButtonActive") : GUI.skin.GetStyle("ToolbarButton")); if (GUILayout.Button(QuestRegistry.Categories[i], val, Array.Empty())) { categoryTab = i; } } GUILayout.EndHorizontal(); if (categoryTab != num) { multiplierText = multiplier.ToString("0.0"); } GUILayout.Space(4f); GUI.enabled = value; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Multiplier:", "Scale all targets in this category (0 to 1)"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(68f) }); float num2 = multiplier; multiplier = GUILayout.HorizontalSlider(multiplier, 0f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(150f) }); multiplier = Mathf.Round(multiplier * 10f) / 10f; if (!Mathf.Approximately(multiplier, num2)) { multiplierText = multiplier.ToString("0.00"); } multiplierText = GUILayout.TextField(multiplierText, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(40f) }); if (GUILayout.Button("Apply", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) }) && float.TryParse(multiplierText, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { result = (multiplier = Mathf.Clamp(result, 0f, 1f)); multiplierText = result.ToString("0.00"); ApplyMultiplier(result); } if (GUILayout.Button("1x", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(28f) })) { multiplier = 1f; multiplierText = "1.0"; ApplyMultiplier(1f); } if (GUILayout.Button(new GUIContent("R", "Reset this category to original values"), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { ResetCategory(); } GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Presets:", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(50f) }); if (GUILayout.Button("Set to 1", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) })) { QuestCompletionOverrides.ApplyPresetAll("set1"); countInputs.Clear(); RefreshOverrides(); } if (GUILayout.Button("QoL Half", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) })) { QuestCompletionOverrides.ApplyPresetAll("qol"); countInputs.Clear(); RefreshOverrides(); } if (GUILayout.Button("Farmable", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(65f) })) { QuestCompletionOverrides.ApplyPresetAll("farmable"); countInputs.Clear(); RefreshOverrides(); } if (GUILayout.Button("Default", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(55f) })) { QuestCompletionOverrides.ApplyPresetAll("default"); countInputs.Clear(); RefreshOverrides(); } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); GUI.enabled = true; if (!value) { GUILayout.Label("Overrides disabled in config. Counts are read-only", Array.Empty()); } else if (QuestModPlugin.DevRemoveLimits.Value) { GUILayout.Label("⚠ DevRemoveLimits, no count limits (may break quest state)", Array.Empty()); } GUILayout.Space(4f); string key = QuestRegistry.Categories[categoryTab]; if (!cachedByCategory.ContainsKey(key) || cachedByCategory[key].Count == 0) { GUILayout.Label("No quests found in this category.", Array.Empty()); return; } int num3 = 0; int num4 = 0; foreach (QuestOverrideInfo item in cachedByCategory[key]) { foreach (QuestTargetInfo target in item.Targets) { num4++; if (target.CurrentCount != target.OriginalCount) { num3++; } } } if (num3 > 0) { Color color = GUI.color; GUI.color = new Color(1f, 0.85f, 0.4f); GUILayout.Label($"{num3} of {num4} targets modified", Array.Empty()); GUI.color = color; } overrideScroll = GUILayout.BeginScrollView(overrideScroll, Array.Empty()); bool flag = false; foreach (QuestOverrideInfo item2 in cachedByCategory[key]) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); int num5 = 0; foreach (QuestTargetInfo target2 in item2.Targets) { num5 += target2.CurrentCount; } string text = $"[{item2.CompletedCount}/{num5}]"; GUILayout.Label(new GUIContent(item2.QuestName, item2.QuestName), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(240f) }); Color color2 = GUI.color; GUI.color = ((item2.CompletedCount >= num5) ? Color.green : Color.white); GUILayout.Label(text, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUI.color = color2; GUILayout.FlexibleSpace(); if (value && GUILayout.Button("Reset", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(48f) })) { QuestCompletionOverrides.ResetToOriginal(item2.QuestName); foreach (QuestTargetInfo target3 in item2.Targets) { string key2 = $"{item2.QuestName}_{target3.TargetIndex}"; countInputs.Remove(key2); } flag = true; } GUILayout.EndHorizontal(); foreach (QuestTargetInfo target4 in item2.Targets) { string key3 = $"{item2.QuestName}_{target4.TargetIndex}"; if (!countInputs.ContainsKey(key3)) { countInputs[key3] = target4.CurrentCount.ToString(); } GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Space(15f); string text2 = ((target4.CurrentCount != target4.OriginalCount) ? $"{target4.DisplayName} ({target4.OriginalCount}→{target4.CurrentCount})" : $"{target4.DisplayName} ({target4.OriginalCount})"); int maxCap = QuestCompletionOverrides.GetMaxCap(item2.QuestName, target4.TargetIndex); if (maxCap > 0) { text2 += $" [max {maxCap}]"; } GUILayout.Label(text2, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(210f) }); if (!value) { GUILayout.EndHorizontal(); continue; } if (item2.Targets.Count > 1) { bool flag2 = target4.CurrentCount > 0; bool flag3 = GUILayout.Toggle(flag2, "", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(18f) }); if (flag3 != flag2) { int newCount = (flag3 ? target4.OriginalCount : 0); QuestCompletionOverrides.SetTargetCount(item2.QuestName, target4.TargetIndex, newCount); countInputs[key3] = newCount.ToString(); flag = true; } } if (GUILayout.Button("−", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { int newCount2 = QuestCompletionOverrides.ClampCount(item2.QuestName, target4.TargetIndex, target4.CurrentCount - 1); QuestCompletionOverrides.SetTargetCount(item2.QuestName, target4.TargetIndex, newCount2); countInputs[key3] = newCount2.ToString(); flag = true; } countInputs[key3] = GUILayout.TextField(countInputs[key3], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(38f) }); if (GUILayout.Button("+", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(22f) })) { int newCount3 = QuestCompletionOverrides.ClampCount(item2.QuestName, target4.TargetIndex, target4.CurrentCount + 1); QuestCompletionOverrides.SetTargetCount(item2.QuestName, target4.TargetIndex, newCount3); countInputs[key3] = newCount3.ToString(); flag = true; } if (GUILayout.Button("Set", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(32f) }) && int.TryParse(countInputs[key3], out var result2)) { QuestCompletionOverrides.SetTargetCount(item2.QuestName, target4.TargetIndex, result2); flag = true; } GUILayout.EndHorizontal(); } GUILayout.EndVertical(); GUILayout.Space(2f); } GUILayout.EndScrollView(); if (flag) { RefreshOverrides(); } } private void ApplyMultiplier(float mult) { string key = QuestRegistry.Categories[categoryTab]; if (!cachedByCategory.ContainsKey(key)) { return; } foreach (QuestOverrideInfo item in cachedByCategory[key]) { foreach (QuestTargetInfo target in item.Targets) { int num = Mathf.Max(1, Mathf.RoundToInt((float)target.OriginalCount * mult)); int newCount = ((num <= 1) ? 1 : ((num % 2 == 0) ? num : (num + 1))); string key2 = $"{item.QuestName}_{target.TargetIndex}"; countInputs[key2] = newCount.ToString(); QuestCompletionOverrides.SetTargetCount(item.QuestName, target.TargetIndex, newCount); } } RefreshOverrides(); } private void ResetCategory() { string key = QuestRegistry.Categories[categoryTab]; if (!cachedByCategory.ContainsKey(key)) { return; } foreach (QuestOverrideInfo item in cachedByCategory[key]) { QuestCompletionOverrides.ResetToOriginal(item.QuestName); foreach (QuestTargetInfo target in item.Targets) { string key2 = $"{item.QuestName}_{target.TargetIndex}"; countInputs.Remove(key2); } } multiplier = 1f; multiplierText = "1.0"; RefreshOverrides(); } private void DrawSafetyGate() { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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_0130: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) if (QuestModPlugin.AreDestructiveFeaturesAllowed) { if (QuestModPlugin.IsSafetyOverridden && !QuestModPlugin.IsQuestModSave) { GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUI.color = new Color(1f, 0.85f, 0.4f); GUILayout.Label("Safety override active on this legacy save.", Array.Empty()); GUI.color = Color.white; GUILayout.EndVertical(); GUILayout.Space(6f); } return; } GUILayout.BeginVertical(GUI.skin.box, Array.Empty()); GUI.color = new Color(1f, 0.6f, 0.6f); GUILayout.Label("Save Safety -- destructive features locked", QuestGUISkin.SectionHeader, Array.Empty()); GUI.color = Color.white; GUILayout.Label("This save was created without QuestMod, or has no QuestMod state set. Destructive features (NPC spawn flags, granular bypasses, force-accept/complete) are disabled to protect existing progress.\n\nStart a New Game with QuestMod active to enable them automatically -- or override below if you accept the risk.", Array.Empty()); if (!(_safetyOverrideArmedAt > 0f) || !(Time.realtimeSinceStartup - _safetyOverrideArmedAt < 4f)) { if (GUILayout.Button("Override Safety on this save...", Array.Empty())) { _safetyOverrideArmedAt = Time.realtimeSinceStartup; } } else { float num = 4f - (Time.realtimeSinceStartup - _safetyOverrideArmedAt); GUI.color = new Color(1f, 0.4f, 0.4f); if (GUILayout.Button($"Confirm: enable destructive features (PERMANENT) [{num:0.0}s]", Array.Empty())) { QuestModPlugin.SetSafetyOverride(value: true); _safetyOverrideArmedAt = -1f; QuestModToast.Show("Safety override active for this save. No off button.", new Color(1f, 0.45f, 0.4f)); } GUI.color = Color.white; if (GUILayout.Button("Cancel", Array.Empty())) { _safetyOverrideArmedAt = -1f; } } GUILayout.EndVertical(); GUILayout.Space(6f); } private void DrawResetAllPerSaveTogglesButton() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData == null) { return; } GUILayout.Label("Per-save toggles", QuestGUISkin.SectionHeader, Array.Empty()); if (_resetAllArmedAt < 0f || Time.realtimeSinceStartup - _resetAllArmedAt > 4f) { if (GUILayout.Button(new GUIContent("Reset all per-save toggles", "Clears every BypassX flag in QuestMod's save data. Does NOT undo the PlayerData side effects: hasDoubleJump, hasNeedolin, visitedFleatopia, bonebottomQuestBoardFixed, promisedFirstWish stay TRUE on this save. Does NOT touch Override Safety (one-way ratchet)."), Array.Empty())) { _resetAllArmedAt = Time.realtimeSinceStartup; } return; } float num = 4f - (Time.realtimeSinceStartup - _resetAllArmedAt); GUI.color = new Color(1f, 0.6f, 0.4f); if (GUILayout.Button($"Confirm: clear all per-save toggles [{num:0.0}s]", Array.Empty())) { int num2 = 0; if (questModSaveData.Prereqs != null) { if (questModSaveData.Prereqs.BypassFleatopia) { questModSaveData.Prereqs.BypassFleatopia = false; num2++; } if (questModSaveData.Prereqs.BypassMandatoryWishes) { questModSaveData.Prereqs.BypassMandatoryWishes = false; num2++; } if (questModSaveData.Prereqs.BypassFaydownCloak) { questModSaveData.Prereqs.BypassFaydownCloak = false; num2++; } if (questModSaveData.Prereqs.BypassNeedolin) { questModSaveData.Prereqs.BypassNeedolin = false; num2++; } if (questModSaveData.Prereqs.BypassBonebottomQuestBoard) { questModSaveData.Prereqs.BypassBonebottomQuestBoard = false; num2++; } if (questModSaveData.Prereqs.BypassAllWishwalls) { questModSaveData.Prereqs.BypassAllWishwalls = false; num2++; } } if (questModSaveData.AllQuestsAccepted) { questModSaveData.AllQuestsAccepted = false; num2++; } if (questModSaveData.AutoAcceptAllAvailable) { questModSaveData.AutoAcceptAllAvailable = false; num2++; } int num3 = questModSaveData.QuestPolicies?.Count ?? 0; questModSaveData.QuestPolicies?.Clear(); num2 += num3; QuestModPlugin.SetWishesMode(AllWishesMode.Disabled); questListDirty = true; _resetAllArmedAt = -1f; QuestModToast.Show($"Reset {num2} per-save toggle(s)", new Color(0.9f, 0.7f, 0.4f), 3f); QuestModPlugin.Log.LogInfo((object)$"Reset all per-save toggles: cleared {num2} field(s)"); } GUI.color = Color.white; if (GUILayout.Button("Cancel", Array.Empty())) { _resetAllArmedAt = -1f; } } private void SnapshotBeforeMassOp() { undoSnapshot = QuestAcceptance.GetQuestList(); try { undoSaveDataSnapshot = QuestModPlugin.ExportSaveDataToJson(); } catch { undoSaveDataSnapshot = null; } } private int MassOpCount(Action op, bool accepted) { int num = 0; foreach (QuestInfo quest in QuestAcceptance.GetQuestList()) { if (accepted ? quest.IsAccepted : quest.IsCompleted) { num++; } } op(); int num2 = 0; foreach (QuestInfo quest2 in QuestAcceptance.GetQuestList()) { if (accepted ? quest2.IsAccepted : quest2.IsCompleted) { num2++; } } return Math.Max(0, num2 - num); } private void UndoMassOp() { if (undoSnapshot == null) { return; } if (!string.IsNullOrEmpty(undoSaveDataSnapshot)) { try { QuestModPlugin.ImportSaveDataFromJson(undoSaveDataSnapshot); } catch (Exception ex) { ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogWarning((object)("UndoMassOp save data restore failed: " + ex.Message)); } } } foreach (QuestInfo item in undoSnapshot) { if (item.IsCompleted) { QuestAcceptance.CompleteQuest(item.Name); } else { QuestAcceptance.UncompleteQuest(item.Name); } if (item.IsAccepted) { QuestAcceptance.AcceptQuest(item.Name); } else { QuestAcceptance.UnacceptQuest(item.Name); } } undoSnapshot = null; undoSaveDataSnapshot = null; questListDirty = true; QuestModPlugin.LogDebugInfo("Undid last mass operation"); } private void DrawToolsTab() { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Expected O, but got Unknown //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Expected O, but got Unknown //IL_01af: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0325: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Expected O, but got Unknown //IL_0221: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_0392: Unknown result type (might be due to invalid IL or missing references) //IL_03ab: Expected O, but got Unknown //IL_03c9: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Expected O, but got Unknown //IL_0480: Unknown result type (might be due to invalid IL or missing references) //IL_048f: Expected O, but got Unknown //IL_0421: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_051d: Unknown result type (might be due to invalid IL or missing references) //IL_052c: Expected O, but got Unknown DrawSaveDiscardBanner(); _toolsScroll = GUILayout.BeginScrollView(_toolsScroll, Array.Empty()); DrawSafetyGate(); GUILayout.Label("Mass Operations", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Accept Available", Array.Empty())) { SnapshotBeforeMassOp(); int num = MassOpCount(delegate { QuestAcceptance.AcceptAllQuests(); }, accepted: true); questListDirty = true; QuestModToast.Show($"Accepted {num} quest" + ((num == 1) ? "" : "s")); } if (GUILayout.Button("Complete Available", Array.Empty())) { SnapshotBeforeMassOp(); int num2 = MassOpCount(delegate { QuestAcceptance.CompleteAllQuests(); }, accepted: false); questListDirty = true; QuestModToast.Show($"Completed {num2} quest" + ((num2 == 1) ? "" : "s")); } GUILayout.EndHorizontal(); if (QuestModPlugin.DevForceOperations.Value) { GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button("Accept ALL (force)", Array.Empty())) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModToast.Show("Force op blocked: legacy save (toggle Override Safety first)", new Color(1f, 0.6f, 0.4f), 4f); } else { SnapshotBeforeMassOp(); QuestAcceptance.ForceAcceptAllQuests(); questListDirty = true; QuestModToast.Show("Force-accepted all quests", new Color(0.9f, 0.7f, 0.4f), 3f); } } if (GUILayout.Button("Complete ALL (force)", Array.Empty())) { if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModToast.Show("Force op blocked: legacy save (toggle Override Safety first)", new Color(1f, 0.6f, 0.4f), 4f); } else { SnapshotBeforeMassOp(); QuestAcceptance.ForceCompleteAllQuests(); questListDirty = true; QuestModToast.Show("Force-completed all quests", new Color(0.9f, 0.7f, 0.4f), 3f); } } GUILayout.EndHorizontal(); } GUI.enabled = undoSnapshot != null; if (GUILayout.Button("Undo Last Mass Operation", Array.Empty())) { UndoMassOp(); } GUI.enabled = true; GUILayout.Space(10f); DrawResetAllPerSaveTogglesButton(); GUILayout.Space(10f); DrawExportImportSection(); GUILayout.Space(10f); GUILayout.Label("Mode (this save)", QuestGUISkin.SectionHeader, Array.Empty()); bool allQuestsAccepted = QuestModPlugin.AllQuestsAccepted; bool flag = GUILayout.Toggle(allQuestsAccepted, new GUIContent("All Quests Accepted", "Auto-inject and accept every quest each scene load. Forces All Wishes Mode → Adjusted."), Array.Empty()); if (flag != allQuestsAccepted) { QuestModPlugin.SetAllQuestsAccepted(flag); } bool autoAcceptAllAvailable = QuestModPlugin.AutoAcceptAllAvailable; bool flag2 = !QuestModPlugin.AreDestructiveFeaturesAllowed; GUI.enabled = !flag2; bool flag3 = GUILayout.Toggle(autoAcceptAllAvailable, new GUIContent("Auto-Accept Available", "Each scene load, accept any wish where IsActuallyAvailable() passes.\nRespects chain prereqs, exclusion conflicts, and availableConditions.\nOnly acts in Adjusted/Pure. Toggling on auto-flips Disabled → Adjusted.\nUnlike All Quests Accepted, story-locked wishes only accept once their gate unlocks."), Array.Empty()); GUI.enabled = true; if (flag3 != autoAcceptAllAvailable && !flag2) { QuestModPlugin.SetAutoAcceptAllAvailable(flag3); } GUI.enabled = !allQuestsAccepted; GUILayout.Label(new GUIContent(" All Wishes Mode", "Disabled = vanilla.\nPure = every wish available, no prereq checks (soft-locks possible).\nAdjusted = bypass act/NPC prereqs but respect chain gating (recommended)."), Array.Empty()); AllWishesMode wishesMode = QuestModPlugin.WishesMode; bool num3 = QuestModPlugin.ShowPureWishesMode.Value || wishesMode == AllWishesMode.Pure; GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Toggle(wishesMode == AllWishesMode.Disabled, "Disabled", GUIStyle.op_Implicit("Button"), Array.Empty()) && wishesMode != 0) { QuestModPlugin.SetWishesMode(AllWishesMode.Disabled); } if (GUILayout.Toggle(wishesMode == AllWishesMode.Adjusted, new GUIContent("Adjusted", "Bypass NPC/act prereqs but keep chain gating + edge-case fixes. Safe for normal play (recommended)."), GUIStyle.op_Implicit("Button"), Array.Empty()) && wishesMode != AllWishesMode.Adjusted) { QuestModPlugin.SetWishesMode(AllWishesMode.Adjusted); } if (num3 && GUILayout.Toggle(wishesMode == AllWishesMode.Pure, new GUIContent("Pure", "Every quest available -- no chain gating, no edge-case handling. Soft-locks possible. Enable Advanced > ShowPureWishesMode to see this option."), GUIStyle.op_Implicit("Button"), Array.Empty()) && wishesMode != AllWishesMode.Pure) { QuestModPlugin.SetWishesMode(AllWishesMode.Pure); } GUILayout.EndHorizontal(); if (QuestModPlugin.LastWishesModeChangeRealtime > 0f && Time.realtimeSinceStartup - QuestModPlugin.LastWishesModeChangeRealtime < 12f) { GUI.color = new Color(1f, 0.85f, 0.4f); GUILayout.Label("Mode changed mid-run. Already-applied state stays; future scene loads use the new mode.", Array.Empty()); GUI.color = Color.white; } GUI.enabled = true; GUILayout.Space(10f); GUILayout.Label("Quick Config", QuestGUISkin.SectionHeader, Array.Empty()); bool value = QuestModPlugin.OnlyDiscoveredQuests.Value; bool flag4 = GUILayout.Toggle(value, new GUIContent("Only Discovered Quests", "Only show quests the player has encountered"), Array.Empty()); if (flag4 != value) { QuestModPlugin.OnlyDiscoveredQuests.Value = flag4; } bool value2 = QuestModPlugin.QuestItemInvincible.Value; bool flag5 = GUILayout.Toggle(value2, "Quest Item Invincible", Array.Empty()); if (flag5 != value2) { QuestModPlugin.QuestItemInvincible.Value = flag5; } bool value3 = QuestModPlugin.ShowQuestDisplayNames.Value; bool flag6 = GUILayout.Toggle(value3, "Show Display Names", Array.Empty()); if (flag6 != value3) { QuestModPlugin.ShowQuestDisplayNames.Value = flag6; } bool value4 = QuestModPlugin.BypassWishboardLock.Value; bool flag7 = GUILayout.Toggle(value4, new GUIContent("Bypass Wishboard Lock", "Use the Bonetown wishboard without defeating the Bell Beast. Runtime-only: does NOT touch save data. Turn it off and the vanilla lock returns. Re-enter the room (or reload the scene) for the change to apply."), Array.Empty()); if (flag7 != value4) { QuestModPlugin.BypassWishboardLock.Value = flag7; } if (flag7) { GUILayout.Label("Note: bypass is purely runtime. Save data is untouched, so this will NOT persist across reloads on its own. The toggle stays in mod config and re-applies each scene load.", Array.Empty()); } GUILayout.Space(8f); if (PlayerData.instance != null) { bool blackThreadWorld = PlayerData.instance.blackThreadWorld; bool flag8 = GUILayout.Toggle(blackThreadWorld, "Act 3 (Black Thread World)", Array.Empty()); if (flag8 != blackThreadWorld) { PlayerData.instance.blackThreadWorld = flag8; } } DrawGranularPrereqsSection(); DrawCustomRequirementsSection(); GUILayout.EndScrollView(); } private void DrawGranularPrereqsSection() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Expected O, but got Unknown GranularPrereqs prereqs = QuestModPlugin.Prereqs; if (prereqs != null) { GUILayout.Space(10f); GUILayout.Label("Granular Prerequisite Bypasses", QuestGUISkin.SectionHeader, Array.Empty()); GUILayout.Label("Each toggle writes a PlayerData flag once (false to true). Toggling OFF clears the QuestMod side of the toggle but does NOT undo the PlayerData write. Abilities and visited flags stay granted for that save.", GUI.skin.label, Array.Empty()); bool flag = GUILayout.Toggle(prereqs.BypassFleatopia, new GUIContent("Bypass Fleatopia (one-way)", "ONE-WAY: marks Fleatopia visited + troupe leader spoken. Cannot be undone without editing the save."), Array.Empty()); if (flag != prereqs.BypassFleatopia) { prereqs.BypassFleatopia = flag; } bool flag2 = GUILayout.Toggle(prereqs.BypassMandatoryWishes, new GUIContent("Bypass Mandatory Wishes (one-way)", "ONE-WAY: sets promisedFirstWish so the forced early wishboard wish stops gating. Cannot be undone."), Array.Empty()); if (flag2 != prereqs.BypassMandatoryWishes) { prereqs.BypassMandatoryWishes = flag2; } bool flag3 = GUILayout.Toggle(prereqs.BypassFaydownCloak, new GUIContent("Grant Faydown Cloak (one-way)", "ONE-WAY: grants the Faydown Cloak double-jump ability outright. NOT just a quest-gate bypass. The player keeps the ability for the rest of the save."), Array.Empty()); if (flag3 != prereqs.BypassFaydownCloak) { prereqs.BypassFaydownCloak = flag3; } bool flag4 = GUILayout.Toggle(prereqs.BypassNeedolin, new GUIContent("Grant Needolin (one-way)", "ONE-WAY: grants the Needolin ability outright. Cannot be undone."), Array.Empty()); if (flag4 != prereqs.BypassNeedolin) { prereqs.BypassNeedolin = flag4; } bool flag5 = GUILayout.Toggle(prereqs.BypassBonebottomQuestBoard, new GUIContent("Bypass Bone Bottom Quest Board (one-way)", "ONE-WAY: sets bonebottomQuestBoardFixed so the first wishboard is usable without defeating Bell Beast. Cannot be undone."), Array.Empty()); if (flag5 != prereqs.BypassBonebottomQuestBoard) { prereqs.BypassBonebottomQuestBoard = flag5; } bool flag6 = GUILayout.Toggle(prereqs.BypassAllWishwalls, new GUIContent("Quest boards always available", "Force-activates all 3 wishwall kiosks (Bone Bottom, Bellhart, Songclave) at runtime without touching PlayerData. NPC build animations (e.g. Flick) may still play visually, but the kiosks are interactable. Avoids the cascade of flipping defeatedBellBeast / visitedBellhartSaved / metCaretaker, which would falsely advance story milestones."), Array.Empty()); if (flag6 != prereqs.BypassAllWishwalls) { prereqs.BypassAllWishwalls = flag6; } } } private void DrawCustomRequirementsSection() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Expected O, but got Unknown //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(10f); GUILayout.Label("Custom Requirements", QuestGUISkin.SectionHeader, Array.Empty()); bool isCustomRequirementsEnabled = QuestModPlugin.IsCustomRequirementsEnabled; bool flag = GUILayout.Toggle(isCustomRequirementsEnabled, new GUIContent("Enable custom requirements", "Apply preset + per-quest overrides from QuestRequirements.user.json on save load. Also evaluates extraConditions before quest turn-in."), Array.Empty()); if (flag != isCustomRequirementsEnabled) { QuestModPlugin.IsCustomRequirementsEnabled = flag; } if (!QuestRequirements.IsLoaded) { GUILayout.Label("(rules not loaded)", Array.Empty()); return; } string[] presetNames = QuestRequirements.GetPresetNames(); if (presetSelected < 0) { for (int i = 0; i < presetNames.Length; i++) { if (string.Equals(presetNames[i], QuestRequirements.ActivePresetName, StringComparison.OrdinalIgnoreCase)) { presetSelected = i; break; } } if (presetSelected < 0) { presetSelected = 0; } } GUI.enabled = flag; GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Active preset:", "Applied on save load. 'vanilla' = no preset (slider/per-quest values only)."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(96f) }); int num = GUILayout.SelectionGrid(presetSelected, presetNames, Math.Min(presetNames.Length, 4), Array.Empty()); if (num != presetSelected) { presetSelected = num; string text = presetNames[num]; QuestModPlugin.ActivePreset.Value = text; QuestRequirements.SetActivePreset(text); } GUILayout.EndHorizontal(); if (presetSelected >= 0 && presetSelected < presetNames.Length && QuestRequirements.Presets.TryGetValue(presetNames[presetSelected], out QuestRequirements.Preset value) && !string.IsNullOrEmpty(value.Description)) { GUILayout.Label(" " + value.Description, Array.Empty()); } GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(new GUIContent("Apply now", "Re-applies the active preset to all quests. Slider-touched targets are skipped."), Array.Empty())) { QuestRequirements.ApplyActivePreset(); RefreshOverrides(); } if (GUILayout.Button(new GUIContent("Reload rules", "Re-reads QuestRequirements.user.json from BepInEx/config/QuestMod/."), Array.Empty())) { ReloadRequirements(); ReplaceOpenSnapshot(); } GUILayout.EndHorizontal(); if (!string.IsNullOrEmpty(QuestAcceptance.LastCompletionRefusal)) { Color color = GUI.color; GUI.color = new Color(1f, 0.6f, 0.4f); GUILayout.Label("Last refusal: " + QuestAcceptance.LastCompletionRefusal, Array.Empty()); GUI.color = color; } GUI.enabled = true; } private static void ReloadRequirements() { QuestRequirements.Reload(); QuestRequirements.SetActivePreset(QuestModPlugin.ActivePreset.Value); QuestModPlugin.Log.LogInfo((object)"QuestRequirements reloaded"); } private void DrawExportImportSection() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label(new GUIContent("Save state I/O:", "Copy / paste your QuestMod save state via clipboard. Useful for sharing challenge run setups or backing up before destructive ops. Vanilla save state is not affected."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) }); if (GUILayout.Button(new GUIContent("Copy", "Serialize this save's QuestMod state to your clipboard."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) })) { string text = QuestModPlugin.ExportSaveDataToJson(); if (text == null) { _lastIoMessage = "No save loaded"; QuestModToast.Show("Copy failed: no save loaded", new Color(1f, 0.6f, 0.4f), 3f); } else { GUIUtility.systemCopyBuffer = text; _lastIoMessage = $"Copied {text.Length} chars"; QuestModToast.Show($"Save state copied ({text.Length} chars)", new Color(0.5f, 0.9f, 0.5f), 2.5f); } } if (!(_importArmedAt > 0f) || !(Time.realtimeSinceStartup - _importArmedAt < 4f)) { if (GUILayout.Button(new GUIContent("Paste...", "Replace this save's QuestMod state with the JSON in your clipboard. Two click confirm."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f) })) { _importArmedAt = Time.realtimeSinceStartup; } } else { float num = 4f - (Time.realtimeSinceStartup - _importArmedAt); GUI.color = new Color(1f, 0.6f, 0.6f); if (GUILayout.Button($"Confirm REPLACE [{num:0.0}s]", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(160f) })) { try { string text2 = GUIUtility.systemCopyBuffer ?? ""; QuestModPlugin.ImportSaveDataFromJson(text2); _lastIoMessage = $"Imported {text2.Length} chars"; questListDirty = true; } catch (Exception ex) { _lastIoMessage = "Import failed: " + ex.Message; } _importArmedAt = -1f; } GUI.color = Color.white; if (GUILayout.Button("X", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(24f) })) { _importArmedAt = -1f; } } if (!string.IsNullOrEmpty(_lastIoMessage)) { GUI.color = new Color(0.7f, 0.7f, 0.7f); GUILayout.Label(_lastIoMessage, Array.Empty()); GUI.color = Color.white; } GUILayout.FlexibleSpace(); GUILayout.EndHorizontal(); } private void DrawSaveDiscardBanner() { //IL_0065: 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_0022: 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) //IL_004a: Expected O, but got Unknown //IL_004a: 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_00be: Expected O, but got Unknown //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) bool num = HasUnsavedChanges(); GUILayout.BeginHorizontal(Array.Empty()); if (num) { GUI.color = new Color(1f, 0.85f, 0.4f); GUILayout.Label(new GUIContent("Unsaved changes", "You changed save data or tags since opening this panel. Click Save Changes to commit, or close the panel without saving to revert."), QuestGUISkin.SectionHeader, Array.Empty()); GUI.color = Color.white; } else { GUI.color = new Color(0.7f, 0.9f, 0.7f); GUILayout.Label("All changes saved", QuestGUISkin.SectionHeader, Array.Empty()); GUI.color = Color.white; } GUILayout.FlexibleSpace(); GUI.enabled = num; if (GUILayout.Button(new GUIContent("Save Changes", "Commit the current state as the saved baseline. Closing the panel after this point will not revert."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { MarkSaveExplicit(); } if (GUILayout.Button(new GUIContent("Discard", "Roll back save data and tags to the state at panel open."), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(80f) })) { if (_uiOpenSnapshotSaveData != null) { QuestModPlugin.ImportSaveDataFromJson(_uiOpenSnapshotSaveData); } if (_uiOpenSnapshotTags != null) { QuestRequirements.ImportTagsJson(_uiOpenSnapshotTags); } questListDirty = true; QuestModToast.Show("Discarded changes", new Color(0.9f, 0.7f, 0.4f), 2.5f); } GUI.enabled = true; GUILayout.EndHorizontal(); GUILayout.Space(4f); } } public static class QuestGUISkin { private static GUISkin skin; private static GUIStyle sectionHeader; private static GUIStyle tooltipStyle; private static Texture2D darkTex; private static Texture2D midTex; private static Texture2D lightTex; private static Texture2D accentTex; private static Texture2D hoverTex; private static Texture2D activeTex; private static Texture2D fieldTex; private static Texture2D greenTex; public static GUIStyle SectionHeader => sectionHeader; public static GUIStyle TooltipStyle => tooltipStyle; private static Texture2D MakeTex(Color col) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected O, but got Unknown //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0012: 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_001a: 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_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(2, 2); val.SetPixels((Color[])(object)new Color[4] { col, col, col, col }); val.Apply(); ((Object)val).hideFlags = (HideFlags)52; return val; } public static GUISkin Get() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_011c: 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_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: 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_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: 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_01e6: Expected O, but got Unknown //IL_01e6: 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_01f6: Expected O, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Expected O, but got Unknown //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown //IL_0260: Expected O, but got Unknown //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: 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: Unknown result type (might be due to invalid IL or missing references) //IL_02e4: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: 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_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_0312: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Expected O, but got Unknown //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Expected O, but got Unknown //IL_0335: Expected O, but got Unknown //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Expected O, but got Unknown //IL_0396: Unknown result type (might be due to invalid IL or missing references) //IL_039b: Unknown result type (might be due to invalid IL or missing references) //IL_03a5: Expected O, but got Unknown //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Expected O, but got Unknown //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03c0: Expected O, but got Unknown //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Unknown result type (might be due to invalid IL or missing references) //IL_03e9: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Unknown result type (might be due to invalid IL or missing references) //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_0412: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_0422: Unknown result type (might be due to invalid IL or missing references) //IL_0437: Unknown result type (might be due to invalid IL or missing references) //IL_0441: Unknown result type (might be due to invalid IL or missing references) //IL_0449: 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_0459: Expected O, but got Unknown //IL_045e: Expected O, but got Unknown //IL_046d: Unknown result type (might be due to invalid IL or missing references) //IL_0472: Unknown result type (might be due to invalid IL or missing references) //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_0488: Unknown result type (might be due to invalid IL or missing references) //IL_0492: Unknown result type (might be due to invalid IL or missing references) //IL_04a2: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Unknown result type (might be due to invalid IL or missing references) //IL_04b2: Unknown result type (might be due to invalid IL or missing references) //IL_04c2: Unknown result type (might be due to invalid IL or missing references) //IL_04c8: Unknown result type (might be due to invalid IL or missing references) //IL_04d2: Unknown result type (might be due to invalid IL or missing references) //IL_04da: Unknown result type (might be due to invalid IL or missing references) //IL_04df: Unknown result type (might be due to invalid IL or missing references) //IL_04e9: Expected O, but got Unknown //IL_04e9: Unknown result type (might be due to invalid IL or missing references) //IL_04ee: Unknown result type (might be due to invalid IL or missing references) //IL_04f8: Expected O, but got Unknown //IL_04fd: Expected O, but got Unknown //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_050c: Unknown result type (might be due to invalid IL or missing references) //IL_051c: 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_053b: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Unknown result type (might be due to invalid IL or missing references) //IL_0551: Unknown result type (might be due to invalid IL or missing references) //IL_055b: Unknown result type (might be due to invalid IL or missing references) //IL_0563: Unknown result type (might be due to invalid IL or missing references) //IL_056a: Unknown result type (might be due to invalid IL or missing references) //IL_0571: Unknown result type (might be due to invalid IL or missing references) //IL_0576: Unknown result type (might be due to invalid IL or missing references) //IL_0580: Expected O, but got Unknown //IL_0581: 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_05a0: Expected O, but got Unknown //IL_05a3: Unknown result type (might be due to invalid IL or missing references) //IL_05a8: Unknown result type (might be due to invalid IL or missing references) //IL_05b3: Unknown result type (might be due to invalid IL or missing references) //IL_05c3: Unknown result type (might be due to invalid IL or missing references) //IL_05c9: Unknown result type (might be due to invalid IL or missing references) //IL_05d3: Unknown result type (might be due to invalid IL or missing references) //IL_05db: Expected O, but got Unknown //IL_05ef: 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_0604: 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_0614: Unknown result type (might be due to invalid IL or missing references) //IL_061e: Expected O, but got Unknown //IL_0623: Expected O, but got Unknown //IL_0632: Unknown result type (might be due to invalid IL or missing references) //IL_0637: Unknown result type (might be due to invalid IL or missing references) //IL_0647: Unknown result type (might be due to invalid IL or missing references) //IL_0657: Unknown result type (might be due to invalid IL or missing references) //IL_0662: Unknown result type (might be due to invalid IL or missing references) //IL_0672: Expected O, but got Unknown //IL_0681: Unknown result type (might be due to invalid IL or missing references) //IL_0686: Unknown result type (might be due to invalid IL or missing references) //IL_0696: Unknown result type (might be due to invalid IL or missing references) //IL_06a6: Expected O, but got Unknown //IL_06b5: Unknown result type (might be due to invalid IL or missing references) //IL_06ba: Unknown result type (might be due to invalid IL or missing references) //IL_06cf: Expected O, but got Unknown //IL_06d9: Unknown result type (might be due to invalid IL or missing references) //IL_06de: Unknown result type (might be due to invalid IL or missing references) //IL_06e6: Unknown result type (might be due to invalid IL or missing references) //IL_06ed: Unknown result type (might be due to invalid IL or missing references) //IL_06f4: Unknown result type (might be due to invalid IL or missing references) //IL_0709: Unknown result type (might be due to invalid IL or missing references) //IL_0713: Unknown result type (might be due to invalid IL or missing references) //IL_0718: Unknown result type (might be due to invalid IL or missing references) //IL_0722: Expected O, but got Unknown //IL_0722: Unknown result type (might be due to invalid IL or missing references) //IL_0727: Unknown result type (might be due to invalid IL or missing references) //IL_0731: Expected O, but got Unknown //IL_0736: Expected O, but got Unknown //IL_0740: Unknown result type (might be due to invalid IL or missing references) //IL_0745: Unknown result type (might be due to invalid IL or missing references) //IL_0755: Unknown result type (might be due to invalid IL or missing references) //IL_076a: Unknown result type (might be due to invalid IL or missing references) //IL_0774: Unknown result type (might be due to invalid IL or missing references) //IL_077c: Unknown result type (might be due to invalid IL or missing references) //IL_0783: Unknown result type (might be due to invalid IL or missing references) //IL_078a: Unknown result type (might be due to invalid IL or missing references) //IL_078f: Unknown result type (might be due to invalid IL or missing references) //IL_0799: Expected O, but got Unknown //IL_0799: Unknown result type (might be due to invalid IL or missing references) //IL_07a5: Expected O, but got Unknown if ((Object)(object)skin != (Object)null) { return skin; } darkTex = MakeTex(new Color(0.12f, 0.12f, 0.16f, 0.95f)); midTex = MakeTex(new Color(0.18f, 0.18f, 0.22f, 0.95f)); lightTex = MakeTex(new Color(0.25f, 0.25f, 0.3f, 0.95f)); accentTex = MakeTex(new Color(0.3f, 0.3f, 0.38f, 1f)); hoverTex = MakeTex(new Color(0.35f, 0.35f, 0.42f, 1f)); activeTex = MakeTex(new Color(0.2f, 0.45f, 0.7f, 1f)); fieldTex = MakeTex(new Color(0.1f, 0.1f, 0.14f, 1f)); greenTex = MakeTex(new Color(0.15f, 0.4f, 0.25f, 1f)); skin = ScriptableObject.CreateInstance(); ((Object)skin).hideFlags = (HideFlags)52; if (Array.Exists(Font.GetOSInstalledFontNames(), (string f) => f == "Segoe UI")) { skin.font = Font.CreateDynamicFontFromOSFont("Segoe UI", 13); } GUISkin obj = skin; GUIStyle val = new GUIStyle(GUI.skin.window); val.normal.background = darkTex; val.normal.textColor = Color.white; val.onNormal.background = darkTex; val.onNormal.textColor = Color.white; val.border = new RectOffset(8, 8, 20, 8); val.padding = new RectOffset(8, 8, 22, 8); val.fontSize = 14; val.fontStyle = (FontStyle)1; val.alignment = (TextAnchor)1; obj.window = val; GUISkin obj2 = skin; GUIStyle val2 = new GUIStyle(GUI.skin.label); val2.normal.textColor = new Color(0.9f, 0.9f, 0.92f); val2.fontSize = 13; val2.padding = new RectOffset(2, 2, 1, 1); obj2.label = val2; GUISkin obj3 = skin; GUIStyle val3 = new GUIStyle(GUI.skin.button); val3.normal.background = accentTex; val3.normal.textColor = Color.white; val3.hover.background = hoverTex; val3.hover.textColor = Color.white; val3.active.background = activeTex; val3.active.textColor = Color.white; val3.focused.background = hoverTex; val3.focused.textColor = Color.white; val3.fontSize = 12; val3.fontStyle = (FontStyle)0; val3.padding = new RectOffset(6, 6, 3, 3); val3.margin = new RectOffset(2, 2, 2, 2); val3.border = new RectOffset(4, 4, 4, 4); obj3.button = val3; GUISkin obj4 = skin; GUIStyle val4 = new GUIStyle(GUI.skin.box); val4.normal.background = midTex; val4.normal.textColor = new Color(0.75f, 0.8f, 0.9f); val4.fontSize = 12; val4.fontStyle = (FontStyle)1; val4.padding = new RectOffset(6, 6, 4, 4); val4.margin = new RectOffset(2, 2, 2, 2); val4.border = new RectOffset(2, 2, 2, 2); val4.alignment = (TextAnchor)4; obj4.box = val4; GUISkin obj5 = skin; GUIStyle val5 = new GUIStyle(GUI.skin.toggle); val5.normal.textColor = new Color(0.85f, 0.85f, 0.88f); val5.onNormal.textColor = new Color(0.6f, 0.9f, 0.65f); val5.hover.textColor = Color.white; val5.onHover.textColor = new Color(0.65f, 1f, 0.7f); val5.fontSize = 13; val5.padding = new RectOffset(18, 2, 1, 1); obj5.toggle = val5; GUISkin obj6 = skin; GUIStyle val6 = new GUIStyle(GUI.skin.textField); val6.normal.background = fieldTex; val6.normal.textColor = Color.white; val6.focused.background = lightTex; val6.focused.textColor = Color.white; val6.hover.background = lightTex; val6.hover.textColor = Color.white; val6.fontSize = 12; val6.padding = new RectOffset(4, 4, 2, 2); val6.border = new RectOffset(2, 2, 2, 2); obj6.textField = val6; GUIStyle val7 = new GUIStyle(skin.button); val7.normal.background = lightTex; val7.normal.textColor = new Color(0.7f, 0.7f, 0.75f); val7.hover.background = hoverTex; val7.hover.textColor = Color.white; val7.fontSize = 12; val7.fontStyle = (FontStyle)0; val7.alignment = (TextAnchor)4; val7.margin = new RectOffset(0, 0, 0, 0); GUIStyle val8 = val7; GUISkin obj7 = skin; GUIStyle[] obj8 = new GUIStyle[2] { new GUIStyle(val8) { name = "ToolbarButton" }, default(GUIStyle) }; GUIStyle val9 = new GUIStyle(val8) { name = "ToolbarButtonActive" }; val9.normal.background = activeTex; val9.normal.textColor = Color.white; val9.fontStyle = (FontStyle)1; obj8[1] = val9; obj7.customStyles = (GUIStyle[])(object)obj8; GUISkin obj9 = skin; GUIStyle val10 = new GUIStyle(GUI.skin.horizontalSlider); val10.normal.background = midTex; val10.fixedHeight = 12f; val10.margin = new RectOffset(4, 4, 6, 6); obj9.horizontalSlider = val10; GUISkin obj10 = skin; GUIStyle val11 = new GUIStyle(GUI.skin.horizontalSliderThumb); val11.normal.background = activeTex; val11.hover.background = hoverTex; val11.fixedWidth = 14f; val11.fixedHeight = 14f; obj10.horizontalSliderThumb = val11; GUISkin obj11 = skin; GUIStyle val12 = new GUIStyle(GUI.skin.verticalScrollbar); val12.normal.background = midTex; val12.fixedWidth = 10f; obj11.verticalScrollbar = val12; GUISkin obj12 = skin; GUIStyle val13 = new GUIStyle(GUI.skin.verticalScrollbarThumb); val13.normal.background = lightTex; obj12.verticalScrollbarThumb = val13; GUIStyle val14 = new GUIStyle(skin.label) { fontSize = 13, fontStyle = (FontStyle)1, alignment = (TextAnchor)4 }; val14.normal.textColor = new Color(0.65f, 0.75f, 0.9f); val14.padding = new RectOffset(0, 0, 4, 4); val14.margin = new RectOffset(0, 0, 4, 2); sectionHeader = val14; GUIStyle val15 = new GUIStyle(skin.box); val15.normal.background = darkTex; val15.normal.textColor = new Color(0.95f, 0.95f, 0.8f); val15.fontSize = 11; val15.fontStyle = (FontStyle)0; val15.alignment = (TextAnchor)3; val15.padding = new RectOffset(8, 8, 4, 4); val15.wordWrap = true; tooltipStyle = val15; return skin; } } public static class QuestModToast { private struct Entry { public string Msg; public float Expiry; public Color Tint; } private const float DefaultDurationSec = 5f; private static readonly List _entries = new List(); private static GUIStyle _boxStyle; private static readonly object _lock = new object(); public static void Show(string msg, float durationSec = 5f) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) Show(msg, Color.white, durationSec); } public static void Show(string msg, Color tint, float durationSec = 5f) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(msg)) { return; } lock (_lock) { _entries.Add(new Entry { Msg = msg, Expiry = Time.realtimeSinceStartup + durationSec, Tint = tint }); if (_entries.Count > 8) { _entries.RemoveAt(0); } } } public static void DrawAll() { //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0078: 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_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_009c: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Expected O, but got Unknown //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) lock (_lock) { if (_entries.Count == 0) { return; } float now = Time.realtimeSinceStartup; _entries.RemoveAll((Entry e) => e.Expiry < now); if (_entries.Count == 0) { return; } if (_boxStyle == null) { _boxStyle = new GUIStyle(GUI.skin.box) { fontSize = 14, alignment = (TextAnchor)3, wordWrap = true, padding = new RectOffset(10, 10, 6, 6) }; } float num = 380f; float num2 = Screen.width; float num3 = 24f; Rect val2 = default(Rect); foreach (Entry entry in _entries) { GUIContent val = new GUIContent(entry.Msg); float num4 = Mathf.Min(_boxStyle.CalcSize(val).x + 24f, num); ((Rect)(ref val2))..ctor(num2 - num4 - 24f, num3, num4, _boxStyle.CalcHeight(val, num4)); float num5 = Mathf.Clamp01((entry.Expiry - now) / 1.5f); Color color = GUI.color; GUI.color = new Color(entry.Tint.r, entry.Tint.g, entry.Tint.b, num5); GUI.Box(val2, val, _boxStyle); GUI.color = color; num3 += ((Rect)(ref val2)).height + 6f; } } } } public static class EdgeBugDoubleTrobbio { private const string TrobbioNamePrefix = "Trobbio"; private static readonly string[] BossFsmHints = new string[3] { "phase", "hit_state", "boss" }; public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; QuestModPlugin.Log.LogInfo((object)"EdgeBugDoubleTrobbio: registered (scene-load dedup)"); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!QuestModConstants.IsTransientMenuScene(((Scene)(ref scene)).name) && QuestModPlugin.IsAdjustedWishes) { string queuedScene = ((Scene)(ref scene)).name; UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { DedupTrobbio(queuedScene); }, 0.75f); UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { DedupTrobbio(queuedScene); }, 2.25f); } } private static void DedupTrobbio(string queuedScene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != queuedScene) { return; } List list = new List(); GameObject val = null; GameObject[] array = Object.FindObjectsByType((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (GameObject val2 in array) { if ((Object)(object)val2 == (Object)null || !IsTrobbio(val2) || ((Object)(object)val2.transform.parent != (Object)null && IsTrobbio(((Component)val2.transform.parent).gameObject))) { continue; } if (LooksLikeBoss(val2)) { if ((Object)(object)val == (Object)null || ScoreForActivity(val2) > ScoreForActivity(val)) { val = val2; } } else { list.Add(val2); } } if (list.Count == 0 || ((Object)(object)val == (Object)null && list.Count <= 1)) { return; } GameObject val3; if ((Object)(object)val != (Object)null) { val3 = val; } else { val3 = list[0]; int num = ScoreForActivity(val3); int num2 = SceneRootIndex(val3); for (int j = 1; j < list.Count; j++) { int num3 = ScoreForActivity(list[j]); int num4 = SceneRootIndex(list[j]); if (num3 > num || (num3 == num && num4 < num2)) { val3 = list[j]; num = num3; num2 = num4; } } } int num5 = 0; foreach (GameObject item in list) { if (!((Object)(object)item == (Object)(object)val3)) { try { item.SetActive(false); num5++; QuestModPlugin.LogDebugInfo($"EdgeBugDoubleTrobbio: suppressed wandering '{((Object)item).name}' (id={((Object)item).GetInstanceID()})"); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("EdgeBugDoubleTrobbio: could not deactivate '" + ((Object)item).name + "': " + ex.Message)); } } } if (num5 > 0) { QuestModPlugin.Log.LogInfo((object)$"EdgeBugDoubleTrobbio: kept '{((Object)val3).name}' (id={((Object)val3).GetInstanceID()}), suppressed {num5} wandering duplicate(s)"); } } private static bool IsTrobbio(GameObject go) { if (((Object)go).name.IndexOf("Trobbio", StringComparison.OrdinalIgnoreCase) < 0) { return false; } return (Object)(object)go.GetComponentInChildren(true) != (Object)null; } private static bool LooksLikeBoss(GameObject go) { PlayMakerFSM[] componentsInChildren = go.GetComponentsInChildren(true); foreach (PlayMakerFSM val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string text = val.FsmName ?? string.Empty; string[] bossFsmHints = BossFsmHints; foreach (string value in bossFsmHints) { if (text.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } if (val.Fsm == null || val.Fsm.States == null) { continue; } FsmState[] states = val.Fsm.States; foreach (FsmState obj in states) { string text2 = ((obj != null) ? obj.Name : null); if (string.IsNullOrEmpty(text2)) { continue; } bossFsmHints = BossFsmHints; foreach (string value2 in bossFsmHints) { if (text2.IndexOf(value2, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } } return false; } private static int SceneRootIndex(GameObject go) { Transform val = go.transform; while ((Object)(object)val.parent != (Object)null) { val = val.parent; } return val.GetSiblingIndex(); } private static int ScoreForActivity(GameObject go) { int num = 0; PlayMakerFSM[] componentsInChildren = go.GetComponentsInChildren(false); foreach (PlayMakerFSM val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && val.Fsm != null) { num++; string activeStateName = val.Fsm.ActiveStateName; if (!string.IsNullOrEmpty(activeStateName) && activeStateName.IndexOf("idle", StringComparison.OrdinalIgnoreCase) < 0 && activeStateName.IndexOf("init", StringComparison.OrdinalIgnoreCase) < 0) { num += 2; } } } return num; } } public static class EdgeBugPinstressNeedleStrike { public const string PinstressPreQuest = "Pinstress Battle Pre"; private static FieldInfo? _superJumpField; private static FieldInfo? _pinstressReadyField; private static bool _resolved; public static void Initialize() { QuestModPlugin.Log.LogInfo((object)"EdgeBugPinstressNeedleStrike: registered (gates Pinstress Battle Pre on hasSuperJump + pinstressQuestReady)"); } internal static bool HasNeedleStrike() { return PinstressIsReady(); } internal static bool PinstressIsReady() { if (PlayerData.instance == null) { return false; } if (!_resolved) { _resolved = true; Type type = ((object)PlayerData.instance).GetType(); _superJumpField = type.GetField("hasSuperJump", BindingFlags.Instance | BindingFlags.Public); _pinstressReadyField = type.GetField("pinstressQuestReady", BindingFlags.Instance | BindingFlags.Public); if (_superJumpField == null) { QuestModPlugin.Log.LogWarning((object)"EdgeBugPinstressNeedleStrike: PlayerData.hasSuperJump missing - failing open (no narrowing)"); } if (_pinstressReadyField == null) { QuestModPlugin.Log.LogWarning((object)"EdgeBugPinstressNeedleStrike: PlayerData.pinstressQuestReady missing - failing open (no narrowing)"); } } if (_superJumpField == null || _pinstressReadyField == null) { return true; } try { bool num = (bool)_superJumpField.GetValue(PlayerData.instance); bool flag = (bool)_pinstressReadyField.GetValue(PlayerData.instance); return num && flag; } catch { return true; } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] [HarmonyPriority(0)] public static class PinstressIsAvailableNarrowingPatch { public static void Postfix(FullQuestBase __instance, ref bool __result) { if (QuestModPlugin.IsAdjustedWishes && __result && !((Object)(object)__instance == (Object)null) && !(((Object)__instance).name != "Pinstress Battle Pre") && !EdgeBugPinstressNeedleStrike.PinstressIsReady()) { QuestModPlugin.LogDebugInfo("PinstressNarrow: '" + ((Object)__instance).name + "' gated until pinstressQuestReady && hasSuperJump"); __result = false; } } } public static class GourmandTimerPatch { private static Type? deliveryQuestItemType; private static FieldInfo? totalTimerField; private static MethodInfo? takeHitIntMethod; private static bool resolved; private static bool harmonyApplied; public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!string.IsNullOrEmpty(((Scene)(ref scene)).name) && !(((Scene)(ref scene)).name == "Menu_Title")) { EnsureResolved(); UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)ApplyRuntimeOverrides, 0.5f); UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)ApplyRuntimeOverrides, 2f); } } private static void EnsureResolved() { //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown if (resolved) { return; } resolved = true; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { Type type = assembly.GetType("DeliveryQuestItem", throwOnError: false); if (type != null) { deliveryQuestItemType = type; break; } } catch { } } if (deliveryQuestItemType == null) { QuestModPlugin.Log.LogWarning((object)"GourmandTimerPatch: DeliveryQuestItem type not found at runtime. Decay overrides will be no-ops."); return; } totalTimerField = deliveryQuestItemType.GetField("totalTimer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (totalTimerField == null) { QuestModPlugin.Log.LogWarning((object)"GourmandTimerPatch: totalTimer field missing on DeliveryQuestItem; decay-seconds override is a no-op."); } takeHitIntMethod = deliveryQuestItemType.GetMethod("TakeHit", BindingFlags.Static | BindingFlags.Public, null, new Type[1] { typeof(int) }, null); if (takeHitIntMethod == null) { QuestModPlugin.Log.LogWarning((object)"GourmandTimerPatch: static TakeHit(int) method missing on DeliveryQuestItem; StopDecay toggle is a no-op."); } if (harmonyApplied || !(takeHitIntMethod != null)) { return; } try { Harmony val = new Harmony("com.silkmod.questmod.gourmand-timer"); MethodInfo method = typeof(GourmandTimerPatch).GetMethod("TakeHitPrefix", BindingFlags.Static | BindingFlags.NonPublic); val.Patch((MethodBase)takeHitIntMethod, new HarmonyMethod(method), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmonyApplied = true; QuestModPlugin.Log.LogInfo((object)("GourmandTimerPatch: prefixed " + deliveryQuestItemType.Name + ".TakeHit(int) for StopDecay toggle.")); } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("GourmandTimerPatch: failed to patch TakeHit(int): " + ex.Message)); } } private static bool TakeHitPrefix() { if (QuestModPlugin.GourmandStopDecay == null) { return true; } if (!QuestModPlugin.GourmandStopDecay.Value) { return true; } return false; } private static void ApplyRuntimeOverrides() { if (deliveryQuestItemType == null || totalTimerField == null || QuestModPlugin.GourmandDecaySeconds == null) { return; } float value = QuestModPlugin.GourmandDecaySeconds.Value; if (Math.Abs(value - 47f) < 0.01f) { return; } try { Object[] array = Resources.FindObjectsOfTypeAll(deliveryQuestItemType); int num = 0; Object[] array2 = array; foreach (Object obj in array2) { try { if (Math.Abs((float)totalTimerField.GetValue(obj) - value) > 0.01f) { totalTimerField.SetValue(obj, value); num++; } } catch { } } if (num > 0) { QuestModPlugin.LogDebugInfo($"GourmandTimerPatch: applied totalTimer={value:F1}s to {num} DeliveryQuestItem instance(s)."); } } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("GourmandTimerPatch: ApplyRuntimeOverrides failed: " + ex.Message)); } } } public static class QuestItemProtection { private static bool _applied; public static bool InvinciblePrefix() { return !QuestModPlugin.QuestItemInvincible.Value; } public static void Initialize() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown if (_applied) { return; } _applied = true; try { Harmony val = new Harmony("com.silkmod.questmod.item-invincible"); HarmonyMethod val2 = new HarmonyMethod(typeof(QuestItemProtection).GetMethod("InvinciblePrefix", BindingFlags.Static | BindingFlags.Public)); int num = 0; Type type = AccessTools.TypeByName("DeliveryQuestItem"); if (type != null) { MethodInfo methodInfo = AccessTools.Method(type, "BreakAllInternal", new Type[2] { typeof(bool), typeof(bool) }, (Type[])null); if (methodInfo != null) { val.Patch((MethodBase)methodInfo, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } foreach (MethodInfo item in from m in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where m.Name == "TakeHitForItem" select m) { try { val.Patch((MethodBase)item, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("QuestItemProtection: TakeHitForItem(" + string.Join(",", from p in item.GetParameters() select p.ParameterType.Name) + ") patch failed: " + ex.Message)); } } } else { QuestModPlugin.Log.LogWarning((object)"QuestItemProtection: DeliveryQuestItem type missing; invincibility no-op."); } Type type2 = AccessTools.TypeByName("HeroController"); MethodInfo methodInfo2 = ((type2 != null) ? AccessTools.Method(type2, "TickDeliveryItems", (Type[])null, (Type[])null) : null); if (methodInfo2 != null) { val.Patch((MethodBase)methodInfo2, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num++; } QuestModPlugin.Log.LogInfo((object)$"QuestItemProtection: applied {num} prefix(es) gating QuestItemInvincible."); } catch (Exception ex2) { QuestModPlugin.Log.LogWarning((object)("QuestItemProtection: Initialize threw: " + ex2.Message)); } } } public static class QuestStateHooks { private sealed class ReferenceEqualityComparer : IEqualityComparer { public new bool Equals(object x, object y) { return x == y; } public int GetHashCode(object obj) { return RuntimeHelpers.GetHashCode(obj); } } private static readonly HashSet PatchedFSMs = new HashSet(); private static readonly HashSet PatchedActions = new HashSet(new ReferenceEqualityComparer()); private static readonly HashSet NpcSpawnFlags = new HashSet { "metMapper", "MapperAppearInBellhart", "hasMarker_a", "shermaQuestActive", "shermaInBellhart", "ShakraFinalQuestAppear", "MetCityMerchantScavenge", "MetCityMerchantEnclave" }; public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; QuestModPlugin.Log.LogInfo((object)"QuestStateHooks: Registered sceneLoaded hook"); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (QuestModConstants.IsTransientMenuScene(((Scene)(ref scene)).name)) { return; } QuestModPlugin.SyncFromSaveData(); QuestModPlugin.LogDebugInfo("Scene loaded: " + ((Scene)(ref scene)).name); PatchedFSMs.Clear(); PatchedActions.Clear(); SetNpcSpawnFlags(); ApplyGranularBypasses(); ActivateQuestBoards(); RefreshQuestBoards(); ApplyAllWishesTargetBypass(); string queuedScene = ((Scene)(ref scene)).name; UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != queuedScene) { QuestModPlugin.LogDebugInfo("Delayed re-patch skipped: scene changed from " + queuedScene); } else { QuestModPlugin.LogDebugInfo("Delayed re-patch running..."); PatchedFSMs.Clear(); PatchedActions.Clear(); PatchAllQuestFSMs(); SetNpcSpawnFlags(); ApplyGranularBypasses(); ActivateQuestBoards(); RefreshQuestBoards(); DumpNpcDiagnostics(); if (QuestModPlugin.AllQuestsAccepted) { QuestAcceptance.InjectAndAcceptAllQuests(); } else { QuestAcceptance.AutoAcceptFlaggedQuests(); } if (QuestModPlugin.EnableCompletionOverrides.Value) { QuestCompletionOverrides.ApplySavedOverrides(); } ApplyAllWishesTargetBypass(); } }, 0.5f); } private static void ApplyAllWishesTargetBypass() { if (QuestPolicyStore.IsAvailable("Extractor Blue")) { QuestCompletionOverrides.SetTargetCountTransient("Extractor Blue", 1, 0); } if (QuestPolicyStore.IsAvailable("Extractor Blue Worms")) { QuestCompletionOverrides.SetTargetCountTransient("Extractor Blue Worms", 1, 0); } } private static string ReadActionQuestName(object action) { try { FieldInfo field = action.GetType().GetField("Quest", BindingFlags.Instance | BindingFlags.Public); if (field == null) { return null; } object value = field.GetValue(action); if (value == null) { return null; } PropertyInfo property = value.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public); if (property == null) { return null; } object value2 = property.GetValue(value); if (value2 == null) { return null; } PropertyInfo property2 = value2.GetType().GetProperty("name", BindingFlags.Instance | BindingFlags.Public); if (property2 == null) { return null; } return property2.GetValue(value2)?.ToString(); } catch (Exception) { return null; } } private static bool ReachesBeginQuest(PlayMakerFSM fsm, FsmState start, int maxDepth = 6) { if ((Object)(object)fsm == (Object)null || start == null) { return false; } HashSet visited = new HashSet(); return ReachesBeginQuestInner(fsm, start, maxDepth, visited); } private static bool ReachesBeginQuestInner(PlayMakerFSM fsm, FsmState state, int depth, HashSet visited) { if (state == null || depth < 0) { return false; } if (visited.Contains(state.Name)) { return false; } visited.Add(state.Name); if (state.Actions != null) { FsmStateAction[] actions = state.Actions; foreach (FsmStateAction val in actions) { if (val != null) { switch (((object)val).GetType().Name) { case "BeginQuest": case "BeginQuestV2": case "BoardQuest": return true; } } } } if (state.Transitions == null) { return false; } FsmTransition[] transitions = state.Transitions; foreach (FsmTransition val2 in transitions) { if (val2 == null) { continue; } string toState = val2.ToState; if (string.IsNullOrEmpty(toState)) { continue; } FsmState val3 = null; FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val4 in fsmStates) { if (val4 != null && val4.Name == toState) { val3 = val4; break; } } if (val3 != null && ReachesBeginQuestInner(fsm, val3, depth - 1, visited)) { return true; } } return false; } public static bool ShouldRedirectActionForCurrentMode(PlayMakerFSM fsm, FsmState state, object action) { return ShouldRedirect(fsm, state, action); } private static bool ShouldRedirect(PlayMakerFSM fsm, FsmState state, object action) { if (QuestModPlugin.WishesMode == AllWishesMode.Disabled) { return false; } if (QuestModPlugin.IsPureWishes) { return true; } string text = ReadActionQuestName(action); if (string.IsNullOrEmpty(text)) { return false; } if (!QuestAcceptance.IsChainPrereqMet(text)) { return false; } if (QuestAcceptance.GetExclusionConflict(text) != null) { return false; } if (!ReachesBeginQuest(fsm, state)) { return false; } return true; } private static bool AnyQuestAvailable() { if (QuestModPlugin.AllQuestsAvailable) { return true; } Dictionary map = QuestPolicyStore.Map; if (map == null) { return false; } foreach (KeyValuePair item in map) { if (item.Value != null && item.Value.Available) { return true; } } return false; } public static void RePatchAllForCurrentMode() { PatchedFSMs.Clear(); PatchedActions.Clear(); PatchAllQuestFSMs(); } private static void PatchAllQuestFSMs() { if (!AnyQuestAvailable()) { return; } PlayMakerFSM[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (PlayMakerFSM val in array) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject.GetComponent() != (Object)null || val.Fsm == null) { continue; } try { if (val.FsmStates != null) { string text = ((Object)((Component)val).gameObject).name + "/" + val.FsmName; if (!PatchedFSMs.Contains(text)) { PatchQuestStateFSM(val, text); } } } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)("Error patching FSM " + ((Object)((Component)val).gameObject).name + ": " + ex.Message)); } } } private static void PatchQuestStateFSM(PlayMakerFSM fsm, string fsmKey) { bool flag = false; FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (val.Actions == null) { continue; } FsmStateAction[] actions = val.Actions; foreach (FsmStateAction val2 in actions) { CheckQuestStateV2 val3 = (CheckQuestStateV2)(object)((val2 is CheckQuestStateV2) ? val2 : null); if (val3 != null) { if (PatchCheckAction(val3, fsm, val, fsmKey, "V2")) { flag = true; } continue; } if (((object)val2).GetType().Name == "CheckQuestState") { if (PatchCheckActionV1(val2, fsm, val, fsmKey)) { flag = true; } continue; } PlayerDataBoolTest val4 = (PlayerDataBoolTest)(object)((val2 is PlayerDataBoolTest) ? val2 : null); if (val4 != null) { if (PatchPlayerDataBoolTest(val4, val.Name, fsmKey)) { flag = true; } continue; } BoolTest val5 = (BoolTest)(object)((val2 is BoolTest) ? val2 : null); if (val5 != null && PatchBoolTest(val5, val.Name, fsmKey)) { flag = true; } } } if (flag) { PatchedFSMs.Add(fsmKey); } } private static bool PatchCheckAction(CheckQuestStateV2 checkAction, PlayMakerFSM fsm, FsmState state, string fsmKey, string version) { try { if (!ShouldRedirect(fsm, state, checkAction)) { return false; } FsmEvent incompleteEvent = checkAction.IncompleteEvent; FsmEvent completedEvent = checkAction.CompletedEvent; FsmEvent val = incompleteEvent ?? completedEvent; if (val == null) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + state.Name + " (" + version + "): no Incomplete or Completed event, skipping")); return false; } checkAction.NotTrackedEvent = val; string text = ((incompleteEvent != null) ? "IncompleteEvent" : "CompletedEvent (fallback)"); string text2 = ReadActionQuestName(checkAction) ?? "(?)"; QuestModPlugin.LogDebugInfo(" " + fsmKey + "/" + state.Name + " (" + version + ", quest=" + text2 + "): Redirected NotTracked → " + text); return true; } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + state.Name + " (" + version + "): Patch failed - " + ex.Message)); return false; } } private static bool PatchCheckActionV1(FsmStateAction action, PlayMakerFSM fsm, FsmState state, string fsmKey) { try { if (!ShouldRedirect(fsm, state, action)) { return false; } Type type = ((object)action).GetType(); FieldInfo field = type.GetField("NotTrackedEvent", BindingFlags.Instance | BindingFlags.Public); FieldInfo field2 = type.GetField("TrackedEvent", BindingFlags.Instance | BindingFlags.Public); FieldInfo field3 = type.GetField("CompletedEvent", BindingFlags.Instance | BindingFlags.Public); if (field == null) { return false; } object obj = field2?.GetValue(action); object obj2 = field3?.GetValue(action); object obj3 = obj ?? obj2; if (obj3 == null) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + state.Name + " (V1): no Tracked or Completed event, skipping")); return false; } field.SetValue(action, obj3); string text = ((obj != null) ? "TrackedEvent" : "CompletedEvent (fallback)"); string text2 = ReadActionQuestName(action) ?? "(?)"; QuestModPlugin.LogDebugInfo(" " + fsmKey + "/" + state.Name + " (V1, quest=" + text2 + "): Redirected NotTracked → " + text); return true; } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + state.Name + " (V1): Patch failed - " + ex.Message)); return false; } } private static bool PatchPlayerDataBoolTest(PlayerDataBoolTest boolTest, string stateName, string fsmKey) { try { string text = ((boolTest.boolName != null) ? boolTest.boolName.Value : null); if (string.IsNullOrEmpty(text) || !NpcSpawnFlags.Contains(text)) { return false; } FsmEvent isTrue = boolTest.isTrue; if (isTrue == null || boolTest.isFalse == null) { return false; } if (!PatchedActions.Add(boolTest)) { return false; } boolTest.isFalse = isTrue; QuestModPlugin.LogDebugInfo(" " + fsmKey + "/" + stateName + " (PDTest '" + text + "'): Redirected isFalse → isTrue"); return true; } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + stateName + " (PDTest): Patch failed - " + ex.Message)); return false; } } private static bool PatchBoolTest(BoolTest boolTest, string stateName, string fsmKey) { try { string text = ((boolTest.boolVariable != null) ? ((NamedVariable)boolTest.boolVariable).Name : null); if (string.IsNullOrEmpty(text) || !NpcSpawnFlags.Contains(text)) { return false; } FsmEvent isTrue = boolTest.isTrue; if (isTrue == null || boolTest.isFalse == null) { return false; } if (!PatchedActions.Add(boolTest)) { return false; } boolTest.isFalse = isTrue; QuestModPlugin.LogDebugInfo(" " + fsmKey + "/" + stateName + " (BoolTest '" + text + "'): Redirected isFalse → isTrue"); return true; } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)(" " + fsmKey + "/" + stateName + " (BoolTest): Patch failed - " + ex.Message)); return false; } } private static void SetNpcSpawnFlags() { if (!AnyQuestAvailable()) { return; } if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.LogDebugInfo("SetNpcSpawnFlags: skipped (legacy save, no override)"); } else { if (PlayerData.instance == null) { return; } int num = 0; Type type = ((object)PlayerData.instance).GetType(); foreach (string npcSpawnFlag in NpcSpawnFlags) { FieldInfo field = type.GetField(npcSpawnFlag, BindingFlags.Instance | BindingFlags.Public); if (!(field == null) && !(field.FieldType != typeof(bool)) && !(bool)field.GetValue(PlayerData.instance)) { field.SetValue(PlayerData.instance, true); num++; QuestModPlugin.LogDebugInfo("SetNpcSpawnFlags: " + npcSpawnFlag + " = true"); } } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"Set {num} NPC spawn flags for AllQuests mode"); } } } private static void ActivateQuestBoards() { if (!AnyQuestAvailable()) { return; } MonoBehaviour[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (MonoBehaviour val in array) { if (((object)val).GetType().Name == "QuestBoardInteractable" && !((Component)val).gameObject.activeSelf) { ((Component)val).gameObject.SetActive(true); QuestModPlugin.LogDebugInfo("Activated quest board: " + ((Object)((Component)val).gameObject).name); } } } private static void RefreshQuestBoards() { MonoBehaviour[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (MonoBehaviour val in array) { if (!(((object)val).GetType().Name == "QuestBoardInteractable")) { continue; } try { MethodInfo method = ((object)val).GetType().GetMethod("RefreshQuestBoard"); if (method != null) { method.Invoke(val, null); QuestModPlugin.LogDebugInfo("Refreshed quest board: " + ((Object)((Component)val).gameObject).name); } } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)("Failed to refresh quest board: " + ex.Message)); } } } private static void ApplyGranularBypasses() { if (PlayerData.instance == null) { return; } if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.LogDebugInfo("ApplyGranularBypasses: skipped (legacy save, no override)"); return; } GranularPrereqs prereqs = QuestModPlugin.Prereqs; if (prereqs != null) { int num = 0; if (prereqs.BypassFleatopia) { num += SetPdBoolIfFalse("visitedFleatopia"); num += SetPdBoolIfFalse("SeenFleatopiaEmpty"); num += SetPdBoolIfFalse("SethJoinedFleatopia"); num += SetPdBoolIfFalse("TroupeLeaderSpokenFleatopiaSearch"); num += SetPdBoolIfFalse("MetCaravanTroupeLeader"); } if (prereqs.BypassMandatoryWishes) { num += SetPdBoolIfFalse("promisedFirstWish"); } if (prereqs.BypassFaydownCloak) { num += SetPdBoolIfFalse("hasDoubleJump"); } if (prereqs.BypassNeedolin) { num += SetPdBoolIfFalse("hasNeedolin"); } if (prereqs.BypassBonebottomQuestBoard) { num += SetPdBoolIfFalse("bonebottomQuestBoardFixed"); } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"ApplyGranularBypasses: set {num} PlayerData flag(s)"); } } } private static int SetPdBoolIfFalse(string fieldName) { try { FieldInfo field = ((object)PlayerData.instance).GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public); if (field == null || field.FieldType != typeof(bool)) { QuestModPlugin.LogDebugInfo("ApplyGranularBypasses: PlayerData." + fieldName + " not found (skipping)"); return 0; } if ((bool)field.GetValue(PlayerData.instance)) { return 0; } field.SetValue(PlayerData.instance, true); QuestModPlugin.LogDebugInfo("ApplyGranularBypasses: " + fieldName + " = true"); return 1; } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)("ApplyGranularBypasses: failed to set " + fieldName + ": " + ex.Message)); return 0; } } private static void DumpNpcDiagnostics() { if (!QuestModPlugin.DebugLogging.Value || !AnyQuestAvailable()) { return; } QuestModPlugin.Log.LogInfo((object)"=== NPC Diagnostics ==="); PlayMakerFSM[] array = Object.FindObjectsByType((FindObjectsInactive)0, (FindObjectsSortMode)0); foreach (PlayMakerFSM val in array) { if ((Object)(object)val == (Object)null || val.Fsm == null || !((Component)val).gameObject.activeInHierarchy) { continue; } try { if (val.FsmStates == null) { continue; } GameObject gameObject = ((Component)val).gameObject; List list = new List(); FsmState[] fsmStates = val.FsmStates; foreach (FsmState val2 in fsmStates) { if (val2.Actions == null) { continue; } FsmStateAction[] actions = val2.Actions; for (int k = 0; k < actions.Length; k++) { string name = ((object)actions[k]).GetType().Name; if (name.Contains("Quest") || name.Contains("Bool") || name.Contains("PlayerData") || name.Contains("Activate") || name.Contains("SetActive") || name.Contains("GetPlayerData")) { list.Add(val2.Name + "/" + name); } } } if (list.Count > 0) { QuestModPlugin.Log.LogInfo((object)(" [" + (gameObject.activeInHierarchy ? "ON" : "OFF") + "] " + ((Object)gameObject).name + "/" + val.FsmName + ": " + string.Join(", ", list))); } } catch (Exception ex) { QuestModPlugin.Log.LogDebug((object)("Diagnostics error on " + ((Object)((Component)val).gameObject).name + ": " + ex.Message)); } } QuestModPlugin.Log.LogInfo((object)"=== End NPC Diagnostics ==="); } } [HarmonyPatch(typeof(GameManager), "StartNewGame")] [HarmonyPriority(0)] public static class QuestModInitializeOnNewGame { public static void Postfix() { QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData != null && !questModSaveData.QuestModInitialized) { questModSaveData.QuestModInitialized = true; ManualLogSource log = QuestModPlugin.Log; if (log != null) { log.LogInfo((object)"StartNewGame: marked save as QuestMod-aware"); } } } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public static class QuestAvailabilityPatch { public static void Postfix(FullQuestBase __instance, ref bool __result) { if (!QuestPolicyStore.IsAvailable(((Object)__instance).name) || !QuestModPlugin.IsQuestDiscovered(((Object)__instance).name) || (QuestModPlugin.IsAdjustedWishes && !QuestAcceptance.IsChainPrereqMet(((Object)__instance).name))) { return; } if (QuestModPlugin.IsAdjustedWishes && QuestAcceptance.GetExclusionConflict(((Object)__instance).name) != null) { QuestModPlugin.LogDebugInfo("IsAvailable narrowing (Adjusted): " + ((Object)__instance).name + " blocked by mutually-exclusive twin"); return; } if (QuestModPlugin.IsAdjustedWishes) { QuestRequirements.ExtraConditionResult extraConditionResult = QuestRequirements.EvaluateAvailableConditions(((Object)__instance).name); if (!extraConditionResult.Pass) { QuestModPlugin.LogDebugInfo("IsAvailable narrowing (Adjusted): " + ((Object)__instance).name + " blocked -- " + extraConditionResult.Reason); return; } } if (!__result) { QuestModPlugin.LogDebugInfo($"IsAvailable override ({QuestModPlugin.WishesMode}): {((Object)__instance).name} was False, returning True"); __result = true; } } } public static class SilverBellPatch { public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!QuestModConstants.IsTransientMenuScene(((Scene)(ref scene)).name)) { string queuedScene = ((Scene)(ref scene)).name; UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { PatchDroppers(queuedScene); }, 0.5f); UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { PatchDroppers(queuedScene); }, 2f); } } private static void PatchDroppers(string queuedScene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != queuedScene || !QuestModPlugin.GuaranteedSilverBells.Value) { return; } PlayMakerFSM[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (PlayMakerFSM val in array) { if (!((Object)(object)val == (Object)null) && ((Object)((Component)val).gameObject).name.StartsWith("Quest Bell Dropper") && !(val.FsmName != "Control")) { PatchDropperFSM(val); } } } private static void PatchDropperFSM(PlayMakerFSM fsm) { if (fsm.Fsm == null || fsm.FsmStates == null) { return; } FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (val == null || val.Name != "Drop Type") { continue; } if (val.Transitions == null) { break; } FsmTransition[] transitions = val.Transitions; foreach (FsmTransition val2 in transitions) { if (val2.EventName == "STANDARD") { val2.ToState = "Silver"; } } break; } } } [HarmonyPatch(typeof(ActivateIfPlayerdataTrue), "Start")] public static class ActivateIfPlayerdataTrueStartPatch { internal const string WishboardBoolName = "bonebottomQuestBoardFixed"; internal static readonly string[] WishwallExcludeSubstrings = new string[6] { "broken", "destroyed", "damaged", "ruined", "skull king", "skullking" }; private static bool LooksLikeWishwallGate(ActivateIfPlayerdataTrue inst) { if ((Object)(object)inst == (Object)null) { return false; } string a = inst.boolName ?? ""; string text = (((Object)(object)((Component)inst).gameObject != (Object)null) ? ((Object)((Component)inst).gameObject).name : ""); string text2 = (((Object)(object)inst.objectToActivate != (Object)null) ? ((Object)inst.objectToActivate).name : ""); if (text.IndexOf("wishwall", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("wish_wall", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("questboard", StringComparison.OrdinalIgnoreCase) < 0 && text.IndexOf("quest_board", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("wishwall", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("wish_wall", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("questboard", StringComparison.OrdinalIgnoreCase) < 0 && text2.IndexOf("quest_board", StringComparison.OrdinalIgnoreCase) < 0) { return string.Equals(a, "bonebottomQuestBoardFixed", StringComparison.Ordinal); } return true; } internal static bool IsExcludedWishwall(string name) { if (string.IsNullOrEmpty(name)) { return false; } string text = name.ToLowerInvariant(); string[] wishwallExcludeSubstrings = WishwallExcludeSubstrings; foreach (string value in wishwallExcludeSubstrings) { if (text.Contains(value)) { return true; } } return false; } public static void Postfix(ActivateIfPlayerdataTrue __instance) { bool flag = QuestModPlugin.BypassWishboardLock?.Value ?? false; QuestModPlugin instance = QuestModPlugin.Instance; bool flag2 = instance != null && (instance.SaveData?.Prereqs?.BypassAllWishwalls).GetValueOrDefault(); if ((!flag && !flag2) || (Object)(object)__instance == (Object)null) { return; } GameObject gameObject = ((Component)__instance).gameObject; if (!((Object)(object)gameObject == (Object)null) && (!flag || flag2 || string.Equals(__instance.boolName, "bonebottomQuestBoardFixed", StringComparison.Ordinal)) && (!flag2 || (LooksLikeWishwallGate(__instance) && !IsExcludedWishwall(((Object)(object)__instance.objectToActivate != (Object)null) ? ((Object)__instance.objectToActivate).name : "") && !IsExcludedWishwall(((Object)gameObject).name)))) { if (!((Component)__instance).gameObject.activeSelf) { ((Component)__instance).gameObject.SetActive(true); } if ((Object)(object)__instance.objectToActivate != (Object)null && !__instance.objectToActivate.activeSelf) { __instance.objectToActivate.SetActive(true); } QuestModPlugin.LogDebugInfo("WishwallBypass: forced activation on '" + ((Object)((Component)__instance).gameObject).name + "' (boolName='" + __instance.boolName + "', objectToActivate='" + (((Object)(object)__instance.objectToActivate != (Object)null) ? ((Object)__instance.objectToActivate).name : "null") + "')"); } } } public static class WishboardSceneSweep { private static readonly string[] WishwallGoNamePatterns = new string[6] { "Quest_Board", "Quest Board", "Wishwall", "Wish_Wall", "Wish Wall", "QuestBoard" }; private static readonly string[] WishwallExcludePatterns = new string[14] { "broken", "destroyed", "damaged", "ruined", "skull king", "skullking", "covered", "unlit", "pre_", "_pre", "inactive", "off_state", "scaffold", "construction" }; public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; QuestModPlugin.Log.LogInfo((object)"WishboardSceneSweep: registered sceneLoaded hook"); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (!QuestModConstants.IsTransientMenuScene(((Scene)(ref scene)).name)) { string queuedScene = ((Scene)(ref scene)).name; UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { SweepOnce(queuedScene); }, 0.5f); UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { SweepOnce(queuedScene); }, 2f); } } private static void SweepOnce(string queuedScene) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0311: Unknown result type (might be due to invalid IL or missing references) //IL_0316: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0328: Unknown result type (might be due to invalid IL or missing references) Scene val = SceneManager.GetActiveScene(); if (((Scene)(ref val)).name != queuedScene) { return; } bool flag = QuestModPlugin.BypassWishboardLock?.Value ?? false; QuestModPlugin instance = QuestModPlugin.Instance; bool flag2 = instance != null && (instance.SaveData?.Prereqs?.BypassAllWishwalls).GetValueOrDefault(); if (!flag && !flag2) { return; } ActivateIfPlayerdataTrue[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); int num = 0; ActivateIfPlayerdataTrue[] array2 = array; foreach (ActivateIfPlayerdataTrue val2 in array2) { if ((Object)(object)val2 == (Object)null) { continue; } bool flag3 = false; if (flag && string.Equals(val2.boolName, "bonebottomQuestBoardFixed", StringComparison.Ordinal)) { flag3 = true; } if (flag2) { string text = (((Object)(object)((Component)val2).gameObject != (Object)null) ? ((Object)((Component)val2).gameObject).name : ""); string text2 = (((Object)(object)val2.objectToActivate != (Object)null) ? ((Object)val2.objectToActivate).name : ""); if (text.IndexOf("wishwall", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("wish_wall", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("questboard", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("quest_board", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("wishwall", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("wish_wall", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("questboard", StringComparison.OrdinalIgnoreCase) >= 0 || text2.IndexOf("quest_board", StringComparison.OrdinalIgnoreCase) >= 0) { flag3 = true; } } if (!flag3) { continue; } string name = (((Object)(object)val2.objectToActivate != (Object)null) ? ((Object)val2.objectToActivate).name : ""); if (!flag2 || (!ActivateIfPlayerdataTrueStartPatch.IsExcludedWishwall(name) && !ActivateIfPlayerdataTrueStartPatch.IsExcludedWishwall(((Object)((Component)val2).gameObject).name))) { bool flag4 = false; if (!((Component)val2).gameObject.activeSelf) { ((Component)val2).gameObject.SetActive(true); flag4 = true; } if ((Object)(object)val2.objectToActivate != (Object)null && !val2.objectToActivate.activeSelf) { val2.objectToActivate.SetActive(true); flag4 = true; } if (flag4) { num++; QuestModPlugin.LogDebugInfo("WishboardSceneSweep: forced '" + ((Object)((Component)val2).gameObject).name + "' (objectToActivate='" + (((Object)(object)val2.objectToActivate != (Object)null) ? ((Object)val2.objectToActivate).name : "null") + "')"); } } } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"WishboardSceneSweep: activated {num} dormant wishboard gate(s)"); } int num2 = 0; if (flag2) { Transform[] array3 = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); HashSet visited = new HashSet(); Transform[] array4 = array3; foreach (Transform val3 in array4) { if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)null) { continue; } GameObject gameObject = ((Component)val3).gameObject; val = gameObject.scene; if (!((Scene)(ref val)).IsValid()) { continue; } val = gameObject.scene; if (((Scene)(ref val)).name == "DontDestroyOnLoad") { continue; } string text3 = ((Object)gameObject).name ?? ""; bool flag5 = false; string[] wishwallGoNamePatterns = WishwallGoNamePatterns; foreach (string value in wishwallGoNamePatterns) { if (text3.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0) { flag5 = true; break; } } if (flag5 && !ActivateIfPlayerdataTrueStartPatch.IsExcludedWishwall(text3)) { num2 += ForceActivateRecursive(val3, visited); } } } if (num2 > 0) { QuestModPlugin.Log.LogInfo((object)$"WishboardSceneSweep: force-activated {num2} wishwall descendant GameObject(s)"); } } private static int ForceActivateRecursive(Transform root, HashSet visited) { //IL_008c: 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) int num = 0; if ((Object)(object)root == (Object)null || (Object)(object)((Component)root).gameObject == (Object)null) { return num; } if (!visited.Add(((Object)((Component)root).gameObject).GetInstanceID())) { return num; } string text = ((Object)((Component)root).gameObject).name ?? ""; if (!ActivateIfPlayerdataTrueStartPatch.IsExcludedWishwall(text) && !((Component)root).gameObject.activeSelf) { ((Component)root).gameObject.SetActive(true); num++; string[] obj = new string[5] { "WishboardSceneSweep: force-activated '", text, "' (scene=", null, null }; Scene scene = ((Component)root).gameObject.scene; obj[3] = ((Scene)(ref scene)).name; obj[4] = ")"; QuestModPlugin.LogDebugInfo(string.Concat(obj)); } int childCount = root.childCount; for (int i = 0; i < childCount; i++) { Transform child = root.GetChild(i); if (!((Object)(object)child == (Object)null)) { num += ForceActivateRecursive(child, visited); } } return num; } } public static class WishwallFsmPatch { private static readonly HashSet _wishwallBoolNames = new HashSet { "defeatedBellBeast", "fixerQuestBoardConvo", "bonebottomQuestBoardFixed", "visitedBellhartSaved", "metCaretaker", "bellShrineEnclave" }; private static readonly HashSet _structurallyUniqueBools = new HashSet { "fixerQuestBoardConvo", "bonebottomQuestBoardFixed" }; private static readonly string[] _fsmNamePatterns = new string[10] { "fixer", "flick", "wishwall", "wish_wall", "wisher", "questboard", "quest_board", "builder", "construction", "hammering" }; private static readonly HashSet _patchedActions = new HashSet(); public static void Initialize() { SceneManager.sceneLoaded += OnSceneLoaded; QuestModPlugin.Log.LogInfo((object)"WishwallFsmPatch: registered sceneLoaded hook"); } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (QuestModConstants.IsTransientMenuScene(((Scene)(ref scene)).name)) { return; } _patchedActions.Clear(); string queuedScene = ((Scene)(ref scene)).name; for (float num = 0.5f; num <= 30f; num += 2f) { UnityExtensions.InvokeAfterSeconds((MonoBehaviour)(object)QuestModPlugin.Instance, (Action)delegate { SweepOnce(queuedScene); }, num); } } private static void SweepOnce(string queuedScene) { //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 name = ((Scene)(ref activeScene)).name; if (string.IsNullOrEmpty(name) || name != queuedScene) { return; } QuestModPlugin instance = QuestModPlugin.Instance; bool flag = instance != null && (instance.SaveData?.Prereqs?.BypassAllWishwalls).GetValueOrDefault(); bool flag2 = QuestModPlugin.BypassWishboardLock?.Value ?? false; if (!flag && !flag2) { return; } int num = 0; PlayMakerFSM[] array = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (PlayMakerFSM val in array) { if (!((Object)(object)val == (Object)null) && val.Fsm != null && val.FsmStates != null && LooksLikeWishwallBuilder(val) && PatchFsm(val, flag, flag2)) { num++; } } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"WishwallFsmPatch: rewrote {num} wishwall-builder FSM action(s)"); } } private static bool LooksLikeWishwallBuilder(PlayMakerFSM fsm) { string text = (((Object)(object)((Component)fsm).gameObject != (Object)null) ? ((Object)((Component)fsm).gameObject).name : "").ToLowerInvariant(); string text2 = (fsm.FsmName ?? "").ToLowerInvariant(); string[] fsmNamePatterns = _fsmNamePatterns; foreach (string value in fsmNamePatterns) { if (text.Contains(value) || text2.Contains(value)) { return true; } } FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (((val != null) ? val.Actions : null) == null) { continue; } FsmStateAction[] actions = val.Actions; foreach (FsmStateAction obj in actions) { PlayerDataBoolTest val2 = (PlayerDataBoolTest)(object)((obj is PlayerDataBoolTest) ? obj : null); if (val2 != null) { FsmString boolName = val2.boolName; string item = ((boolName != null) ? boolName.Value : null) ?? ""; if (_structurallyUniqueBools.Contains(item)) { return true; } } } } return false; } private static bool PatchFsm(PlayMakerFSM fsm, bool allWishwalls, bool bonebottomOnly) { bool result = false; FsmState[] fsmStates = fsm.FsmStates; foreach (FsmState val in fsmStates) { if (((val != null) ? val.Actions : null) == null) { continue; } for (int j = 0; j < val.Actions.Length; j++) { FsmStateAction obj = val.Actions[j]; PlayerDataBoolTest val2 = (PlayerDataBoolTest)(object)((obj is PlayerDataBoolTest) ? obj : null); if (val2 == null) { continue; } FsmString boolName = val2.boolName; string text = ((boolName != null) ? boolName.Value : null) ?? ""; if ((bonebottomOnly && !allWishwalls && text != "bonebottomQuestBoardFixed") || !_wishwallBoolNames.Contains(text)) { continue; } string item = ((Object)fsm).GetInstanceID() + "|" + val.Name + "|" + j; if (_patchedActions.Contains(item)) { continue; } FsmEvent isTrue = val2.isTrue; FsmEvent isFalse = val2.isFalse; if (isTrue != null || isFalse != null) { if (isTrue != null && isFalse != null) { val2.isFalse = isTrue; QuestModPlugin.LogDebugInfo("WishwallFsmPatch: redirected isFalse -> isTrue('" + isTrue.Name + "') on PlayerDataBoolTest('" + text + "') in " + ((Object)((Component)fsm).gameObject).name + "/" + fsm.FsmName + "/" + val.Name); } else if (isTrue == null) { val2.isFalse = null; QuestModPlugin.LogDebugInfo("WishwallFsmPatch: cleared isFalse('" + isFalse.Name + "') on PlayerDataBoolTest('" + text + "') in " + ((Object)((Component)fsm).gameObject).name + "/" + fsm.FsmName + "/" + val.Name); } _patchedActions.Add(item); result = true; } } } return result; } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("com.silkmod.questmod", "QuestMod", "2.1.2")] public class QuestModPlugin : BaseUnityPlugin, ISaveDataMod, IRawSaveDataMod { private QuestModSaveData _saveData = new QuestModSaveData(); public const string Id = "com.silkmod.questmod"; internal static ManualLogSource Log { get; private set; } = null; internal static QuestModPlugin Instance { get; private set; } = null; public QuestModSaveData? SaveData { get { return _saveData; } set { _saveData = value ?? new QuestModSaveData(); if (_saveData.QuestPolicies == null) { _saveData.QuestPolicies = new Dictionary(); } if (_saveData.QuestTargetOverrides == null) { _saveData.QuestTargetOverrides = new Dictionary(); } if (_saveData.InjectedQuests == null) { _saveData.InjectedQuests = new HashSet(); } if (_saveData.CompletedQuests == null) { _saveData.CompletedQuests = new HashSet(); } if (_saveData.WishLocationOverrides == null) { _saveData.WishLocationOverrides = new Dictionary(); } if (_saveData.WishLocationTriggersFired == null) { _saveData.WishLocationTriggersFired = new HashSet(); } if (_saveData.Prereqs == null) { _saveData.Prereqs = new GranularPrereqs(); } if (_saveData.AllWishesMode == AllWishesMode.Disabled && _saveData.AllQuestsAvailable) { _saveData.AllWishesMode = AllWishesMode.Adjusted; _saveData.AllQuestsAvailable = false; } MigrateLegacyAvailability(); if (_saveData.SchemaVersion < 2) { ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"SaveData schema migration {_saveData.SchemaVersion} -> {2}"); } _saveData.SchemaVersion = 2; } WishesMode = _saveData.AllWishesMode; AllQuestsAccepted = _saveData.AllQuestsAccepted; if (!_saveData.QuestModInitialized && HasAnyQuestModState(_saveData)) { _saveData.QuestModInitialized = true; ManualLogSource log2 = Log; if (log2 != null) { log2.LogInfo((object)"Grandfathered existing QuestMod state on this save -> QuestModInitialized=true"); } } if (QuestRequirements.IsLoaded && !string.IsNullOrEmpty(_saveData.ActiveDslPreset)) { QuestRequirements.SetActivePreset(_saveData.ActiveDslPreset); } ManualLogSource log3 = Log; if (log3 != null) { log3.LogInfo((object)$"SaveData loaded: WishesMode={WishesMode}, Accepted={AllQuestsAccepted}, Policies={_saveData.QuestPolicies.Count}, Initialized={_saveData.QuestModInitialized}, Override={_saveData.OverrideSafetyForThisSave}, DslPreset={_saveData.ActiveDslPreset}"); } } } public static AllWishesMode WishesMode { get; private set; } = AllWishesMode.Disabled; public static bool AllQuestsAvailable => WishesMode != AllWishesMode.Disabled; public static bool IsPureWishes => WishesMode == AllWishesMode.Pure; public static bool IsAdjustedWishes => WishesMode == AllWishesMode.Adjusted; public static bool IsCustomRequirementsEnabled { get { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData != null && questModSaveData.EnableCustomRequirements.HasValue) { return questModSaveData.EnableCustomRequirements.Value; } return EnableCustomRequirements?.Value ?? true; } set { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData != null) { questModSaveData.EnableCustomRequirements = value; } else if (EnableCustomRequirements != null) { EnableCustomRequirements.Value = value; } } } public static bool IsFullRemoteCompleteEnabled { get { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData != null && questModSaveData.EnableFullRemoteComplete.HasValue) { return questModSaveData.EnableFullRemoteComplete.Value; } return EnableFullRemoteComplete?.Value ?? false; } set { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData != null) { questModSaveData.EnableFullRemoteComplete = value; } else if (EnableFullRemoteComplete != null) { EnableFullRemoteComplete.Value = value; } } } public static bool AllQuestsAccepted { get; private set; } public static bool AutoAcceptAllAvailable { get; private set; } public static bool IsQuestModSave => (Instance?.SaveData?.QuestModInitialized).GetValueOrDefault(); public static bool IsSafetyOverridden => (Instance?.SaveData?.OverrideSafetyForThisSave).GetValueOrDefault(); public static bool AreDestructiveFeaturesAllowed { get { if (!IsQuestModSave) { return IsSafetyOverridden; } return true; } } public static AllWishesMode LastWishesModeChange { get; private set; } = AllWishesMode.Disabled; public static float LastWishesModeChangeRealtime { get; private set; } = -1f; public static GranularPrereqs? Prereqs { get { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData == null) { return null; } if (questModSaveData.Prereqs == null) { questModSaveData.Prereqs = new GranularPrereqs(); } return questModSaveData.Prereqs; } } public static ConfigEntry EnableCompletionOverrides { get; private set; } = null; public static ConfigEntry OnlyDiscoveredQuests { get; private set; } = null; public static ConfigEntry QuestItemInvincible { get; private set; } = null; public static ConfigEntry ShowQuestDisplayNames { get; private set; } = null; public static ConfigEntry GuiToggleKey { get; private set; } = null; public static ConfigEntry GuiScale { get; private set; } = null; public static ConfigEntry GuaranteedSilverBells { get; private set; } = null; public static ConfigEntry BypassWishboardLock { get; private set; } = null; public static ConfigEntry DebugLogging { get; private set; } = null; public static ConfigEntry DevRemoveLimits { get; private set; } = null; public static ConfigEntry DevForceOperations { get; private set; } = null; public static ConfigEntry ShowPureWishesMode { get; private set; } = null; public static ConfigEntry EnableSilkSoulTab { get; private set; } = null; public static ConfigEntry ActivePreset { get; private set; } = null; public static ConfigEntry EnableCustomRequirements { get; private set; } = null; public static ConfigEntry EnableWishLocationReassignment { get; private set; } = null; public static ConfigEntry EnableFullRemoteComplete { get; private set; } = null; public static ConfigEntry GourmandStopDecay { get; private set; } = null; public static ConfigEntry GourmandDecaySeconds { get; private set; } = null; public static string Name => "QuestMod"; public static string Version => "2.1.2"; private void MigrateLegacyAvailability() { } void IRawSaveDataMod.ReadSaveData(Stream saveFile) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown if (saveFile == null) { SaveData = null; return; } string text = null; try { using StreamReader streamReader = new StreamReader(saveFile); text = streamReader.ReadToEnd(); JsonSerializer val = JsonSerializer.CreateDefault(); JsonTextReader val2 = new JsonTextReader((TextReader)new StringReader(text)); try { SaveData = val.Deserialize((JsonReader)(object)val2); } finally { ((IDisposable)val2)?.Dispose(); } } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Failed to deserialize save data (old format or truncated): " + ex.Message)); } TryBackupCorruptSave(text); QuestModToast.Show("QuestMod save data corrupted, reset to defaults. Backup written to BepInEx/config/QuestMod/corrupt-saves/.", new Color(1f, 0.5f, 0.3f), 8f); SaveData = new QuestModSaveData(); } } private static void TryBackupCorruptSave(string rawJson) { if (string.IsNullOrEmpty(rawJson)) { return; } try { string text = Path.Combine(Paths.ConfigPath, "QuestMod", "corrupt-saves"); Directory.CreateDirectory(text); string text2 = Path.Combine(text, $"corrupt-{DateTime.UtcNow:yyyyMMdd-HHmmss}.json"); File.WriteAllText(text2, rawJson); ManualLogSource log = Log; if (log != null) { log.LogWarning((object)("QuestMod: corrupt save data archived to " + text2)); } } catch (Exception ex) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogWarning((object)("QuestMod: corrupt-save backup failed: " + ex.Message)); } } } private static bool HasAnyQuestModState(QuestModSaveData d) { if (d == null) { return false; } if (d.AllWishesMode != 0) { return true; } if (d.AllQuestsAccepted) { return true; } if (d.AutoAcceptAllAvailable) { return true; } if (d.AllQuestsAvailable) { return true; } if (d.QuestPolicies != null && d.QuestPolicies.Count > 0) { return true; } if (d.InjectedQuests != null && d.InjectedQuests.Count > 0) { return true; } if (d.CompletedQuests != null && d.CompletedQuests.Count > 0) { return true; } if (d.QuestTargetOverrides != null && d.QuestTargetOverrides.Count > 0) { return true; } if (d.WishLocationOverrides != null && d.WishLocationOverrides.Count > 0) { return true; } if (!string.IsNullOrEmpty(d.ActiveDslPreset) && d.ActiveDslPreset != "vanilla") { return true; } if (d.EnableCustomRequirements.HasValue) { return true; } if (d.EnableFullRemoteComplete.HasValue) { return true; } if (d.Prereqs != null) { FieldInfo[] fields = typeof(GranularPrereqs).GetFields(BindingFlags.Instance | BindingFlags.Public); foreach (FieldInfo fieldInfo in fields) { if (!(fieldInfo.FieldType != typeof(bool)) && (bool)fieldInfo.GetValue(d.Prereqs)) { return true; } } PropertyInfo[] properties = typeof(GranularPrereqs).GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo propertyInfo in properties) { if (!(propertyInfo.PropertyType != typeof(bool)) && propertyInfo.CanRead && (bool)propertyInfo.GetValue(d.Prereqs)) { return true; } } } return false; } public static void SetSafetyOverride(bool value) { QuestModSaveData questModSaveData = Instance?.SaveData; if (questModSaveData == null) { return; } if (!value) { ManualLogSource log = Log; if (log != null) { log.LogWarning((object)"SetSafetyOverride: ignored value=false (override is one-way by design)"); } return; } questModSaveData.OverrideSafetyForThisSave = true; ManualLogSource log2 = Log; if (log2 != null) { log2.LogMessage((object)"SetSafetyOverride: ON"); } } public static void SetWishesMode(AllWishesMode mode) { AllWishesMode wishesMode = WishesMode; WishesMode = mode; SyncToSaveData(); if (wishesMode != mode) { LastWishesModeChange = mode; LastWishesModeChangeRealtime = Time.realtimeSinceStartup; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)$"WishesMode changed: {wishesMode} -> {mode}. State already in flight (FSM patches, accepted quests, NPC spawn flags) is NOT rewound; only future scene loads see the new mode."); } QuestModToast.Show($"All Wishes Mode: {wishesMode} -> {mode}"); } } public static void SetAllQuestsAvailable(bool value) { WishesMode = (value ? AllWishesMode.Adjusted : AllWishesMode.Disabled); SyncToSaveData(); } public static void SetAllQuestsAccepted(bool value) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) AllQuestsAccepted = value; if (value && WishesMode == AllWishesMode.Disabled) { WishesMode = AllWishesMode.Adjusted; LastWishesModeChange = AllWishesMode.Adjusted; LastWishesModeChangeRealtime = Time.realtimeSinceStartup; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"AllQuestsAccepted=true also enabled WishesMode=Adjusted (required coupling)"); } QuestModToast.Show("All Quests Accepted also enabled Adjusted mode", new Color(0.7f, 0.85f, 1f), 4f); } SyncToSaveData(); } public static void SetAutoAcceptAllAvailable(bool value) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) AutoAcceptAllAvailable = value; if (value && WishesMode == AllWishesMode.Disabled) { WishesMode = AllWishesMode.Adjusted; LastWishesModeChange = AllWishesMode.Adjusted; LastWishesModeChangeRealtime = Time.realtimeSinceStartup; ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"AutoAcceptAllAvailable=true also enabled WishesMode=Adjusted (required coupling)"); } QuestModToast.Show("Auto-Accept Available also enabled Adjusted mode", new Color(0.7f, 0.85f, 1f), 4f); } SyncToSaveData(); } public static string ExportSaveDataToJson() { if (Instance?.SaveData == null) { return null; } return JsonConvert.SerializeObject((object)Instance.SaveData, (Formatting)1); } public static void ImportSaveDataFromJson(string json) { if (string.IsNullOrEmpty(json)) { throw new ArgumentException("empty json"); } if ((Object)(object)Instance == (Object)null) { throw new InvalidOperationException("plugin not initialized"); } QuestModSaveData questModSaveData = JsonConvert.DeserializeObject(json); if (questModSaveData == null) { throw new ArgumentException("parsed json was null"); } QuestModSaveData saveData = Instance.SaveData; questModSaveData.QuestModInitialized = saveData?.QuestModInitialized ?? false; questModSaveData.OverrideSafetyForThisSave = saveData?.OverrideSafetyForThisSave ?? false; if (questModSaveData.AllWishesMode < AllWishesMode.Disabled || questModSaveData.AllWishesMode > AllWishesMode.Adjusted) { throw new ArgumentException($"invalid AllWishesMode {questModSaveData.AllWishesMode}"); } if (questModSaveData.QuestPolicies != null && questModSaveData.QuestPolicies.Count > 5000) { throw new ArgumentException($"QuestPolicies too large ({questModSaveData.QuestPolicies.Count} > {5000})"); } if (questModSaveData.InjectedQuests != null && questModSaveData.InjectedQuests.Count > 5000) { throw new ArgumentException($"InjectedQuests too large ({questModSaveData.InjectedQuests.Count} > {5000})"); } if (questModSaveData.CompletedQuests != null && questModSaveData.CompletedQuests.Count > 5000) { throw new ArgumentException($"CompletedQuests too large ({questModSaveData.CompletedQuests.Count} > {5000})"); } if (questModSaveData.QuestTargetOverrides != null && questModSaveData.QuestTargetOverrides.Count > 5000) { throw new ArgumentException($"QuestTargetOverrides too large ({questModSaveData.QuestTargetOverrides.Count} > {5000})"); } if (questModSaveData.WishLocationOverrides != null && questModSaveData.WishLocationOverrides.Count > 5000) { throw new ArgumentException($"WishLocationOverrides too large ({questModSaveData.WishLocationOverrides.Count} > {5000})"); } Instance.SaveData = questModSaveData; Log.LogInfo((object)"ImportSaveDataFromJson: applied imported save state (safety gate preserved from live save)"); } internal static void SyncFromSaveData() { if ((Object)(object)Instance == (Object)null) { return; } QuestModSaveData saveData = Instance.SaveData; if (saveData != null) { WishesMode = saveData.AllWishesMode; AllQuestsAccepted = saveData.AllQuestsAccepted; AutoAcceptAllAvailable = saveData.AutoAcceptAllAvailable; if (saveData.Prereqs == null) { saveData.Prereqs = new GranularPrereqs(); } LogDebugInfo($"SyncFromSave: WishesMode={WishesMode}, Accepted={AllQuestsAccepted}, AutoAcceptAvail={AutoAcceptAllAvailable}"); } } private static void SyncToSaveData() { if (!((Object)(object)Instance == (Object)null)) { QuestModSaveData saveData = Instance.SaveData; if (saveData != null) { saveData.AllWishesMode = WishesMode; saveData.AllQuestsAvailable = WishesMode != AllWishesMode.Disabled; saveData.AllQuestsAccepted = AllQuestsAccepted; saveData.AutoAcceptAllAvailable = AutoAcceptAllAvailable; LogDebugInfo($"SyncToSave: WishesMode={WishesMode}, Accepted={AllQuestsAccepted}, AutoAcceptAvail={AutoAcceptAllAvailable}"); } } } internal static void LogDebugInfo(string message) { if (DebugLogging.Value) { Log.LogInfo((object)message); } } internal static bool IsQuestDiscovered(string questName) { if (!OnlyDiscoveredQuests.Value) { return true; } if (PlayerData.instance == null) { return false; } return QuestDataAccess.GetRuntimeData()?.Contains(questName) ?? false; } private void Awake() { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Expected O, but got Unknown //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Expected O, but got Unknown //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Expected O, but got Unknown //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Expected O, but got Unknown //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_025a: Expected O, but got Unknown //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Expected O, but got Unknown //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Expected O, but got Unknown //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Expected O, but got Unknown //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_0333: Expected O, but got Unknown //IL_035e: Unknown result type (might be due to invalid IL or missing references) //IL_0368: Expected O, but got Unknown //IL_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03af: Expected O, but got Unknown //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03e4: Expected O, but got Unknown //IL_040f: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Expected O, but got Unknown //IL_0444: Unknown result type (might be due to invalid IL or missing references) //IL_044e: Expected O, but got Unknown //IL_0479: Unknown result type (might be due to invalid IL or missing references) //IL_0483: Expected O, but got Unknown //IL_04de: Unknown result type (might be due to invalid IL or missing references) //IL_04e4: Expected O, but got Unknown if ((Object)(object)Instance != (Object)null && (Object)(object)Instance != (Object)(object)this) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"QuestMod: second Awake detected, destroying duplicate component"); Object.Destroy((Object)(object)this); return; } Instance = this; Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)"QuestMod initializing..."); SceneManager.activeSceneChanged += OnActiveSceneChanged; EnableCompletionOverrides = ((BaseUnityPlugin)this).Config.Bind("General", "EnableCompletionOverrides", true, new ConfigDescription("Apply your custom quest target counts when loading a save. Disable to use the game's default target counts.", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); OnlyDiscoveredQuests = ((BaseUnityPlugin)this).Config.Bind("General", "OnlyDiscoveredQuests", true, new ConfigDescription("Only show and modify quests you have already discovered in your current save.", (AcceptableValueBase)null, new object[1] { new { Order = 2 } })); QuestItemInvincible = ((BaseUnityPlugin)this).Config.Bind("General", "QuestItemInvincible", false, new ConfigDescription("Prevent delivery quest items from being destroyed by enemy attacks.", (AcceptableValueBase)null, new object[1] { new { Order = 3 } })); ShowQuestDisplayNames = ((BaseUnityPlugin)this).Config.Bind("GUI", "ShowQuestDisplayNames", true, new ConfigDescription("Show in-game display names (e.g. 'Flexile Spines') instead of internal asset names (e.g. 'Brolly Get').", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); GuiToggleKey = ((BaseUnityPlugin)this).Config.Bind("GUI", "GuiToggleKey", new KeyboardShortcut((KeyCode)290, Array.Empty()), new ConfigDescription("Key to open/close the Quest Manager window.", (AcceptableValueBase)null, new object[1] { new { Order = 2 } })); GuiScale = ((BaseUnityPlugin)this).Config.Bind("GUI", "GuiScale", 0f, new ConfigDescription("GUI scale override. 0 = auto-detect from system DPI. Set manually (e.g. 1.5) to override.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), new object[1] { new { Order = 3 } })); GuaranteedSilverBells = ((BaseUnityPlugin)this).Config.Bind("Quest Tweaks", "GuaranteedSilverBells", false, new ConfigDescription("Make every bell drop a Silver Bell (overrides the 75/25 normal/silver split for the Silver Bells quest).", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); BypassWishboardLock = ((BaseUnityPlugin)this).Config.Bind("Quest Tweaks", "BypassWishboardLock", false, new ConfigDescription("Force-activate the Bonetown wishboard without defeating the Bell Beast. Runtime-only override on the ActivateIfPlayerdataTrue gate, does NOT modify PlayerData, so disabling the toggle restores the vanilla lock immediately.", (AcceptableValueBase)null, new object[1] { new { Order = 2 } })); EnableSilkSoulTab = ((BaseUnityPlugin)this).Config.Bind("Features", "EnableSilkSoulTab", true, new ConfigDescription("Show the Silk & Soul tab in the Quest Manager GUI.", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); EnableCustomRequirements = ((BaseUnityPlugin)this).Config.Bind("Custom Requirements", "Enable", true, new ConfigDescription("Enable the custom requirements rules (presets + per-quest extra conditions). See docs/CustomRequirements.md and BepInEx/config/QuestMod/QuestRequirements.user.json.", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); ActivePreset = ((BaseUnityPlugin)this).Config.Bind("Custom Requirements", "ActivePreset", "vanilla", new ConfigDescription("Name of the preset to apply on save load. Built-ins: 'vanilla', 'farmable-only', 'farmable-quarter', 'quick'.", (AcceptableValueBase)null, new object[1] { new { Order = 2 } })); EnableWishLocationReassignment = ((BaseUnityPlugin)this).Config.Bind("Features", "EnableWishLocationReassignment", false, new ConfigDescription("[STRETCH - NOT YET IMPLEMENTED] Allow quests to be accepted from non-default sources (NPC wishes from wishboards; wishboard wishes from world locations). This flag is a placeholder; no behavior is wired up yet.", (AcceptableValueBase)null, new object[1] { new { Order = 99 } })); EnableFullRemoteComplete = ((BaseUnityPlugin)this).Config.Bind("Features", "EnableFullRemoteComplete", false, new ConfigDescription("When ON, the per quest Complete button in the Quests tab routes through QuestManager so vanilla side effects (item deductions, rewards, dialogue flags) fire. When OFF (default), the button only flips QuestData flags (legacy behaviour, useful for dev/debug). in TODO.md.", (AcceptableValueBase)null, new object[1] { new { Order = 7 } })); GourmandStopDecay = ((BaseUnityPlugin)this).Config.Bind("Delivery", "GourmandStopDecay", false, new ConfigDescription("Stop the Courier's Rasher (Great Gourmand quest item) from decaying while carried.", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); GourmandDecaySeconds = ((BaseUnityPlugin)this).Config.Bind("Delivery", "GourmandDecaySeconds", 47f, new ConfigDescription("Seconds per segment before the Courier's Rasher loses one durability tick. Default is 47s.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 600f), new object[1] { new { Order = 3 } })); DebugLogging = ((BaseUnityPlugin)this).Config.Bind("Advanced", "DebugLogging", false, new ConfigDescription("Log detailed quest operations to BepInEx console. Enable when troubleshooting.", (AcceptableValueBase)null, new object[1] { new { Order = 1 } })); DevRemoveLimits = ((BaseUnityPlugin)this).Config.Bind("Advanced", "DevRemoveLimits", false, new ConfigDescription("Remove all count limits (min/max caps) in the Targets tab. Values outside normal ranges may break quest state.", (AcceptableValueBase)null, new object[1] { new { Order = 2 } })); DevForceOperations = ((BaseUnityPlugin)this).Config.Bind("Advanced", "DevForceOperations", false, new ConfigDescription("Show Force Accept ALL / Force Complete ALL buttons. These directly inject and modify quest state and can irreversibly break saves.", (AcceptableValueBase)null, new object[1] { new { Order = 3 } })); ShowPureWishesMode = ((BaseUnityPlugin)this).Config.Bind("Advanced", "ShowPureWishesMode", false, new ConfigDescription("Show the 'Pure' All Wishes option in the Tools tab. Pure bypasses chain gating + mutually-exclusive + edge-case patches -- raw chaos with soft-locks possible. Hidden by default; enable only if you want the option alongside Adjusted.", (AcceptableValueBase)null, new object[1] { new { Order = 4 } })); QuestRegistry.Load(); QuestRequirements.Load(); QuestRequirements.SetActivePreset(ActivePreset.Value); ActivePreset.SettingChanged += delegate { QuestRequirements.SetActivePreset(ActivePreset.Value); }; QuestStateHooks.Initialize(); QuestAcceptance.Initialize(); QuestCompletionOverrides.Initialize(); Harmony val = new Harmony("com.silkmod.questmod"); try { val.PatchAll(typeof(QuestModPlugin).Assembly); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Harmony.PatchAll failed: " + ex.Message + "\n" + ex.StackTrace)); } try { val.UnpatchSelf(); } catch (Exception ex2) { ManualLogSource log2 = Log; if (log2 != null) { log2.LogError((object)("Harmony.UnpatchSelf after PatchAll failure also threw: " + ex2.Message)); } } } TryStep("QuestItemProtection", QuestItemProtection.Initialize); TryStep("SilverBellPatch", SilverBellPatch.Initialize); TryStep("WishboardSceneSweep", WishboardSceneSweep.Initialize); TryStep("WishwallFsmPatch", WishwallFsmPatch.Initialize); TryStep("GourmandTimerPatch", GourmandTimerPatch.Initialize); TryStep("EdgeBugPinstressNeedleStrike", EdgeBugPinstressNeedleStrike.Initialize); TryStep("EdgeBugDoubleTrobbio", EdgeBugDoubleTrobbio.Initialize); TryStep("QuestGUI", delegate { ((Component)this).gameObject.AddComponent(); }); Log.LogInfo((object)$"QuestMod initialized - WishesMode={WishesMode}, AllQuestsAccepted={AllQuestsAccepted}"); Log.LogInfo((object)" F9 = Quest Manager GUI"); } private void OnActiveSceneChanged(Scene from, Scene to) { if (!(((Scene)(ref to)).name != "Menu_Title")) { WishesMode = AllWishesMode.Disabled; AllQuestsAccepted = false; AutoAcceptAllAvailable = false; LastWishesModeChange = AllWishesMode.Disabled; LastWishesModeChangeRealtime = -1f; _saveData = new QuestModSaveData(); SilkSoulOverrides.Reset(); QuestCompletionOverrides.ResetSlotState(); ManualLogSource log = Log; if (log != null) { log.LogInfo((object)"Returned to title, mode reset"); } } } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { SceneManager.activeSceneChanged -= OnActiveSceneChanged; } } private static void TryStep(string name, Action step) { try { step(); } catch (Exception ex) { ManualLogSource log = Log; if (log != null) { log.LogError((object)("Initialize " + name + " failed: " + ex.Message + "\n" + ex.StackTrace)); } } } } internal static class QuestModConstants { public const string PluginGuid = "com.silkmod.questmod"; public const string PluginName = "QuestMod"; public const string HarmonyId_GourmandTimer = "com.silkmod.questmod.gourmand-timer"; public const string HarmonyId_ItemInvincible = "com.silkmod.questmod.item-invincible"; public const string EmbeddedQuestCapabilitiesJson = "QuestMod.Data.QuestCapabilities.json"; public const string EmbeddedQuestRequirementsJson = "QuestMod.Data.QuestRequirements.json"; public const int GuiWindowId = 12345; public const float ConfirmArmWindow = 4f; public const float ModeChangeHintDurationSec = 12f; public static bool IsTransientMenuScene(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { return true; } if (sceneName.StartsWith("Pre_Menu_", StringComparison.Ordinal)) { return true; } switch (sceneName) { default: return sceneName == "PermaDeath_Unlocked"; case "Menu_Title": case "Quit_To_Menu": case "Opening_Sequence": case "PermaDeath": return true; } } } internal static class ReflectionCache { private static readonly Dictionary typeCache = new Dictionary(); private static readonly Dictionary<(Type, string), FieldInfo> fieldCache = new Dictionary<(Type, string), FieldInfo>(); private static readonly Dictionary<(Type, string), PropertyInfo> propCache = new Dictionary<(Type, string), PropertyInfo>(); private static readonly Dictionary<(Type, string), MemberInfo> memberCache = new Dictionary<(Type, string), MemberInfo>(); private static readonly HashSet warned = new HashSet(); internal static Type GetType(string name) { if (typeCache.TryGetValue(name, out Type value)) { return value; } Type type = AccessTools.TypeByName(name); if (type == null) { WarnOnce("Type '" + name + "' not found"); } typeCache[name] = type; return type; } internal static FieldInfo GetField(Type type, string name) { if (type == null) { return null; } (Type, string) key = (type, name); if (fieldCache.TryGetValue(key, out FieldInfo value)) { return value; } FieldInfo fieldInfo = AccessTools.Field(type, name); if (fieldInfo == null) { WarnOnce("Field '" + name + "' not found on " + type.Name); } fieldCache[key] = fieldInfo; return fieldInfo; } internal static FieldInfo GetField(string typeName, string fieldName) { Type type = GetType(typeName); if (!(type != null)) { return null; } return GetField(type, fieldName); } internal static PropertyInfo GetProperty(Type type, string name) { if (type == null) { return null; } (Type, string) key = (type, name); if (propCache.TryGetValue(key, out PropertyInfo value)) { return value; } PropertyInfo propertyInfo = AccessTools.Property(type, name); if (propertyInfo == null) { WarnOnce("Property '" + name + "' not found on " + type.Name); } propCache[key] = propertyInfo; return propertyInfo; } internal static PropertyInfo GetProperty(string typeName, string propName) { Type type = GetType(typeName); if (!(type != null)) { return null; } return GetProperty(type, propName); } internal static MemberInfo FindMember(Type type, string name) { if (type == null) { return null; } (Type, string) key = (type, name); if (memberCache.TryGetValue(key, out MemberInfo value)) { return value; } MemberInfo memberInfo = (MemberInfo)(((object)AccessTools.Field(type, name)) ?? ((object)AccessTools.Property(type, name))); if (memberInfo == null) { WarnOnce("Member '" + name + "' not found on " + type.Name); } memberCache[key] = memberInfo; return memberInfo; } internal static T Read(object obj, FieldInfo field, T fallback = default(T)) { if (obj == null || field == null) { return fallback; } object value = field.GetValue(obj); if (value is T) { return (T)value; } return fallback; } internal static T Read(object obj, PropertyInfo prop, T fallback = default(T)) { if (obj == null || prop == null) { return fallback; } object value = prop.GetValue(obj); if (value is T) { return (T)value; } return fallback; } internal static object ReadMember(MemberInfo member, object obj) { if (member == null || obj == null) { return null; } if (member is PropertyInfo propertyInfo) { return propertyInfo.GetValue(obj); } if (member is FieldInfo fieldInfo) { return fieldInfo.GetValue(obj); } return null; } internal static void WriteMember(MemberInfo member, object obj, object value) { if (!(member == null) && obj != null) { if (member is PropertyInfo propertyInfo) { propertyInfo.SetValue(obj, value); } else if (member is FieldInfo fieldInfo) { fieldInfo.SetValue(obj, value); } } } internal static void Write(object obj, FieldInfo field, object value) { if (obj != null && !(field == null)) { field.SetValue(obj, value); } } internal static void WriteToArray(Array array, int index, object element, FieldInfo field, object value) { if (array != null && !(field == null)) { field.SetValue(element, value); if (array.GetType().GetElementType().IsValueType) { array.SetValue(element, index); } } } internal static string MemberTag(MemberInfo m) { if (m is PropertyInfo) { return "prop"; } if (m is FieldInfo) { return "field"; } return "NOT FOUND"; } private static void WarnOnce(string msg) { if (!warned.Contains(msg)) { warned.Add(msg); QuestModPlugin.Log.LogWarning((object)("ReflectionCache: " + msg)); } } } }