Decompiled source of QuestMod v2.0.1
plugins/QuestMod/QuestMod.dll
Decompiled 4 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using 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.0.1.0")] [assembly: AssemblyInformationalVersion("2.0.1+317b6c0983945f8c8f04147f79ce5e303c569ddb")] [assembly: AssemblyProduct("QuestMod")] [assembly: AssemblyTitle("QuestMod")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.1.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 (cluster P)"); 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<FullQuestBase>(); 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; foreach (string item in QuestPolicyStore.AutoAcceptNames()) { if (string.IsNullOrEmpty(item) || QuestRegistry.ExcludedQuests.Contains(item) || !IsActuallyAvailable(item)) { continue; } if (runtimeData.Contains(item)) { object qd = runtimeData[item]; if (QuestDataAccess.IsAccepted(qd) || QuestDataAccess.IsCompleted(qd)) { continue; } } AcceptQuest(item); num++; } if (num > 0) { QuestModPlugin.Log.LogInfo((object)$"Auto-accepted {num} flagged quests"); QuestModToast.Show($"Auto-accepted {num} quest" + ((num == 1) ? "" : "s")); } } 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 (cluster P)"); } else { if (PlayerData.instance == null) { return; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); if (runtimeData == null) { return; } bool blackThreadWorld = PlayerData.instance.blackThreadWorld; FullQuestBase[] array = Resources.FindObjectsOfTypeAll<FullQuestBase>(); 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<QuestInfo> GetQuestList() { List<QuestInfo> list = new List<QuestInfo>(); if (PlayerData.instance == null) { return list; } IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); HashSet<string> hashSet = new HashSet<string>(); 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<FullQuestBase>(); 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) { 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 (!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<string>()); } private static bool RemoteComplete(string name, HashSet<string> visited) { if (visited.Contains(name)) { return false; } visited.Add(name); if (!QuestModPlugin.AreDestructiveFeaturesAllowed) { QuestModPlugin.Log.LogInfo((object)("RemoteComplete(" + name + "): skipped -- legacy save, safety override not set (cluster P)")); return false; } 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; } string name = counter.GetType().Name; if (name.IndexOf("Item", StringComparison.OrdinalIgnoreCase) < 0) { return name.IndexOf("Collectable", StringComparison.OrdinalIgnoreCase) >= 0; } return true; } 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<string> 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)) { RemoteComplete(text, visited); } } } } catch (Exception ex) { QuestModPlugin.Log.LogWarning((object)("RemoteComplete cascade threw: " + ex.Message)); } } public static void UnacceptQuest(string name) { 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 (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<object> list = new List<object>(); foreach (object key in rt.Keys) { list.Add(key); } foreach (string item in list) { if (respectGates && !IsActuallyAvailable(item, out string reason)) { QuestModPlugin.LogDebugInfo(" SKIP [" + item + "]: " + reason); num2++; continue; } object qd = rt[item]; bool flag = QuestDataAccess.IsCompleted(qd); qd = QuestDataAccess.SetFields(qd, seen: true, accepted: true, complete || flag, complete || flag); 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<ChainInfo> GetChainList() { List<ChainInfo> list = new List<ChainInfo>(); IDictionary runtimeData = QuestDataAccess.GetRuntimeData(); foreach (KeyValuePair<string, string[]> 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 (!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 (!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<QuestTargetInfo> Targets; } public static class QuestCompletionOverrides { private static FieldInfo? targetsBackingField; private static FieldInfo? countField; private static Dictionary<string, int[]> originalCounts = new Dictionary<string, int[]>(); private static Dictionary<string, int[]> overrideCounts = new Dictionary<string, int[]>(); 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<QuestOverrideInfo> GetQuestsByCategory(string category) { List<QuestOverrideInfo> allQuestsWithTargets = GetAllQuestsWithTargets(); List<QuestOverrideInfo> list = new List<QuestOverrideInfo>(); 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 CacheQuests() { //IL_0083: Unknown result type (might be due to invalid IL or missing references) if (!IsInitialized) { return; } cachedQuests = Resources.FindObjectsOfTypeAll<FullQuestBase>(); QuestModPlugin.Log.LogInfo((object)$"Cached {cachedQuests.Length} FullQuestBase objects"); originalCounts.Clear(); FullQuestBase[] array = cachedQuests; foreach (FullQuestBase obj in array) { string key = ((Object)obj).name ?? ""; IReadOnlyList<QuestTarget> targets = obj.Targets; if (targets != null && targets.Count != 0) { int[] array2 = new int[targets.Count]; for (int j = 0; j < targets.Count; j++) { array2[j] = targets[j].Count; } originalCounts[key] = array2; } } } public static List<QuestOverrideInfo> 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<QuestOverrideInfo> list = new List<QuestOverrideInfo>(); 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<QuestTarget> 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<QuestTargetInfo>(); 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<string> list = new List<string>(); 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<string>(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<string, int> 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<QuestTarget> 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 (!done && QuestRegistry.SequentialQuests.Contains(questName)) { string[] array = QuestRegistry.ChecklistQuests[questName]; for (int i = index + 1; i < array.Length; i++) { SetTargetCount(questName, i, 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<string, float> pointOverrides = new Dictionary<string, float>(); 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 (declaredField2.FieldType.Name.Contains("Quest") || declaredField2.FieldType.Name.Contains("FullQuestBase")) { fieldInfo = declaredField2; } else if (declaredField2.FieldType == typeof(float) || declaredField2.FieldType == typeof(int)) { fieldInfo2 = declaredField2; } } 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<PointEntry> GetPointEntries() { List<PointEntry> list = new List<PointEntry>(); 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; } 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 = ((f_entryValue.FieldType == typeof(float)) ? ((object)value) : ((object)(int)value)); 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; } 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() { 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 HashSet<string> InjectedQuests { get; set; } = new HashSet<string>(); public HashSet<string> CompletedQuests { get; set; } = new HashSet<string>(); public Dictionary<string, int> QuestTargetOverrides { get; set; } = new Dictionary<string, int>(); public bool AllQuestsAvailable { get; set; } public AllWishesMode AllWishesMode { get; set; } public bool AllQuestsAccepted { get; set; } public Dictionary<string, QuestPolicy> QuestPolicies { get; set; } = new Dictionary<string, QuestPolicy>(); public GranularPrereqs Prereqs { get; set; } = new GranularPrereqs(); public Dictionary<string, string> WishLocationOverrides { get; set; } = new Dictionary<string, string>(); public HashSet<string> WishLocationTriggersFired { get; set; } = new HashSet<string>(); 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 <AutoAcceptNames>d__8 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable { private int <>1__state; private string <>2__current; private int <>l__initialThreadId; private Dictionary<string, QuestPolicy>.Enumerator <>7__wrap1; string IEnumerator<string>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <AutoAcceptNames>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<string, QuestPolicy>.Enumerator); <>1__state = -2; } private bool MoveNext() { try { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; Dictionary<string, QuestPolicy> 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<string, QuestPolicy> 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<string, QuestPolicy>.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<string> IEnumerable<string>.GetEnumerator() { if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId) { <>1__state = 0; return this; } return new <AutoAcceptNames>d__8(0); } [DebuggerHidden] IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable<string>)this).GetEnumerator(); } } public static Dictionary<string, QuestPolicy> Map { get { QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData == null) { return null; } if (questModSaveData.QuestPolicies == null) { questModSaveData.QuestPolicies = new Dictionary<string, QuestPolicy>(); } return questModSaveData.QuestPolicies; } } public static QuestPolicy Get(string questName) { Dictionary<string, QuestPolicy> 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<string, QuestPolicy> 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(<AutoAcceptNames>d__8))] public static IEnumerable<string> AutoAcceptNames() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <AutoAcceptNames>d__8(-2); } } public static class QuestRegistry { public static HashSet<string> ExcludedQuests { get; private set; } = new HashSet<string>(); public static Dictionary<string, string[]> ChainRegistry { get; private set; } = new Dictionary<string, string[]>(); public static Dictionary<string, string> ChainDisplayNames { get; private set; } = new Dictionary<string, string>(); public static HashSet<string> ChainStepNames { get; private set; } = new HashSet<string>(); public static Dictionary<string, string> MutuallyExclusiveQuests { get; private set; } = new Dictionary<string, string>(); public static Dictionary<string, string> SharedTargetQuests { get; private set; } = new Dictionary<string, string>(); public static Dictionary<string, string> DisplayNames { get; private set; } = new Dictionary<string, string>(); public static Dictionary<string, string> QuestCategories { get; private set; } = new Dictionary<string, string>(); public static string[] Categories { get; private set; } = Array.Empty<string>(); public static Dictionary<string, int[]> MaxCaps { get; private set; } = new Dictionary<string, int[]>(); public static Dictionary<string, string[]> ChecklistQuests { get; private set; } = new Dictionary<string, string[]>(); public static HashSet<string> SequentialQuests { get; private set; } = new HashSet<string>(); public static Dictionary<string, string> SequentialStagePdPatterns { get; private set; } = new Dictionary<string, string>(); public static HashSet<string> FarmableExcluded { get; private set; } = new HashSet<string>(); public static int DefaultThreshold { get; private set; } = 17; public static Dictionary<string, float> SilkSoulPointValues { get; private set; } = new Dictionary<string, float>(); public static string[] SilkSoulRequiredQuests { get; private set; } = Array.Empty<string>(); 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<string>((IEnumerable<JToken>)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<string>((IEnumerable<JToken>)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<string, JToken> item in val) { JToken value = item.Value; string text = ((value != null) ? Extensions.Value<string>((IEnumerable<JToken>)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<string, JToken> item in val) { JToken value = item.Value; string text = ((value != null) ? Extensions.Value<string>((IEnumerable<JToken>)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<int>((IEnumerable<JToken>)val2); } JToken obj2 = val["requiredQuests"]; JArray val3 = (JArray)(object)((obj2 is JArray) ? obj2 : null); if (val3 != null) { List<string> list = new List<string>(); foreach (JToken item in val3) { string text = Extensions.Value<string>((IEnumerable<JToken>)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<string, JToken> item2 in val4) { SilkSoulPointValues[item2.Key] = Extensions.Value<float>((IEnumerable<JToken>)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<string, JToken> item in val) { JToken value = item.Value; JArray val2 = (JArray)(object)((value is JArray) ? value : null); if (val2 == null) { continue; } List<int> list = new List<int>(); foreach (JToken item2 in val2) { list.Add(Extensions.Value<int>((IEnumerable<JToken>)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<string, JToken> item in val) { JToken value = item.Value; JArray val2 = (JArray)(object)((value is JArray) ? value : null); if (val2 == null) { continue; } List<string> list = new List<string>(); foreach (JToken item2 in val2) { string text = Extensions.Value<string>((IEnumerable<JToken>)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<string>((IEnumerable<JToken>)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<string, JToken> item4 in val4) { if (!item4.Key.StartsWith("_")) { JToken value2 = item4.Value; string value3 = ((value2 != null) ? Extensions.Value<string>((IEnumerable<JToken>)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<string, JToken> 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<string>((IEnumerable<JToken>)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<string> list = new List<string>(); foreach (JToken item2 in val3) { string text2 = Extensions.Value<string>((IEnumerable<JToken>)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<string> list = new List<string>(); foreach (KeyValuePair<string, JToken> 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<string, JToken> 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<string, JToken> 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<string>((IEnumerable<JToken>)obj) : null); if (text != null) { DisplayNames[questName] = text; } } } } public static class QuestRequirements { public sealed class Preset { public string Name = ""; public string Description = ""; public List<Rule> Rules = new List<Rule>(); } 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<string>? QuestId; public HashSet<string>? Category; public HashSet<string>? AnyTag; public HashSet<string>? 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<int, int> TargetCounts = new Dictionary<int, int>(); public List<ExtraCondition> ExtraConditions = new List<ExtraCondition>(); public List<ExtraCondition> AvailableConditions = new List<ExtraCondition>(); } public sealed class ExtraCondition { public string Kind = ""; public string Field = ""; public string Op = "=="; public JToken? Value; public string Quest = ""; public HashSet<string>? 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<string, HashSet<string>> Tags { get; private set; } = new Dictionary<string, HashSet<string>>(); public static HashSet<string> PlayerDataWhitelist { get; private set; } = new HashSet<string>(); public static Dictionary<string, Preset> Presets { get; private set; } = new Dictionary<string, Preset>(StringComparer.OrdinalIgnoreCase); public static Dictionary<string, PerQuestEntry> PerQuest { get; private set; } = new Dictionary<string, PerQuestEntry>(); public static bool IsLoaded { 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<string>(StringComparer.OrdinalIgnoreCase); } if (Tags[questName].Add(tag.Trim())) { SaveTagsToUserOverlay(); } } } public static void RemoveTag(string questName, string tag) { if (Tags.TryGetValue(questName, out HashSet<string> value) && value.Remove(tag)) { if (value.Count == 0) { Tags.Remove(questName); } 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<string, HashSet<string>> 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<JsonConverter>()); } 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<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (JToken item2 in val) { if ((int)item2.Type == 8) { hashSet.Add(Extensions.Value<string>((IEnumerable<JToken>)item2)); } } Tags[item.Name] = hashSet; } 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<JsonConverter>())); } 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<string> GetAllUniqueTags() { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<string, HashSet<string>> 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<string, HashSet<string>> 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<JsonConverter>())); 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Invalid comparison between Unknown and I4 //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Invalid comparison between Unknown and I4 Tags.Clear(); 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<string>((IEnumerable<JToken>)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<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (JToken item3 in val3) { if ((int)item3.Type == 8) { hashSet.Add(Extensions.Value<string>((IEnumerable<JToken>)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<string>((IEnumerable<JToken>)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<string>((IEnumerable<JToken>)obj3) : null) ?? "ceil"; Rule rule2 = rule; JToken obj4 = obj["set"]; if (obj4 != null && (int)obj4.Type == 6) { rule2.Set = Extensions.Value<int>((IEnumerable<JToken>)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<float>((IEnumerable<JToken>)obj["scale"]); goto IL_00d9; IL_00d9: JToken obj7 = obj["min"]; if (obj7 != null && (int)obj7.Type == 6) { rule2.Min = Extensions.Value<int>((IEnumerable<JToken>)obj["min"]); } JToken obj8 = obj["max"]; if (obj8 != null && (int)obj8.Type == 6) { rule2.Max = Extensions.Value<int>((IEnumerable<JToken>)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<string>? 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<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); { foreach (JToken item in val) { if ((int)item.Type == 8) { hashSet.Add(Extensions.Value<string>((IEnumerable<JToken>)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<int>((IEnumerable<JToken>)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<string>((IEnumerable<JToken>)obj) : null) ?? ""; JToken obj2 = co["field"]; extraCondition.Field = ((obj2 != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj2) : null) ?? ""; JToken obj3 = co["op"]; extraCondition.Op = ((obj3 != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj3) : null) ?? "=="; extraCondition.Value = co["value"]; JToken obj4 = co["quest"]; extraCondition.Quest = ((obj4 != null) ? Extensions.Value<string>((IEnumerable<JToken>)obj4) : null) ?? ""; extraCondition.AnyTag = ReadStringSet(co["anyTag"]); JToken obj5 = co["count"]; extraCondition.Count = ((obj5 != null) ? Extensions.Value<int>((IEnumerable<JToken>)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<string> 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<string> 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"; } ActivePresetName = name; QuestModSaveData questModSaveData = QuestModPlugin.Instance?.SaveData; if (questModSaveData != null && questModSaveData.ActiveDslPreset != name) { questModSaveData.ActiveDslPreset = name; } } public static string[] GetPresetNames() { List<string> list = new List<string>(Presets.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); return list.ToArray(); } public static void ApplyActivePreset() { if (!IsLoaded || !QuestCompletionOverrides.IsInitialized) { return; } Dictionary<string, int> dictionary = (QuestModPlugin.Instance?.SaveData)?.QuestTargetOverrides ?? new Dictionary<string, int>(); 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<string, HashSet<string>> 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_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: 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 && c.Value != null) { bool flag2 = (int)c.Value.Type == 9 && Extensions.Value<bool>((IEnumerable<JToken>)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<double>() : 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 Bypa