Decompiled source of QuestMod v2.0.1

plugins/QuestMod/QuestMod.dll

Decompiled 4 hours ago
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