Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of QuestMod v2.1.2
plugins/QuestMod/QuestMod.dll
Decompiled a week 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.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<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; HashSet<string> seen = new HashSet<string>(); foreach (string item in QuestPolicyStore.AutoAcceptNames()) { if (TryAutoAcceptOne(item, runtimeData, seen)) { num++; } } if (QuestModPlugin.AutoAcceptAllAvailable && QuestModPlugin.WishesMode != 0) { FullQuestBase[] array = Resources.FindObjectsOfTypeAll<FullQuestBase>(); 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<string> 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<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) { 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<string>()); } private static bool RemoteComplete(string name, HashSet<string> 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<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) && 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<object> list = new List<object>(); 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<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 (!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<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 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<FullQuestBase>(); 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<QuestTarget> 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<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 (!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<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 (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<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; } 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<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 bool AutoAcceptAllAvailable { 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 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<string>(StringComparer.OrdinalIgnoreCase); } if (Tags[questName].Add(tag.Trim())) { TagsVersion++; 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); } 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<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; } 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<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_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<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"; } 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<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)("QuestRequireme