Decompiled source of DemigodTrees v0.7.1

plugins/NorseDemigodsTree.dll

Decompiled an hour ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Jotunn.Managers;
using Microsoft.CodeAnalysis;
using NorseDemigods;
using NorseDemigods.Abilities;
using NorseDemigods.Attributes;
using NorseDemigods.StatusEffects;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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 OblitusEs.DemigodTree
{
	internal static class AbilityLocks
	{
		public static NodeDef LockedNode(DemigodAbility ability)
		{
			if (ability == null || !Nd.IsLocal(ability.Owner))
			{
				return null;
			}
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			NodeDef nodeDef = godTree?.ForAbility(((object)ability).GetType().Name);
			if (nodeDef == null || nodeDef.Starter || !nodeDef.Purchasable)
			{
				return null;
			}
			return (Progress.RankOf(godTree.God, nodeDef.Id) > 0) ? null : nodeDef;
		}
	}
	[HarmonyPatch(typeof(DemigodAbility), "CanExecute")]
	internal static class DemigodAbility_CanExecute_Lock
	{
		private static float lastMessage;

		private static bool Prefix(DemigodAbility __instance, bool __0, ref bool __result)
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			NodeDef nodeDef = AbilityLocks.LockedNode(__instance);
			if (nodeDef == null)
			{
				return true;
			}
			__result = false;
			if (__0 && Time.time - lastMessage > 1.5f)
			{
				lastMessage = Time.time;
				Nd.Message($"{nodeDef.Name} is locked. Unlock it in the demigod tree ({Plugin.ToggleKey.Value}).");
			}
			return false;
		}
	}
	internal static class AutoScale
	{
		private enum Kind
		{
			Passive,
			Power,
			Duration
		}

		private sealed class Entry
		{
			public string Class;

			public Kind Kind;

			public float Neutral;

			public bool IsInt;
		}

		private static readonly Dictionary<string, Entry> entries = new Dictionary<string, Entry>();

		private static readonly HashSet<string> powerClasses = new HashSet<string>();

		private static readonly HashSet<string> durationClasses = new HashSet<string>();

		private static readonly Dictionary<string, float> cooldowns = new Dictionary<string, float>();

		private static string previewClass;

		private static int previewRank = -1;

		private static readonly Regex PassiveSkip = new Regex("Cooldown|Interval|Delay|Period|WarmUp|Time|Threshold|Trigger|Radius|Range|Distance|Penalty|Divisor|List|Prefab|CustomData|DataVersion|BUFF_|FIRE_|Forbid|Prevent|Display|Replace|Enable|Play|BoltSpeed|Seek", RegexOptions.Compiled);

		private static readonly Regex PowerMatch = new Regex("Damage|Heal|HealthAt|HealthRestored|HealthRegeneration|HealthPointsPer|^Bonus|StaminaRegeneration|CultivatingSpeed|TeleportRangeAt", RegexOptions.Compiled);

		private static readonly Regex PowerSkip = new Regex("Delay|Radius|Range|Interval|Start|End|Time|Peak|Threshold|Resource|Trees|Ores|Mine|Taken|Trigger|Velocity|Chance|Fraction|Ratio|Cap|Reset|Coefficient|Capsule|Infused|Loss|Limit|MinHealth|PerStar|Fed|Reflect|Duration", RegexOptions.Compiled);

		private static readonly Regex DurationMatch = new Regex("Duration", RegexOptions.Compiled);

		private static readonly Regex DurationSkip = new Regex("Fx|Attack|Rest|Min|Max|Phase|Pull|Peak|Infused", RegexOptions.Compiled);

		private static readonly FieldInfo hitTier = AccessTools.Field(typeof(HitData), "m_toolTier");

		private static readonly FieldInfo sharedTier = AccessTools.Field(typeof(SharedData), "m_toolTier");

		private static readonly FieldInfo chop = AccessTools.Field(typeof(DamageTypes), "m_chop");

		private static readonly FieldInfo pickaxe = AccessTools.Field(typeof(DamageTypes), "m_pickaxe");

		private static float lastHarvestLog;

		private static float UpgradeStep => Plugin.AutoUpgradeStep.Value * Plugin.UpgradeFactor;

		private static float UpgradeCooldownCut => Plugin.AutoUpgradeCooldownCut.Value * Plugin.UpgradeFactor;

		private static float KeystoneBonus => Plugin.AutoKeystoneBonus.Value * Plugin.UpgradeFactor;

		private static float KeystoneCooldownCut => Plugin.AutoKeystoneCooldownCut.Value * Plugin.UpgradeFactor;

		private static float NeutralFor(string field, float value)
		{
			if (field.Contains("Bonus"))
			{
				return 0f;
			}
			if (field.Contains("Modifier") || field.Contains("Multiplier"))
			{
				return (Mathf.Abs(value) >= 10f) ? 100f : 1f;
			}
			return 0f;
		}

		public static void Install(Harmony harmony)
		{
			Assembly assembly = typeof(Demigod).Assembly;
			HashSet<string> hashSet = new HashSet<string>();
			HashSet<string> hashSet2 = new HashSet<string>();
			HashSet<string> hashSet3 = new HashSet<string>();
			foreach (GodTree value in TreeDefs.Trees.Values)
			{
				foreach (NodeDef node in value.Nodes)
				{
					if (node.Auto && node.Kind == NodeKind.Passive)
					{
						hashSet.Add(node.AttributeClass);
					}
					if (node.Auto && node.Kind == NodeKind.Ability)
					{
						hashSet2.Add(node.AbilityClass);
					}
					if (node.Kind == NodeKind.Harvest)
					{
						hashSet3.Add(node.AbilityClass);
					}
				}
			}
			int num = 0;
			foreach (string item in hashSet)
			{
				num += PatchConfigReads(harmony, assembly.GetType("NorseDemigods.Attributes." + item), item, isAbility: false);
			}
			foreach (string item2 in hashSet2)
			{
				num += PatchConfigReads(harmony, assembly.GetType("NorseDemigods.Abilities." + item2), item2, isAbility: true);
			}
			int num2 = 0;
			foreach (string item3 in hashSet3)
			{
				num2 += PatchHarvest(harmony, assembly.GetType("NorseDemigods.Abilities." + item3), item3);
			}
			int num3 = 0;
			foreach (string item4 in hashSet2.Concat(hashSet3).Distinct())
			{
				num3 += PatchToolTier(harmony, assembly.GetType("NorseDemigods.Abilities." + item4));
			}
			HashSet<string> scaled = new HashSet<string>(from e in entries.Values
				where e.Kind == Kind.Passive
				select e.Class);
			foreach (GodTree value2 in TreeDefs.Trees.Values)
			{
				foreach (NodeDef item5 in value2.Nodes.Where((NodeDef x) => x.Auto && x.Kind == NodeKind.Passive && !scaled.Contains(x.AttributeClass)))
				{
					item5.MaxRank = 1;
				}
			}
			foreach (GodTree value3 in TreeDefs.Trees.Values)
			{
				foreach (NodeDef item6 in value3.Nodes.Where((NodeDef x) => x.Auto && (x.Kind == NodeKind.Upgrade || x.Kind == NodeKind.Keystone)))
				{
					bool flag = powerClasses.Contains(item6.AbilityClass);
					bool flag2 = durationClasses.Contains(item6.AbilityClass);
					bool flag3 = Cooldown(item6.AbilityClass) > 0f;
					BoostMode boostMode = ((!flag) ? (flag2 ? BoostMode.Duration : BoostMode.Cooldown) : BoostMode.Power);
					if (item6.Kind == NodeKind.Upgrade)
					{
						item6.Mode = boostMode;
					}
					else
					{
						item6.Mode = ((flag3 && boostMode != BoostMode.Cooldown) ? BoostMode.Cooldown : boostMode);
					}
				}
			}
			Plugin.Log.LogInfo((object)$"[AutoScale] {entries.Count} ND values ranked across {hashSet.Count} passives and {hashSet2.Count} abilities ({num} methods); resource damage gated in {num2} methods; tool tier gated in {num3}.");
		}

		private static int PatchConfigReads(Harmony harmony, Type type, string cls, bool isAbility)
		{
			//IL_02fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0303: Expected O, but got Unknown
			//IL_023b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			if (type == null)
			{
				Plugin.Log.LogWarning((object)("[AutoScale] " + cls + " not found in NorseDemigods"));
				return 0;
			}
			FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (!fieldInfo.FieldType.IsGenericType || fieldInfo.FieldType.GetGenericTypeDefinition() != typeof(ConfigEntry<>))
				{
					continue;
				}
				Type type2 = fieldInfo.FieldType.GetGenericArguments()[0];
				if (type2 != typeof(float) && type2 != typeof(int))
				{
					continue;
				}
				string name = fieldInfo.Name;
				string key = cls + "." + name;
				if (isAbility)
				{
					if (name == "AbilityCooldown")
					{
						try
						{
							cooldowns[cls] = Convert.ToSingle(((ConfigEntryBase)fieldInfo.GetValue(null)).BoxedValue);
						}
						catch
						{
						}
					}
					else if (PowerMatch.IsMatch(name) && !PowerSkip.IsMatch(name))
					{
						entries[key] = new Entry
						{
							Class = cls,
							Kind = Kind.Power,
							IsInt = (type2 == typeof(int))
						};
						powerClasses.Add(cls);
					}
					else if (DurationMatch.IsMatch(name) && !DurationSkip.IsMatch(name))
					{
						entries[key] = new Entry
						{
							Class = cls,
							Kind = Kind.Duration,
							IsInt = (type2 == typeof(int))
						};
						durationClasses.Add(cls);
					}
				}
				else if (!PassiveSkip.IsMatch(name))
				{
					float value = 0f;
					try
					{
						value = Convert.ToSingle(((ConfigEntryBase)fieldInfo.GetValue(null)).BoxedValue);
					}
					catch
					{
					}
					entries[key] = new Entry
					{
						Class = cls,
						Kind = Kind.Passive,
						Neutral = NeutralFor(name, value),
						IsInt = (type2 == typeof(int))
					};
				}
			}
			if (isAbility && !cooldowns.ContainsKey(cls))
			{
				cooldowns[cls] = Nd.Cfg(type, "AbilityCooldown");
			}
			int num = 0;
			HarmonyMethod val = new HarmonyMethod(typeof(AutoScale), "ConfigTranspiler", (Type[])null);
			foreach (MethodBase item in AllMethods(type))
			{
				if (!(item.Name == "SetupConfigs") && ReadsOurConfig(item))
				{
					try
					{
						harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[AutoScale] " + type.Name + "." + item.Name + ": " + ex.Message));
					}
				}
			}
			return num;
		}

		private static int PatchHarvest(Harmony harmony, Type type, string cls)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			if (type == null)
			{
				return 0;
			}
			int num = 0;
			HarmonyMethod val = new HarmonyMethod(typeof(AutoScale), "HarvestTranspiler", (Type[])null);
			foreach (MethodBase item in AllMethods(type))
			{
				if (WritesResourceDamage(item))
				{
					try
					{
						harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[AutoScale] harvest " + type.Name + "." + item.Name + ": " + ex.Message));
					}
				}
			}
			return num;
		}

		private static int PatchToolTier(Harmony harmony, Type type)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			if (type == null)
			{
				return 0;
			}
			int num = 0;
			HarmonyMethod val = new HarmonyMethod(typeof(AutoScale), "ToolTierTranspiler", (Type[])null);
			foreach (MethodBase item in AllMethods(type))
			{
				if (Body(item).Any((KeyValuePair<OpCode, object> i) => i.Key == OpCodes.Stfld && i.Value is FieldInfo fieldInfo && (fieldInfo == hitTier || fieldInfo == sharedTier)))
				{
					try
					{
						harmony.Patch(item, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null);
						num++;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogWarning((object)("[AutoScale] tool tier " + type.Name + "." + item.Name + ": " + ex.Message));
					}
				}
			}
			return num;
		}

		private static IEnumerable<MethodBase> AllMethods(Type type)
		{
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo m in methods)
			{
				if (!m.IsAbstract && !m.ContainsGenericParameters)
				{
					yield return m;
				}
			}
			Type[] nestedTypes = type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic);
			foreach (Type nested in nestedTypes)
			{
				MethodInfo[] methods2 = nested.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (MethodInfo m2 in methods2)
				{
					if (!m2.IsAbstract && !m2.ContainsGenericParameters)
					{
						yield return m2;
					}
				}
			}
		}

		private static IEnumerable<KeyValuePair<OpCode, object>> Body(MethodBase m)
		{
			try
			{
				return PatchProcessor.ReadMethodBody(m).ToList();
			}
			catch
			{
				return Enumerable.Empty<KeyValuePair<OpCode, object>>();
			}
		}

		private static bool ReadsOurConfig(MethodBase m)
		{
			return Body(m).Any((KeyValuePair<OpCode, object> i) => i.Key == OpCodes.Ldsfld && i.Value is FieldInfo f && entries.ContainsKey(KeyOf(f)));
		}

		private static bool WritesResourceDamage(MethodBase m)
		{
			return Body(m).Any((KeyValuePair<OpCode, object> i) => i.Key == OpCodes.Stfld && i.Value is FieldInfo fieldInfo && (fieldInfo == chop || fieldInfo == pickaxe));
		}

		private static string KeyOf(FieldInfo f)
		{
			Type declaringType = f.DeclaringType;
			while (declaringType != null && declaringType.DeclaringType != null && declaringType.Name.StartsWith("<"))
			{
				declaringType = declaringType.DeclaringType;
			}
			return declaringType?.Name + "." + f.Name;
		}

		private static IEnumerable<CodeInstruction> ConfigTranspiler(IEnumerable<CodeInstruction> instructions)
		{
			//IL_010e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Expected O, but got Unknown
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			MethodInfo methodInfo = AccessTools.Method(typeof(AutoScale), "ScaleF", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(AutoScale), "ScaleI", (Type[])null, (Type[])null);
			for (int i = 0; i < list.Count - 1; i++)
			{
				if (list[i].opcode != OpCodes.Ldsfld || !(list[i].operand is FieldInfo f))
				{
					continue;
				}
				string text = KeyOf(f);
				if (entries.TryGetValue(text, out var value))
				{
					CodeInstruction val = list[i + 1];
					if ((!(val.opcode != OpCodes.Callvirt) || !(val.opcode != OpCodes.Call)) && val.operand is MethodInfo methodInfo3 && !(methodInfo3.Name != "get_Value"))
					{
						list.Insert(i + 2, new CodeInstruction(OpCodes.Ldstr, (object)text));
						list.Insert(i + 3, new CodeInstruction(OpCodes.Call, (object)(value.IsInt ? methodInfo2 : methodInfo)));
						i += 3;
					}
				}
			}
			return list;
		}

		private static IEnumerable<CodeInstruction> HarvestTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			//IL_0132: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Expected O, but got Unknown
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			Type declaringType = original.DeclaringType;
			while (declaringType != null && declaringType.DeclaringType != null && declaringType.Name.StartsWith("<"))
			{
				declaringType = declaringType.DeclaringType;
			}
			string operand = declaringType?.Name ?? "";
			MethodInfo methodInfo = AccessTools.Method(typeof(AutoScale), "Harvest", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(AutoScale), "SetChop", (Type[])null, (Type[])null);
			MethodInfo methodInfo3 = AccessTools.Method(typeof(AutoScale), "SetPickaxe", (Type[])null, (Type[])null);
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode != OpCodes.Stfld) && list[i].operand is FieldInfo fieldInfo && (!(fieldInfo != chop) || !(fieldInfo != pickaxe)))
				{
					list[i].opcode = OpCodes.Ldstr;
					list[i].operand = operand;
					list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo));
					list.Insert(i + 2, new CodeInstruction(OpCodes.Call, (object)((fieldInfo == chop) ? methodInfo2 : methodInfo3)));
					i += 2;
				}
			}
			return list;
		}

		public static float ScaleF(float v, string key)
		{
			if (!entries.TryGetValue(key, out var value))
			{
				return v;
			}
			if (value.Kind == Kind.Passive)
			{
				float num = PassiveFraction(value.Class);
				return (num >= 1f) ? v : (value.Neutral + (v - value.Neutral) * num);
			}
			return v * BoostFactor(value.Class, (value.Kind != Kind.Power) ? BoostMode.Duration : BoostMode.Power);
		}

		public static int ScaleI(int v, string key)
		{
			if (!entries.TryGetValue(key, out var value))
			{
				return v;
			}
			if (value.Kind == Kind.Passive)
			{
				float num = PassiveFraction(value.Class);
				if (num >= 1f)
				{
					return v;
				}
				int num2 = Mathf.RoundToInt(value.Neutral);
				int num3 = Mathf.RoundToInt((float)num2 + (float)(v - num2) * num);
				if (num > 0f && num3 == num2 && v != num2)
				{
					num3 = num2 + Math.Sign(v - num2);
				}
				return num3;
			}
			return Mathf.RoundToInt((float)v * BoostFactor(value.Class, (value.Kind != Kind.Power) ? BoostMode.Duration : BoostMode.Power));
		}

		public static float Harvest(float v, string cls)
		{
			if (v <= 0f)
			{
				return v;
			}
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			NodeDef nodeDef = godTree?.Find(NodeKind.Harvest, cls);
			if (nodeDef == null)
			{
				return v;
			}
			int num = ((previewClass == cls && previewRank >= 0) ? previewRank : Progress.RankOf(godTree.God, nodeDef.Id));
			float num2 = v * HarvestPercent(num) / 100f;
			if (previewClass == null && Time.time - lastHarvestLog > 0.5f)
			{
				lastHarvestLog = Time.time;
				Plugin.Debug($"[Harvest] {cls}: resource damage {v:0.#} -> {num2:0.#} ({nodeDef.Name} rank {num}/3)");
			}
			return num2;
		}

		public static void SetChop(ref DamageTypes d, float v)
		{
			d.m_chop = v;
			if (Plugin.HarvestBothResources.Value && v > d.m_pickaxe)
			{
				d.m_pickaxe = v;
			}
		}

		public static void SetPickaxe(ref DamageTypes d, float v)
		{
			d.m_pickaxe = v;
			if (Plugin.HarvestBothResources.Value && v > d.m_chop)
			{
				d.m_chop = v;
			}
		}

		private static IEnumerable<CodeInstruction> ToolTierTranspiler(IEnumerable<CodeInstruction> instructions, MethodBase original)
		{
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Expected O, but got Unknown
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			List<CodeInstruction> list = instructions.ToList();
			Type declaringType = original.DeclaringType;
			while (declaringType != null && declaringType.DeclaringType != null && declaringType.Name.StartsWith("<"))
			{
				declaringType = declaringType.DeclaringType;
			}
			string operand = declaringType?.Name ?? "";
			MethodInfo methodInfo = AccessTools.Method(typeof(AutoScale), "ToolTier", (Type[])null, (Type[])null);
			for (int i = 0; i < list.Count; i++)
			{
				if (!(list[i].opcode != OpCodes.Stfld) && list[i].operand is FieldInfo fieldInfo && (!(fieldInfo != hitTier) || !(fieldInfo != sharedTier)))
				{
					list[i].opcode = OpCodes.Ldstr;
					list[i].operand = operand;
					list.Insert(i + 1, new CodeInstruction(OpCodes.Call, (object)methodInfo));
					list.Insert(i + 2, new CodeInstruction(OpCodes.Stfld, (object)fieldInfo));
					i += 2;
				}
			}
			return list;
		}

		public static int ToolTier(int v, string cls)
		{
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			if (godTree == null || godTree.ForAbility(cls) == null)
			{
				return v;
			}
			NodeDef nodeDef = godTree.Find(NodeKind.Harvest, cls);
			return (nodeDef != null && Progress.RankOf(godTree.God, nodeDef.Id) > 0) ? v : (-1);
		}

		public static float HarvestPercent(int rank)
		{
			return Mathf.Clamp(Plugin.HarvestMaxPercent.Value, 0f, 100f) * (float)Mathf.Clamp(rank, 0, 3) / 3f;
		}

		private static float PassiveFraction(string cls)
		{
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			NodeDef nodeDef = godTree?.ForAttribute(cls);
			int num = ((nodeDef != null && nodeDef.MaxRank > 0) ? nodeDef.MaxRank : 3);
			if (previewClass == cls && previewRank >= 0)
			{
				return Mathf.Clamp01((float)previewRank / (float)num) * Plugin.PassiveFactor;
			}
			if (nodeDef == null || !nodeDef.Auto)
			{
				return 1f;
			}
			return (float)Mathf.Clamp(Progress.RankOf(godTree.God, nodeDef.Id), 0, num) / (float)num * Plugin.PassiveFactor;
		}

		private static float BoostFactor(string cls, BoostMode mode)
		{
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			NodeDef nodeDef = godTree?.Find(NodeKind.Upgrade, cls);
			if (nodeDef == null || !nodeDef.Auto)
			{
				return 1f;
			}
			NodeDef nodeDef2 = godTree.Find(NodeKind.Keystone, cls);
			int num = ((previewClass == cls && previewRank >= 0) ? previewRank : Progress.RankOf(godTree.God, nodeDef.Id));
			float num2 = 1f;
			if (nodeDef.Mode == mode)
			{
				num2 += UpgradeStep * (float)num / 100f;
			}
			if (nodeDef2 != null && nodeDef2.Mode == mode && Progress.RankOf(godTree.God, nodeDef2.Id) > 0)
			{
				num2 += KeystoneBonus / 100f;
			}
			return num2;
		}

		public static float Cooldown(string cls)
		{
			float value;
			return cooldowns.TryGetValue(cls, out value) ? value : 0f;
		}

		public static float CooldownFactor(string cls)
		{
			GodTree godTree = TreeDefs.For(Nd.LocalGod());
			NodeDef nodeDef = godTree?.Find(NodeKind.Upgrade, cls);
			if (nodeDef == null || !nodeDef.Auto)
			{
				return 1f;
			}
			NodeDef nodeDef2 = godTree.Find(NodeKind.Keystone, cls);
			float num = 0f;
			if (nodeDef.Mode == BoostMode.Cooldown)
			{
				num += UpgradeCooldownCut * (float)Progress.RankOf(godTree.God, nodeDef.Id) / 100f;
			}
			if (nodeDef2 != null && nodeDef2.Mode == BoostMode.Cooldown && Progress.RankOf(godTree.God, nodeDef2.Id) > 0)
			{
				num += KeystoneCooldownCut / 100f;
			}
			return Mathf.Clamp(1f - num, 0.1f, 1f);
		}

		public static string UpgradeText(NodeDef up, string name, int r)
		{
			float upgradeStep = UpgradeStep;
			return up.Mode switch
			{
				BoostMode.Power => $"{name} is <color=#F0AA3C>+{upgradeStep * (float)r:0.#}%</color> stronger (its damage, healing, summon health or bonuses).", 
				BoostMode.Duration => $"{name} lasts <color=#F0AA3C>+{upgradeStep * (float)r:0.#}%</color> longer.", 
				_ => $"{name} cooldown <color=#F0AA3C>-{UpgradeCooldownCut * (float)r:0.#}%</color>.", 
			};
		}

		public static string KeystoneText(NodeDef key, string name)
		{
			return key.Mode switch
			{
				BoostMode.Cooldown => $"{name} cooldown <color=#F0AA3C>-{KeystoneCooldownCut:0.#}%</color> (never below 1s).", 
				BoostMode.Duration => $"{name} lasts an extra <color=#F0AA3C>+{KeystoneBonus:0.#}%</color>.", 
				_ => $"{name} gets an extra <color=#F0AA3C>+{KeystoneBonus:0.#}%</color> strength (damage, healing or bonuses).", 
			};
		}

		public static string PreviewAttribute(string god, string cls, int rank)
		{
			Demigod val = Nd.LocalDemigod();
			DemigodAttribute attr = (((Object)(object)val == (Object)null) ? null : Gating.FindAttribute(val, cls));
			if (attr == null)
			{
				return "";
			}
			return WithPreview(cls, rank, () => attr.GetTooltip());
		}

		public static string PreviewAbility(string cls, int rank)
		{
			Demigod val = Nd.LocalDemigod();
			if (val?.Abilities == null)
			{
				return "";
			}
			DemigodAbility ability = ((IEnumerable<DemigodAbility>)val.Abilities.Values).FirstOrDefault((Func<DemigodAbility, bool>)((DemigodAbility a) => a != null && ((object)a).GetType().Name == cls));
			if (ability == null)
			{
				return "";
			}
			return WithPreview(cls, rank, () => ability.GetTooltip());
		}

		private static string WithPreview(string cls, int rank, Func<string> get)
		{
			previewClass = ((rank >= 0) ? cls : null);
			previewRank = rank;
			try
			{
				return "<color=#A69A85>" + get() + "</color>";
			}
			catch
			{
				return "";
			}
			finally
			{
				previewClass = null;
				previewRank = -1;
			}
		}
	}
	[HarmonyPatch(typeof(DemigodAbility), "Execute")]
	internal static class AutoCooldown_Patch
	{
		private static readonly FieldInfo cooldownSe = AccessTools.Field(typeof(DemigodAbility), "AbilityCooldownStatusEffect");

		private static void Postfix(DemigodAbility __instance)
		{
			if (__instance == null || !Nd.IsLocal(__instance.Owner) || cooldownSe == null)
			{
				return;
			}
			float num = AutoScale.CooldownFactor(((object)__instance).GetType().Name);
			if (!(num >= 1f))
			{
				object? value = cooldownSe.GetValue(__instance);
				StatusEffect val = (StatusEffect)((value is StatusEffect) ? value : null);
				if (val != null && val.m_ttl > 0f)
				{
					val.m_ttl = Mathf.Max(1f, val.m_ttl * num);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "SetMaxEitr")]
	internal static class Player_SetMaxEitr_BaseEitr
	{
		private static void Prefix(Player __instance, ref float __0)
		{
			if (Nd.IsLocal(__instance) && !(Plugin.BaseEitr.Value <= 0f))
			{
				Demigod val = Nd.LocalDemigod();
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Owner != (Object)(object)__instance))
				{
					__0 += Plugin.BaseEitr.Value;
				}
			}
		}
	}
	internal static class EitrCost
	{
		private struct Pending
		{
			public Player Player;

			public float Before;

			public float Need;

			public float CheckAt;

			public string Name;
		}

		private static readonly List<Pending> pending = new List<Pending>();

		private static readonly Dictionary<Type, MethodInfo> costMethod = new Dictionary<Type, MethodInfo>();

		public static int CostOf(DemigodAbility ability)
		{
			Type type = ((object)ability).GetType();
			if (!costMethod.TryGetValue(type, out var value))
			{
				value = AccessTools.Method(type, "GetAbilityEitrCost", Type.EmptyTypes, (Type[])null);
				if (value != null && value.ReturnType != typeof(int))
				{
					value = null;
				}
				costMethod[type] = value;
			}
			if (value == null)
			{
				return 0;
			}
			try
			{
				return (int)value.Invoke(ability, null);
			}
			catch
			{
				return 0;
			}
		}

		public static void OnExecuted(DemigodAbility ability)
		{
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			Player owner = ability.Owner;
			if (Nd.IsLocal(owner) && Plugin.FixSpecialAttackEitr.Value)
			{
				int num = CostOf(ability);
				if (num > 0)
				{
					ItemData currentWeapon = ((Humanoid)owner).GetCurrentWeapon();
					float num2 = ((currentWeapon != null) ? ((Character)owner).GetSkillFactor(currentWeapon.m_shared.m_skillType) : 0f);
					float need = (float)num * (1f - 0.33f * num2);
					pending.Add(new Pending
					{
						Player = owner,
						Before = owner.GetEitr(),
						Need = need,
						CheckAt = Time.time + 0.5f,
						Name = ((object)ability).GetType().Name
					});
				}
			}
		}

		public static void Tick()
		{
			for (int num = EitrCost.pending.Count - 1; num >= 0; num--)
			{
				Pending pending = EitrCost.pending[num];
				if ((Object)(object)pending.Player == (Object)null)
				{
					EitrCost.pending.RemoveAt(num);
				}
				else if (!(Time.time < pending.CheckAt))
				{
					EitrCost.pending.RemoveAt(num);
					float num2 = pending.Before - pending.Player.GetEitr();
					if (!(num2 >= pending.Need * 0.5f))
					{
						((Character)pending.Player).UseEitr(pending.Need);
						Plugin.Debug($"[Eitr] {pending.Name}: nothing was spent, charged {pending.Need:0.#} eitr");
					}
				}
			}
		}
	}
	[HarmonyPatch(typeof(DemigodAbility), "Execute")]
	internal static class DemigodAbility_Execute_EitrCost
	{
		private static void Postfix(DemigodAbility __instance)
		{
			try
			{
				EitrCost.OnExecuted(__instance);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Eitr] " + ex.Message));
			}
		}
	}
	internal static class Gating
	{
		private sealed class Pool
		{
			public Dictionary<Type, DemigodAttribute> All;

			public Dictionary<Type, DemigodAttribute> Applied;

			public string Signature;
		}

		private static readonly ConditionalWeakTable<Demigod, Pool> pools = new ConditionalWeakTable<Demigod, Pool>();

		public static DemigodAttribute FindAttribute(Demigod d, string cls)
		{
			if ((Object)(object)d == (Object)null)
			{
				return null;
			}
			if (pools.TryGetValue(d, out var value))
			{
				foreach (KeyValuePair<Type, DemigodAttribute> item in value.All)
				{
					if (item.Key.Name == cls)
					{
						return item.Value;
					}
				}
			}
			if (d.DefaultAttributes != null)
			{
				foreach (KeyValuePair<Type, DemigodAttribute> defaultAttribute in d.DefaultAttributes)
				{
					if (defaultAttribute.Key.Name == cls)
					{
						return defaultAttribute.Value;
					}
				}
			}
			return null;
		}

		public static void RefreshLocal(bool force)
		{
			Demigod val = Nd.LocalDemigod();
			if ((Object)(object)val != (Object)null)
			{
				Refresh(val, force);
			}
		}

		public static void Refresh(Demigod d, bool force)
		{
			if ((Object)(object)d == (Object)null || !Nd.IsLocal(d.Owner))
			{
				return;
			}
			GodTree godTree = TreeDefs.For(d.DemigodName);
			if (godTree == null)
			{
				return;
			}
			Dictionary<Type, DemigodAttribute> defaultAttributes = d.DefaultAttributes;
			if (defaultAttributes == null)
			{
				return;
			}
			if (!pools.TryGetValue(d, out var value) || defaultAttributes != value.Applied)
			{
				pools.Remove(d);
				value = new Pool
				{
					All = new Dictionary<Type, DemigodAttribute>(defaultAttributes)
				};
				pools.Add(d, value);
				force = true;
			}
			string text = Progress.Signature(godTree.God);
			if (!force && text == value.Signature)
			{
				return;
			}
			Dictionary<Type, DemigodAttribute> dictionary = new Dictionary<Type, DemigodAttribute>();
			foreach (KeyValuePair<Type, DemigodAttribute> kv in value.All)
			{
				NodeDef nodeDef = godTree.ForAttribute(kv.Key.Name);
				bool flag = nodeDef == null || Progress.RankOf(godTree.God, nodeDef.Id) > 0;
				bool flag2 = value.Applied == null || value.Applied.ContainsKey(kv.Key);
				if (flag)
				{
					dictionary[kv.Key] = kv.Value;
					if (!flag2)
					{
						SafeCall(delegate
						{
							kv.Value.Awake();
						}, kv.Key.Name + ".Awake");
					}
				}
				else if (flag2)
				{
					SafeCall(delegate
					{
						kv.Value.OnDestroy();
					}, kv.Key.Name + ".OnDestroy");
				}
			}
			d.DefaultAttributes = dictionary;
			value.Applied = dictionary;
			value.Signature = text;
			Plugin.Debug($"[Gating] {godTree.God}: {dictionary.Count}/{value.All.Count} passives active ({text})");
		}

		private static void SafeCall(Action a, string what)
		{
			try
			{
				a();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("[Gating] " + what + " failed: " + ex.Message));
			}
		}
	}
	[HarmonyPatch(typeof(Demigod), "GiveDemigodDefaultAttributes")]
	internal static class Demigod_GiveDemigodDefaultAttributes_Patch
	{
		private static void Postfix(Demigod __0)
		{
			try
			{
				Gating.Refresh(__0, force: true);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"[Gating] {arg}");
			}
		}
	}
	internal sealed class GodData
	{
		public string Name;

		public string Aspect;

		public Color Color;

		public string[] Attributes;

		public (string id, bool starter, bool harvest)[] Abilities;

		public static readonly GodData Brokkr = new GodData
		{
			Name = "Brokkr",
			Aspect = "Forge & Lightning",
			Color = new Color(0.95f, 0.62f, 0.22f)
		};

		public static readonly GodData[] All = new GodData[10]
		{
			new GodData
			{
				Name = "Eir",
				Aspect = "Healing & Valor",
				Color = new Color(1f, 0.86f, 0.5f),
				Attributes = new string[6] { "HolyBody", "EagleStrength", "Valkyrie", "Purity", "HareLegs", "DeerEndurance" },
				Abilities = new(string, bool, bool)[6]
				{
					Start("HolyStrike"),
					A("Stagger"),
					H("HolySun"),
					A("ElementalShield"),
					A("HolyHeal"),
					A("HolyConjuration")
				}
			},
			new GodData
			{
				Name = "Fenrir",
				Aspect = "Frost & Fury",
				Color = new Color(0.55f, 0.8f, 1f),
				Attributes = new string[6] { "TitanArms", "FrostBody", "BoilingBlood", "WolfSpeed", "HareLegs", "DeerEndurance" },
				Abilities = new(string, bool, bool)[6]
				{
					H("Iceshards"),
					Start("Slash", harvest: true),
					A("Enraged"),
					A("FrostNova"),
					A("Wolf"),
					A("HealOverTime")
				}
			},
			new GodData
			{
				Name = "Freyja",
				Aspect = "Nature & Love",
				Color = new Color(0.55f, 0.86f, 0.45f),
				Attributes = new string[6] { "ThornBody", "EagleStrength", "Valkyrie", "WolfSpeed", "HareLegs", "DeerEndurance" },
				Abilities = new(string, bool, bool)[6]
				{
					Start("PoisonJavelin", harvest: true),
					A("Stagger"),
					A("Dash"),
					A("PoisonCloud"),
					A("NatureWisp"),
					A("HealOverTime")
				}
			},
			new GodData
			{
				Name = "Hel",
				Aspect = "Death & Decay",
				Color = new Color(0.62f, 0.45f, 0.88f),
				Attributes = new string[6] { "CelestialBody", "PhantomShroud", "CursedHands", "SoulReaper", "ChildOfDarkness", "FoxLegs" },
				Abilities = new(string, bool, bool)[5]
				{
					Start("AcidBolt"),
					H("Meteor"),
					A("DecayAura"),
					A("Minions"),
					A("Sacrifice")
				}
			},
			new GodData
			{
				Name = "Hraesvelgr",
				Aspect = "Wind & Sky",
				Color = new Color(0.7f, 0.95f, 0.95f),
				Attributes = new string[8] { "EagleStrength", "EaglePrecision", "EagleAgility", "EagleEndurance", "EagleSense", "WindAura", "Glide", "HareLegs" },
				Abilities = new(string, bool, bool)[7]
				{
					Start("Cleave"),
					A("Whirlwind"),
					A("Soar"),
					A("Telekinesis"),
					H("Vortex"),
					H("EnergyWisp"),
					A("WindShield")
				}
			},
			new GodData
			{
				Name = "Kvasir",
				Aspect = "Blood & Wisdom",
				Color = new Color(0.88f, 0.22f, 0.28f),
				Attributes = new string[7] { "BloodyArcher", "PiercingInstincts", "RegenerativeFlesh", "Thirsty", "Corruption", "HareLegs", "DeerEndurance" },
				Abilities = new(string, bool, bool)[5]
				{
					Start("BloodSlash"),
					A("Warp"),
					A("DarkAura"),
					A("Charm"),
					A("HealOverTime")
				}
			},
			new GodData
			{
				Name = "Njord",
				Aspect = "Sea & Storm",
				Color = new Color(0.3f, 0.62f, 0.98f),
				Attributes = new string[5] { "AquaticBody", "WaterShield", "BearStrength", "Raider", "TitanLegs" },
				Abilities = new(string, bool, bool)[6]
				{
					Start("LightningBolt"),
					A("Crush"),
					A("WaterSphere"),
					A("Tornado"),
					A("Neck"),
					A("Restoration")
				}
			},
			new GodData
			{
				Name = "Surtr",
				Aspect = "Fire & Ruin",
				Color = new Color(1f, 0.42f, 0.16f),
				Attributes = new string[4] { "FlameBody", "ColdBlood", "BearStrength", "TitanLegs" },
				Abilities = new(string, bool, bool)[7]
				{
					Start("Fireball", harvest: true),
					H("Slash"),
					A("Crush"),
					A("BurningGround"),
					A("ElementalShield"),
					A("Surtling"),
					A("DrainBlood")
				}
			},
			new GodData
			{
				Name = "Thor",
				Aspect = "Thunder & Might",
				Color = new Color(0.45f, 0.66f, 1f),
				Attributes = new string[3] { "StaticBody", "BearStrength", "TitanLegs" },
				Abilities = new(string, bool, bool)[5]
				{
					Start("LightningStrike"),
					A("Dash"),
					A("LightningAura"),
					A("LightningStorm"),
					A("Restoration")
				}
			},
			new GodData
			{
				Name = "Ullr",
				Aspect = "Hunt & Winter",
				Color = new Color(0.62f, 0.9f, 0.8f),
				Attributes = new string[7] { "Hunter", "Sneaky", "Lumberjack", "WolfFur", "WolfSpeed", "HareLegs", "DeerEndurance" },
				Abilities = new(string, bool, bool)[5]
				{
					Start("InfusedArrow"),
					A("Blast"),
					A("SenseCreatures"),
					A("Wolf"),
					A("HealOverTime")
				}
			}
		};

		private static Dictionary<string, GodData> byName;

		private static (string, bool, bool) A(string id)
		{
			return (id, false, false);
		}

		private static (string, bool, bool) Start(string id, bool harvest = false)
		{
			return (id, true, harvest);
		}

		private static (string, bool, bool) H(string id)
		{
			return (id, false, true);
		}

		public static GodData For(string god)
		{
			if (byName == null)
			{
				byName = new Dictionary<string, GodData>(StringComparer.OrdinalIgnoreCase) { [Brokkr.Name] = Brokkr };
				GodData[] all = All;
				foreach (GodData godData in all)
				{
					byName[godData.Name] = godData;
				}
			}
			GodData value;
			return (god != null && byName.TryGetValue(god, out value)) ? value : null;
		}
	}
	internal static class Icons
	{
		private static readonly Dictionary<string, Texture2D> cache = new Dictionary<string, Texture2D>();

		private static Assembly panel;

		private static string[] panelResources = new string[0];

		private static bool searchedPanel;

		private static string folder;

		private static Texture2D ring;

		private static Texture2D glow;

		private static Texture2D circleTex;

		private static Sprite circle;

		private static Texture2D radialGlow;

		private static Texture2D thinRing;

		public static Texture2D RadialGlow => ((Object)(object)radialGlow != (Object)null) ? radialGlow : (radialGlow = DrawRadial(256));

		public static Texture2D ThinRing => ((Object)(object)thinRing != (Object)null) ? thinRing : (thinRing = DrawRing(512, 0.985f, 0.998f, Color.white));

		public static Texture2D Ring
		{
			get
			{
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				object result;
				if (!((Object)(object)ring != (Object)null))
				{
					result = PanelResource("DemigodPanel.Assets.Slots.socket_ring.png") ?? DrawRing(256, 0.86f, 0.98f, new Color(0.86f, 0.62f, 0.3f));
					ring = (Texture2D)result;
				}
				else
				{
					result = ring;
				}
				return (Texture2D)result;
			}
		}

		public static Texture2D Glow
		{
			get
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				object result;
				if (!((Object)(object)glow != (Object)null))
				{
					result = PanelResource("DemigodPanel.Assets.Slots.socket_glow.png") ?? DrawGlow(256, new Color(1f, 0.62f, 0.2f));
					glow = (Texture2D)result;
				}
				else
				{
					result = glow;
				}
				return (Texture2D)result;
			}
		}

		public static Sprite Circle
		{
			get
			{
				//IL_004c: Unknown result type (might be due to invalid IL or missing references)
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				if ((Object)(object)circle != (Object)null)
				{
					return circle;
				}
				circleTex = DrawDisc(256);
				circle = Sprite.Create(circleTex, new Rect(0f, 0f, (float)((Texture)circleTex).width, (float)((Texture)circleTex).height), new Vector2(0.5f, 0.5f));
				return circle;
			}
		}

		public static string ShortName(string key)
		{
			string text = key.Substring(key.IndexOf(':') + 1);
			if (text.StartsWith("Ability"))
			{
				return text.Substring(7);
			}
			if (text.StartsWith("Attribute"))
			{
				return text.Substring(9);
			}
			return text;
		}

		public static Texture2D Get(string key, string god)
		{
			if (string.IsNullOrEmpty(key))
			{
				return null;
			}
			string key2 = key + "@" + god;
			if (cache.TryGetValue(key2, out var value) && (Object)(object)value != (Object)null)
			{
				return value;
			}
			string text = key.Substring(0, key.IndexOf(':'));
			string text2 = ShortName(key);
			value = FromFolder(text2 + "_" + god) ?? FromFolder(text2);
			if ((Object)(object)value == (Object)null)
			{
				value = FromPanel(text, text2, god);
			}
			if ((Object)(object)value == (Object)null && text == "ability")
			{
				value = Nd.AbilityIcon(key.Substring(key.IndexOf(':') + 1));
			}
			if ((Object)(object)value != (Object)null)
			{
				cache[key2] = value;
			}
			return value;
		}

		private static Texture2D FromFolder(string name)
		{
			if (folder == null)
			{
				folder = Path.Combine(Path.GetDirectoryName(typeof(Icons).Assembly.Location) ?? "", "Icons");
			}
			string text = Path.Combine(folder, name + ".png");
			if (!File.Exists(text))
			{
				return null;
			}
			try
			{
				return Load(File.ReadAllBytes(text), name);
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Could not load icon " + text + ": " + ex.Message));
				return null;
			}
		}

		private static void FindPanel()
		{
			if (searchedPanel)
			{
				return;
			}
			searchedPanel = true;
			panel = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "DemigodPanel");
			if (panel != null)
			{
				try
				{
					panelResources = panel.GetManifestResourceNames();
				}
				catch
				{
					panelResources = new string[0];
				}
				Plugin.Log.LogInfo((object)$"NorseDemigodsUI found: using its art for the tree ({panelResources.Length} resources).");
			}
		}

		private static Texture2D PanelResource(string resource)
		{
			FindPanel();
			if (panel == null || resource == null)
			{
				return null;
			}
			try
			{
				using Stream stream = panel.GetManifestResourceStream(resource);
				if (stream == null)
				{
					return null;
				}
				byte[] array = new byte[stream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = stream.Read(array, i, array.Length - i);
					if (num <= 0)
					{
						break;
					}
				}
				return Load(array, resource);
			}
			catch
			{
				return null;
			}
		}

		private static Texture2D FromPanel(string kind, string name, string god)
		{
			FindPanel();
			if (panel == null)
			{
				return null;
			}
			if (kind == "god")
			{
				return PanelResource(Find("DemigodPanel.Assets.GodIcons." + name.ToLowerInvariant() + ".png"));
			}
			if (kind == "ability")
			{
				string resource = Find("DemigodPanel.Assets.Icons." + name + "_" + god + ".png") ?? Find("DemigodPanel.Assets.Icons." + name + ".png") ?? panelResources.FirstOrDefault((string r) => r.StartsWith("DemigodPanel.Assets.Icons." + name + "_", StringComparison.OrdinalIgnoreCase));
				return PanelResource(resource);
			}
			return null;
			static string Find(string exact)
			{
				return panelResources.FirstOrDefault((string r) => string.Equals(r, exact, StringComparison.OrdinalIgnoreCase));
			}
		}

		private static Texture2D Load(byte[] bytes, string name)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
			{
				name = name,
				wrapMode = (TextureWrapMode)1
			};
			return ImageConversion.LoadImage(val, bytes) ? val : null;
		}

		public static Texture2D Background(string god)
		{
			string key = "bg@" + god;
			if (cache.TryGetValue(key, out var value))
			{
				return value;
			}
			value = FromFolder(Path.Combine("Backgrounds", god));
			cache[key] = value;
			return value;
		}

		private static Texture2D DrawRadial(int size)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1
			};
			float num = (float)(size - 1) / 2f;
			float num2 = (float)size / 2f;
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num3 = Mathf.Clamp01(Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num2);
					float num4 = Mathf.Pow(1f - num3, 1.8f);
					array[i * size + j] = new Color(1f, 1f, 1f, num4);
				}
			}
			val.SetPixels(array);
			val.Apply();
			return val;
		}

		private static Texture2D DrawDisc(int size)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1
			};
			float num = (float)(size - 1) / 2f;
			float num2 = (float)size / 2f - 1f;
			Color32[] array = (Color32[])(object)new Color32[size * size];
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num3 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num));
					byte b = (byte)(Mathf.Clamp01(num2 - num3 + 0.5f) * 255f);
					array[i * size + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			val.SetPixels32(array);
			val.Apply();
			return val;
		}

		private static Texture2D DrawRing(int size, float inner, float outer, Color col)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1
			};
			float num = (float)(size - 1) / 2f;
			float num2 = (float)size / 2f;
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num3 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num2;
					float num4 = Mathf.Clamp01((num3 - inner) * (float)size / 2f) * Mathf.Clamp01((outer - num3) * (float)size / 2f);
					float num5 = 0.75f + 0.25f * Mathf.Sin((num3 - inner) / (outer - inner) * MathF.PI);
					array[i * size + j] = new Color(col.r * num5, col.g * num5, col.b * num5, num4);
				}
			}
			val.SetPixels(array);
			val.Apply();
			return val;
		}

		private static Texture2D DrawGlow(int size, Color col)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(size, size, (TextureFormat)4, false)
			{
				wrapMode = (TextureWrapMode)1
			};
			float num = (float)(size - 1) / 2f;
			float num2 = (float)size / 2f;
			Color[] array = (Color[])(object)new Color[size * size];
			for (int i = 0; i < size; i++)
			{
				for (int j = 0; j < size; j++)
				{
					float num3 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num)) / num2;
					float num4 = Mathf.Clamp01(1f - Mathf.Abs(num3 - 0.72f) / 0.28f);
					array[i * size + j] = new Color(col.r, col.g, col.b, num4 * num4 * 0.85f);
				}
			}
			val.SetPixels(array);
			val.Apply();
			return val;
		}
	}
	internal static class Nd
	{
		private static readonly Dictionary<string, ConfigEntryBase> cfgCache = new Dictionary<string, ConfigEntryBase>();

		public static Demigod LocalDemigod()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return null;
			}
			Demigod myDemigod = Cache.MyDemigod;
			if ((Object)(object)myDemigod != (Object)null && (Object)(object)myDemigod.Owner == (Object)(object)localPlayer)
			{
				return myDemigod;
			}
			return ((Component)localPlayer).GetComponent<Demigod>();
		}

		public static string LocalGod()
		{
			Demigod val = LocalDemigod();
			return ((Object)(object)val != (Object)null) ? val.DemigodName : null;
		}

		public static bool IsLocal(Player p)
		{
			return (Object)(object)p != (Object)null && (Object)(object)p == (Object)(object)Player.m_localPlayer;
		}

		public static float DemigodSkillLevel(Player p)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)p == (Object)null || (Object)(object)NorseDemigods.Instance == (Object)null)
			{
				return 0f;
			}
			return ((Character)p).GetSkillLevel(NorseDemigods.Instance.SkillDemigod);
		}

		public static float Cfg(Type owner, string field, float fallback = 0f)
		{
			string key = owner.FullName + "." + field;
			if (!cfgCache.TryGetValue(key, out var value))
			{
				object? obj = AccessTools.Field(owner, field)?.GetValue(null);
				value = (ConfigEntryBase)((obj is ConfigEntryBase) ? obj : null);
				cfgCache[key] = value;
			}
			if (value == null)
			{
				return fallback;
			}
			try
			{
				return Convert.ToSingle(value.BoxedValue);
			}
			catch
			{
				return fallback;
			}
		}

		public static Texture2D AbilityIcon(string abilityClassName)
		{
			Demigod val = LocalDemigod();
			if ((Object)(object)val == (Object)null || val.Abilities == null)
			{
				return null;
			}
			foreach (KeyValuePair<Type, DemigodAbility> ability in val.Abilities)
			{
				if (ability.Key.Name == abilityClassName && ability.Value != null)
				{
					return ability.Value.Icon;
				}
			}
			return null;
		}

		public static void Message(string text)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}
	}
	[BepInPlugin("OblitusEs.NorseDemigodsTree", "NorseDemigodsTree", "0.7.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "OblitusEs.NorseDemigodsTree";

		public const string Name = "NorseDemigodsTree";

		public const string Version = "0.7.0";

		internal static Plugin Instance;

		internal static ManualLogSource Log;

		internal static ConfigEntry<KeyboardShortcut> ToggleKey;

		internal static ConfigEntry<int> LevelsPerPoint;

		internal static ConfigEntry<int> BonusPoints;

		internal static ConfigEntry<int> MaxTreePoints;

		internal static ConfigEntry<int> KeystoneNeedsUpgradeRank;

		internal static ConfigEntry<int> ResourceNeedsUpgradeRank;

		internal static ConfigEntry<bool> FreeRefunds;

		internal static ConfigEntry<float> BaseEitr;

		internal static ConfigEntry<float> WindowScale;

		internal static ConfigEntry<float> TreeSpacing;

		internal static ConfigEntry<float> NodeSize;

		internal static ConfigEntry<float> TextSize;

		internal static ConfigEntry<float> BackdropStrength;

		internal static ConfigEntry<float> ShadeOpacity;

		internal static ConfigEntry<bool> ShowPortrait;

		internal static ConfigEntry<bool> ShowRings;

		internal static ConfigEntry<float> UpgradeStrength;

		internal static ConfigEntry<float> PassiveStrength;

		internal static ConfigEntry<float> HarvestMaxPercent;

		internal static ConfigEntry<float> AutoUpgradeStep;

		internal static ConfigEntry<float> AutoUpgradeCooldownCut;

		internal static ConfigEntry<float> AutoKeystoneBonus;

		internal static ConfigEntry<float> AutoKeystoneCooldownCut;

		internal static ConfigEntry<bool> HarvestBothResources;

		internal static ConfigEntry<bool> FixSpecialAttackEitr;

		internal static ConfigEntry<bool> TreePointsFromHalls;

		internal static ConfigEntry<bool> CountEveryClear;

		internal static ConfigEntry<bool> AbilitiesNeedAscendancy;

		internal static ConfigEntry<bool> UpgradesNeedAscendancy;

		internal static ConfigEntry<bool> KeystonesNeedAscendancy;

		internal static ConfigEntry<bool> ResourceNodesNeedAscendancy;

		internal static ConfigEntry<bool> PassivesNeedAscendancy;

		internal static ConfigEntry<int> TreePointsPerClear;

		internal static ConfigEntry<int> MaxHallsTreePoints;

		internal static ConfigEntry<int> PassiveAscendancyFromRank;

		internal static ConfigEntry<int> UpgradeAscendancyFromRank;

		internal static ConfigEntry<int> ResourceAscendancyFromRank;

		internal static ConfigEntry<int> AscendancyPointsPerClear;

		internal static ConfigEntry<int> MaxAscendancyPoints;

		internal static ConfigEntry<int> BonusAscendancyPoints;

		internal static ConfigEntry<bool> DebugLogging;

		private float gateTimer;

		internal static float UpgradeFactor => Mathf.Clamp(UpgradeStrength.Value, 0f, 200f) / 100f;

		internal static float PassiveFactor => Mathf.Clamp(PassiveStrength.Value, 10f, 100f) / 100f;

		private void Awake()
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			BindGeneral();
			BindProgression();
			BindWindow();
			BindBalance();
			BindPathOfValheiman();
			PovBridge.Bind();
			DebugLogging = Sync("9 - Debug", "Logging", value: false, "Logs passive gating, rank changes, resource damage and Halls clears.");
			Harmony val = new Harmony("OblitusEs.NorseDemigodsTree");
			int num = 0;
			int num2 = 0;
			Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly);
			foreach (Type type in typesFromAssembly)
			{
				if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length != 0)
				{
					try
					{
						val.CreateClassProcessor(type).Patch();
						num++;
					}
					catch (Exception ex)
					{
						num2++;
						Log.LogWarning((object)("Patch group " + type.Name + " failed (NorseDemigods may have changed): " + ex.Message));
					}
				}
			}
			Log.LogInfo((object)($"Patched {num} groups" + ((num2 > 0) ? $", {num2} failed" : "")));
			try
			{
				AutoScale.Install(val);
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"[AutoScale] install failed: {arg}");
			}
			try
			{
				PovHalls.Install(val);
			}
			catch (Exception arg2)
			{
				Log.LogError((object)$"[Halls] install failed: {arg2}");
			}
			Progress.Changed += delegate
			{
				Gating.RefreshLocal(force: true);
			};
			Log.LogInfo((object)"NorseDemigodsTree 0.7.0 loaded.");
		}

		private void Start()
		{
			try
			{
				PovBridge.Start();
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"[Bridge] start failed: {arg}");
			}
		}

		internal static ConfigEntry<T> Sync<T>(string section, string key, T value, ConfigDescription description)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Expected O, but got Unknown
			if (!IsServerSetting(section))
			{
				return ((BaseUnityPlugin)Instance).Config.Bind<T>(section, key, value, description);
			}
			object[] array = new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			} };
			return ((BaseUnityPlugin)Instance).Config.Bind<T>(section, key, value, new ConfigDescription(description.Description + " [Synced with the server]", description.AcceptableValues, array));
		}

		internal static ConfigEntry<T> Sync<T>(string section, string key, T value, string description)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			return Sync(section, key, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()));
		}

		private static bool IsServerSetting(string section)
		{
			return !section.StartsWith("1 ") && !section.StartsWith("3 ") && !section.StartsWith("9 ");
		}

		private static ConfigDescription Range(string text, float min, float max)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(min, max), Array.Empty<object>());
		}

		private static ConfigDescription Range(string text, int min, int max)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			return new ConfigDescription(text, (AcceptableValueBase)(object)new AcceptableValueRange<int>(min, max), Array.Empty<object>());
		}

		private void BindGeneral()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			ToggleKey = Sync<KeyboardShortcut>("1 - General", "ToggleKey", new KeyboardShortcut((KeyCode)107, Array.Empty<KeyCode>()), "Opens and closes the demigod tree.");
		}

		private void BindProgression()
		{
			LevelsPerPoint = Sync("2 - Progression", "LevelsPerPoint", 4, Range("Demigod skill levels needed for one demigod point (4 = 25 points at level 100).", 1, 20));
			BonusPoints = Sync("2 - Progression", "BonusPoints", 0, Range("Extra points on top of the ones you earn (for testing or easier play).", 0, 100));
			MaxTreePoints = Sync("2 - Progression", "MaxTreePoints", 0, Range("Most tree points a character can have, bonus points included (0 = no limit).", 0, 200));
			BaseEitr = Sync("2 - Progression", "BaseEitr", 20f, Range("Base eitr every demigod starts with. It's the only bonus a god gives before you spend points.", 0f, 200f));
			FreeRefunds = Sync("2 - Progression", "FreeRefunds", value: true, "Right-click a node to take a rank back for free. Turn off to make choices permanent.");
			KeystoneNeedsUpgradeRank = Sync("2 - Progression", "KeystoneNeedsUpgradeRank", 1, Range("Rank an ability's upgrade (Empower, Overcharge...) needs before its keystone (Mastery, Storm Battery...) can be taken.", 1, 3));
			ResourceNeedsUpgradeRank = Sync("2 - Progression", "ResourceNodeNeedsUpgradeRank", 2, Range("Rank an ability's upgrade needs before its resource node (Harvest, Demolitions) can be taken.", 1, 3));
		}

		private void BindWindow()
		{
			WindowScale = Sync("3 - Window", "Scale", 1f, Range("Size of the whole tree window.", 0.5f, 2f));
			TreeSpacing = Sync("3 - Window", "TreeSpacing", 100f, Range("Room between nodes, in percent. Higher spreads the tree out (the tree always shrinks to fit the window, so nodes get smaller as it spreads).", 70f, 160f));
			NodeSize = Sync("3 - Window", "NodeSize", 100f, Range("Size of the node circles, in percent.", 70f, 140f));
			TextSize = Sync("3 - Window", "TextSize", 100f, Range("Size of the tooltip and header text, in percent.", 80f, 140f));
			BackdropStrength = Sync("3 - Window", "BackdropStrength", 100f, Range("How strongly the god's aspect colour, glow and portrait show behind the tree, in percent (0 = plain dark window).", 0f, 200f));
			ShowPortrait = Sync("3 - Window", "ShowPortrait", value: true, "Show the god's faded portrait behind the tree.");
			ShowRings = Sync("3 - Window", "ShowRings", value: true, "Show the thin guide rings behind the tree.");
			ShadeOpacity = Sync("3 - Window", "ScreenDim", 55f, Range("How much the game is darkened behind the window, in percent.", 0f, 100f));
			ConfigEntryBase[] array = (ConfigEntryBase[])(object)new ConfigEntryBase[7]
			{
				(ConfigEntryBase)TreeSpacing,
				(ConfigEntryBase)NodeSize,
				(ConfigEntryBase)TextSize,
				(ConfigEntryBase)BackdropStrength,
				(ConfigEntryBase)ShowPortrait,
				(ConfigEntryBase)ShowRings,
				(ConfigEntryBase)ShadeOpacity
			};
			foreach (ConfigEntryBase entry in array)
			{
				AddChangeHandler(entry, delegate
				{
					TreeDefs.LayoutAll();
					TreeWindow.Rebuild();
				});
			}
			WindowScale.SettingChanged += delegate
			{
				TreeWindow.Rebuild();
			};
		}

		private static void AddChangeHandler(ConfigEntryBase entry, Action onChange)
		{
			entry.ConfigFile.SettingChanged += delegate(object s, SettingChangedEventArgs a)
			{
				if (a.ChangedSetting == entry)
				{
					onChange();
				}
			};
		}

		private void BindBalance()
		{
			UpgradeStrength = Sync("4 - Balance", "UpgradeStrength", 100f, Range("Strength of ALL ability upgrades and keystones, in percent (every god). 100 = as designed, 50 = half as strong, 0 = they do nothing. Scales the damage, healing, duration and cooldown bonuses below and Brokkr's own upgrades.", 0f, 200f));
			PassiveStrength = Sync("4 - Balance", "PassiveStrength", 100f, Range("Strength of passives at full rank, in percent of NorseDemigods' own values (lower ranks scale with it). 100 = unchanged.", 10f, 100f));
			AutoUpgradeStep = Sync("4 - Balance", "UpgradePercentPerRank", 15f, Range("How much stronger an ability gets per upgrade rank (damage, healing, summon health, or duration).", 0f, 100f));
			AutoUpgradeCooldownCut = Sync("4 - Balance", "UpgradeCooldownCutPerRank", 10f, Range("Gods after Brokkr: for abilities whose upgrade shortens the cooldown, percent cut per rank.", 0f, 30f));
			AutoKeystoneBonus = Sync("4 - Balance", "KeystoneBonusPercent", 25f, Range("Gods after Brokkr: extra strength from an ability's keystone when the ability has no cooldown to shorten.", 0f, 200f));
			AutoKeystoneCooldownCut = Sync("4 - Balance", "KeystoneCooldownCutPercent", 20f, Range("Gods after Brokkr: how much an ability's keystone shortens its cooldown.", 0f, 90f));
			HarvestMaxPercent = Sync("4 - Balance", "ResourceDamageAtMaxRank", 50f, Range("How much of an ability's normal damage to trees, stone and ores its resource node gives at rank 3 (ranks 1 and 2 give a third and two thirds of this).", 0f, 100f));
			HarvestBothResources = Sync("4 - Balance", "HarvestBothResources", value: true, "With a Harvest rank, an ability that NorseDemigods made hit only trees (or only rocks/ores) can hit both.");
			FixSpecialAttackEitr = Sync("4 - Balance", "FixSpecialAttackEitr", value: true, "Charge the eitr cost of special attacks that NorseDemigods forgets to charge (Brokkr's Lightning Torpedo). Never charges twice.");
		}

		private void BindPathOfValheiman()
		{
			TreePointsFromHalls = Sync("5 - Path of Valheiman", "TreePointsFromHallsOnly", value: false, "Needs Path of Valheiman. On: demigod tree points come ONLY from clearing Halls of Oaths floors and hall maps (each altar claim), not from demigod skill levels. Off, or without PoV: points come from levels.");
			TreePointsPerClear = Sync("5 - Path of Valheiman", "TreePointsPerClear", 1, Range("Demigod tree points per Halls clear (with TreePointsFromHallsOnly on).", 0, 10));
			MaxHallsTreePoints = Sync("5 - Path of Valheiman", "MaxTreePointsFromHalls", 0, Range("Cap on tree points from Halls clears (0 = no cap).", 0, 200));
			AbilitiesNeedAscendancy = Sync("5 - Path of Valheiman", "AbilitiesNeedAscendancyPoints", value: false, "Needs Path of Valheiman. On: unlocking an ability (not its upgrades, see below) costs a demigod ascendancy point instead of a tree point. These are this mod's own points, earned by clearing Halls of Oaths floors and hall maps. PoV's own ascension points are never touched.");
			UpgradesNeedAscendancy = Sync("5 - Path of Valheiman", "UpgradesNeedAscendancyPoints", value: false, "Needs Path of Valheiman. On: an ability's upgrade node (Brokkr's Overcharge, Bigger Boom...; Empower for the other gods) costs demigod ascendancy points from UpgradeAscendancyFromRank up.");
			UpgradeAscendancyFromRank = Sync("5 - Path of Valheiman", "UpgradeAscendancyFromRank", 2, Range("With UpgradesNeedAscendancyPoints on: first upgrade rank that costs an ascendancy point. Lower ranks cost tree points (2 = rank 1 is a tree point, ranks 2 and 3 are ascendancy points; 1 = every rank).", 1, 3));
			KeystonesNeedAscendancy = Sync("5 - Path of Valheiman", "KeystonesNeedAscendancyPoints", value: false, "Needs Path of Valheiman. On: an ability's keystone (Brokkr's Short Fuse, Blast Off...; Mastery for the other gods) costs a demigod ascendancy point.");
			ResourceNodesNeedAscendancy = Sync("5 - Path of Valheiman", "ResourceNodesNeedAscendancyPoints", value: false, "Needs Path of Valheiman. On: a resource node (Demolitions, Harvest) costs demigod ascendancy points from ResourceAscendancyFromRank up.");
			ResourceAscendancyFromRank = Sync("5 - Path of Valheiman", "ResourceAscendancyFromRank", 2, Range("With ResourceNodesNeedAscendancyPoints on: first resource node rank that costs an ascendancy point (2 = rank 1 is a tree point; 1 = every rank).", 1, 3));
			PassivesNeedAscendancy = Sync("5 - Path of Valheiman", "PassiveHighRanksNeedAscendancyPoints", value: false, "Needs Path of Valheiman. On: passive ranks from PassiveAscendancyFromRank up cost a demigod ascendancy point. Lower ranks still cost tree points.");
			PassiveAscendancyFromRank = Sync("5 - Path of Valheiman", "PassiveAscendancyFromRank", 2, Range("First passive rank that costs an ascendancy point (2 = ranks 2 and 3, 3 = only rank 3, 1 = every rank).", 1, 3));
			AscendancyPointsPerClear = Sync("5 - Path of Valheiman", "AscendancyPointsPerClear", 1, Range("Demigod ascendancy points per Halls clear (used when any of the ...NeedAscendancyPoints settings is on).", 0, 10));
			MaxAscendancyPoints = Sync("5 - Path of Valheiman", "MaxAscendancyPoints", 0, Range("Cap on demigod ascendancy points from Halls clears (0 = no cap).", 0, 100));
			BonusAscendancyPoints = Sync("5 - Path of Valheiman", "BonusAscendancyPoints", 0, Range("Extra demigod ascendancy points (for testing or easier play).", 0, 100));
			CountEveryClear = Sync("5 - Path of Valheiman", "CountEveryClear", value: true, "On: every Halls floor or hall map you claim at the altar counts, so runs can be repeated for points. Off: only claims that gave PoV ascension points count (the first clear of each tier, 4 at most).");
		}

		private void Update()
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			gateTimer -= Time.deltaTime;
			if (gateTimer <= 0f)
			{
				gateTimer = 0.5f;
				Gating.RefreshLocal(force: false);
			}
			EitrCost.Tick();
			PovBridge.Tick();
			if ((Object)(object)Player.m_localPlayer == (Object)null)
			{
				if (TreeWindow.IsOpen)
				{
					TreeWindow.Close();
				}
				return;
			}
			KeyboardShortcut value = ToggleKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown() && !IsTyping())
			{
				if (TreeWindow.IsOpen)
				{
					TreeWindow.Close();
				}
				else
				{
					TreeWindow.Open();
				}
			}
			else if (TreeWindow.IsOpen && Input.GetKeyDown((KeyCode)27))
			{
				TreeWindow.Close();
			}
		}

		private static bool IsTyping()
		{
			if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus())
			{
				return true;
			}
			if (Console.IsVisible())
			{
				return true;
			}
			if (TextInput.IsVisible())
			{
				return true;
			}
			if (Menu.IsVisible())
			{
				return true;
			}
			return false;
		}

		internal static void Debug(string msg)
		{
			if (DebugLogging != null && DebugLogging.Value)
			{
				Log.LogInfo((object)msg);
			}
		}
	}
	[HarmonyPatch(typeof(Menu), "Update")]
	internal static class Menu_Update_Patch
	{
		private static bool Prefix()
		{
			return !TreeWindow.IsOpen && !TreeWindow.ClosedThisFrame;
		}
	}
	internal static class PovBridge
	{
		private sealed class BridgeLog
		{
			public void LogInfo(string msg)
			{
				Plugin.Log.LogInfo((object)("[Bridge] " + msg));
			}

			public void LogWarning(string msg)
			{
				Plugin.Log.LogWarning((object)("[Bridge] " + msg));
			}
		}

		private struct Rule
		{
			public string Stat;

			public float Weight;
		}

		private sealed class Route
		{
			public bool Off;

			public bool Equipped;

			public bool ExplicitElement;

			public List<string> Stats = new List<string>();

			public HashSet<string> Subjects = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
		}

		public sealed class ClawState
		{
			public ItemData Weapon;

			public SharedData Original;
		}

		public const string Guid = "OblitusEs.DemigodsPovBridge";

		private const string SecMain = "6 - PoV Bridge";

		private const string SecRoutes = "7 - PoV Bridge Routes";

		private const string SecDebug = "9 - Debug";

		private static readonly BridgeLog Log = new BridgeLog();

		private static ConfigEntry<bool> cfgEnabled;

		private static ConfigEntry<string> cfgUnit;

		private static ConfigEntry<bool> cfgCapEnabled;

		private static ConfigEntry<float> cfgMaxBonus;

		private static ConfigEntry<string> cfgAlwaysStats;

		private static ConfigEntry<string> cfgHealingStats;

		private static ConfigEntry<string> cfgSummonStats;

		private static ConfigEntry<string> cfgAttributeRoute;

		private static ConfigEntry<string> cfgUnknownRoute;

		private static ConfigEntry<bool> cfgLogChanges;

		private static ConfigEntry<bool> cfgLogAllStats;

		private static ConfigEntry<bool> cfgLogRouting;

		private static ConfigEntry<string> cfgMode;

		private static ConfigEntry<bool> cfgLogScoped;

		private static ConfigEntry<bool> cfgLogValues;

		private static ConfigEntry<string> cfgCritMode;

		private static ConfigEntry<bool> cfgLogHits;

		private static ConfigEntry<bool> cfgClaws;

		private static ConfigEntry<string> cfgExtraClaws;

		private static readonly Dictionary<string, ConfigEntry<string>> abilityRoutes = new Dictionary<string, ConfigEntry<string>>(StringComparer.Ordinal);

		private static List<Rule> alwaysRules = new List<Rule>();

		private static List<Rule> healingRules = new List<Rule>();

		private static List<Rule> summonRules = new List<Rule>();

		private static Func<string, float> povGet;

		private static HashSet<string> knownStats;

		private static List<string> knownList;

		private static bool ready;

		private static readonly Dictionary<string, KeyValuePair<int, float>> cache = new Dictionary<string, KeyValuePair<int, float>>();

		private const int CacheMs = 200;

		private static readonly Dictionary<string, Route> routeCache = new Dictionary<string, Route>(StringComparer.Ordinal);

		private static readonly string[] NoStats = new string[0];

		private static readonly Dictionary<string, string[]> RouteStats = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
		{
			{
				"SpellElemental",
				new string[4] { "Dmg_Magical", "Dmg_ElementalMagic", "Dmg_Staves", "Dmg_Scepters" }
			},
			{
				"SpellBlood",
				new string[4] { "Dmg_Magical", "Dmg_BloodMagic", "Dmg_Staves", "Dmg_Scepters" }
			},
			{
				"Staves",
				new string[2] { "Dmg_Magical", "Dmg_Staves" }
			},
			{
				"Scepters",
				new string[2] { "Dmg_Magical", "Dmg_Scepters" }
			},
			{
				"Swords",
				new string[1] { "Dmg_Swords" }
			},
			{
				"Knives",
				new string[1] { "Dmg_Knives" }
			},
			{
				"Clubs",
				new string[1] { "Dmg_Clubs" }
			},
			{
				"Polearms",
				new string[1] { "Dmg_Polearms" }
			},
			{
				"Spears",
				new string[1] { "Dmg_Spears" }
			},
			{
				"Axes",
				new string[1] { "Dmg_Axes" }
			},
			{
				"Unarmed",
				new string[1] { "Dmg_Unarmed" }
			},
			{
				"Bows",
				new string[2] { "Dmg_Bows", "Dmg_Shooting" }
			},
			{
				"Crossbows",
				new string[2] { "Dmg_Crossbows", "Dmg_Shooting" }
			}
		};

		private static readonly Dictionary<string, string[]> RouteSubjects = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
		{
			{
				"SpellElemental",
				new string[5] { "Magical", "Spell", "Cast", "ElementalMagic", "Elemental" }
			},
			{
				"SpellBlood",
				new string[4] { "Magical", "Spell", "Cast", "BloodMagic" }
			},
			{
				"Staves",
				new string[4] { "Magical", "Spell", "Cast", "Staves" }
			},
			{
				"Scepters",
				new string[4] { "Magical", "Spell", "Cast", "Scepters" }
			},
			{
				"Swords",
				new string[4] { "Attack", "Melee", "Physical", "Swords" }
			},
			{
				"Knives",
				new string[4] { "Attack", "Melee", "Physical", "Knives" }
			},
			{
				"Clubs",
				new string[4] { "Attack", "Melee", "Physical", "Clubs" }
			},
			{
				"Polearms",
				new string[4] { "Attack", "Melee", "Physical", "Polearms" }
			},
			{
				"Spears",
				new string[4] { "Attack", "Melee", "Physical", "Spears" }
			},
			{
				"Axes",
				new string[4] { "Attack", "Melee", "Physical", "Axes" }
			},
			{
				"Unarmed",
				new string[4] { "Attack", "Melee", "Physical", "Fists" }
			},
			{
				"Bows",
				new string[7] { "Attack", "Ranged", "Shooting", "Projectile", "Physical", "Bows", "Arrow" }
			},
			{
				"Crossbows",
				new string[7] { "Attack", "Ranged", "Shooting", "Projectile", "Physical", "Crossbows", "Bolt" }
			}
		};

		private static readonly HashSet<string> ElementTokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Fire", "Frost", "Lightning", "Poison", "Spirit", "Physical" };

		private static readonly HashSet<string> TagTokens = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "Projectile", "Area", "Minion" };

		private static readonly Dictionary<string, string[]> ImpliedSubjects = new Dictionary<string, string[]>(StringComparer.Ordinal)
		{
			{
				"AbilityFireball",
				new string[2] { "Fire", "Projectile" }
			},
			{
				"AbilityMeteor",
				new string[2] { "Fire", "Area" }
			},
			{
				"AbilityBurningGround",
				new string[2] { "Fire", "Area" }
			},
			{
				"AbilityFrostNova",
				new string[2] { "Frost", "Area" }
			},
			{
				"AbilityIceshards",
				new string[2] { "Frost", "Projectile" }
			},
			{
				"AbilityLightningBolt",
				new string[2] { "Lightning", "Projectile" }
			},
			{
				"AbilityLightningStorm",
				new string[2] { "Lightning", "Area" }
			},
			{
				"AbilityLightningStrike",
				new string[1] { "Lightning" }
			},
			{
				"AbilityLightningTorpedo",
				new string[2] { "Lightning", "Projectile" }
			},
			{
				"AbilityTornado",
				new string[1] { "Area" }
			},
			{
				"AbilityPoisonCloud",
				new string[2] { "Poison", "Area" }
			},
			{
				"AbilityPoisonJavelin",
				new string[2] { "Poison", "Projectile" }
			},
			{
				"AbilityAcidBolt",
				new string[2] { "Poison", "Projectile" }
			}
		};

		private static readonly string[] KnownAbilities = new string[38]
		{
			"AbilityAcidBolt", "AbilityArcaneBolt", "AbilityBlast", "AbilityBlink", "AbilityBloodSlash", "AbilityBomb", "AbilityBurningGround", "AbilityCleave", "AbilityCrush", "AbilityDash",
			"AbilityDecayAura", "AbilityElementalShield", "AbilityEnergyWisp", "AbilityEnraged", "AbilityFireball", "AbilityFrostNova", "AbilityHolyStrike", "AbilityHolySun", "AbilityIceshards", "AbilityInfusedArrow",
			"AbilityLightningBolt", "AbilityLightningStorm", "AbilityLightningStrike", "AbilityLightningTorpedo", "AbilityMeteor", "AbilityNanoCube", "AbilityPoisonCloud", "AbilityPoisonJavelin", "AbilityRestoration", "AbilitySacrifice",
			"AbilitySharpMist", "AbilitySlash", "AbilityStagger", "AbilityTornado", "AbilityVortex", "AbilityWaterSphere", "AbilityWhirlwind", "AbilityWolf"
		};

		private const string RouteHelp = "Route(s) deciding which Path of Valheiman stats boost this ability. Combine several with commas (e.g. SpellElemental, Spears).\nSpellElemental / SpellBlood = Magical + Elemental (or Blood) magic + Staves + Scepters damage. Staves / Scepters = Magical + only that group.\nEquippedWeapon = the weapon you hold. Swords, Knives, Clubs, Polearms, Spears, Axes, Unarmed, Bows, Crossbows = that weapon's damage.\nNoCategory = only the 'always' stats (Damage > Stats). Off = leave this ability exactly as NorseDemigods made it.\nScoped modes also accept element/delivery tags: Fire, Frost, Lightning, Poison, Spirit, Physical, Projectile, Area, Minion (e.g. 'SpellElemental, Frost, Area'). If no element tag is given, a best guess from the ability's name is used.";

		private static readonly Regex PatchedNdMethod = new Regex("NorseDemigods\\.(Abilities|Attributes)\\.(\\w+)", RegexOptions.Compiled);

		private static bool scopedReady;

		private static Type tStat;

		private static Type tSubject;

		private static Type tSubjects;

		private static MethodInfo miGet;

		private static MethodInfo miMul;

		private static MethodInfo miOf;

		private static Func<object> statSetGetter;

		private static bool scopedErrorLogged;

		private static object emptyScope;

		private static readonly Dictionary<string, object> statEnumCache = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, object> subjectEnumCache = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, object> scopeCache = new Dictionary<string, object>(StringComparer.Ordinal);

		private static readonly HashSet<string> warnedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private const float CritBaseMultiplier = 1.5f;

		private static readonly string[] HealingSubjects = new string[4] { "Healing", "Magical", "Spell", "Cast" };

		private static readonly string[] SummonSubjects = new string[1] { "Minion" };

		private static readonly Dictionary<string, string> lastScopedLine = new Dictionary<string, string>(StringComparer.Ordinal);

		private const string ClawAnimation = "dualaxes";

		private const int ClawChain = 4;

		private const float ClawAngle = 90f;

		private const float ClawHeight = 0.6f;

		private static readonly MethodInfo memberwiseClone = typeof(object).GetMethod("MemberwiseClone", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly Dictionary<SharedData, SharedData> clawShared = new Dictionary<SharedData, SharedData>();

		private static readonly HashSet<string> clawLogged = new HashSet<string>(StringComparer.Ordinal);

		private static HashSet<string> extraClaws = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static readonly Dictionary<string, string> lastValueLogged = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, float> lastRouteLogged = new Dictionary<string, float>(StringComparer.Ordinal);

		private static float nextLog;

		private static string lastLine = "";

		private static string lastAllLine = "";

		public static bool Hooked => ready;

		public static void Bind()
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Expected O, but got Unknown
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Expected O, but got Unknown
			cfgEnabled = Plugin.Sync("6 - PoV Bridge", "Enabled", value: false, "Built-in NorseDemigods + Path of Valheiman bridge: demigod abilities scale with your PoV passive tree (damage, healing, summons), and claw weapons get the claw swipe on every demigod. Needs Path of Valheiman. Stays off while the standalone NorseDemigodsPovBridge mod is installed. Takes effect right away.");
			cfgUnit = Plugin.Sync("6 - PoV Bridge", "StatUnit", "Fraction", new ConfigDescription("How Path of Valheiman reports its stat totals. 'Fraction' means 0.12 = +12% (confirmed for PoV 5.3.6). 'Percent' means 12 = +12%.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Fraction", "Percent" }), Array.Empty<object>()));
			cfgMode = Plugin.Sync("6 - PoV Bridge", "ScalingMode", "Scoped", new ConfigDescription("How the damage bonus is read from Path of Valheiman.\nScoped = ask PoV for DamagePct as it would apply to that ability (spell/weapon/element-specific nodes count only where they fit). Route stat names other than DamagePct are not added on top. Falls back to StatNames if PoV's internals can't be reached.\nStatNames = add up the named stats of the ability's route (the original method; per-element bonuses are invisible to it).\nScopedPlusNamed = Scoped, plus the route's named stats (use only if the log shows Scoped is missing weapon/spell-group nodes).\nTip: with Diagnostics > LogScopedCompare on, the log shows both values per ability so you can compare.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "StatNames", "Scoped", "ScopedPlusNamed" }), Array.Empty<object>()));
			cfgCritMode = Plugin.Sync("6 - PoV Bridge", "CritMode", "Off", new ConfigDescription("Whether Path of Valheiman's crit stats raise ability damage (only in Scoped modes).\nOff = ignore crit. ExpectedValue = multiply damage by the average effect of crits, as PoV's own tooltips count it: 1 + CritChance x (1.5 + CritDamagePct - 1). It is a steady average, not random crits, and is applied after the cap.\nLeave it Off if PoV already rolls real crits on NorseDemigods hits (they would be counted twice).", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "Off", "ExpectedValue" }), Array.Empty<object>()));
			cfgCapEnabled = Plugin.Sync("6 - PoV Bridge", "CapBonus", value: true, "Cap the bonus this bridge adds to one ability. Keeps specialist builds from running away.");
			cfgMaxBonus = Plugin.Sync("6 - PoV Bridge", "MaxBonus", 1.5f, new ConfigDescription("Largest bonus the bridge may add when the cap is on (1.5 = +150%, i.e. at most x2.5 damage).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10f), Array.Empty<object>()));
			cfgAlwaysStats = Plugin.Sync("6 - PoV Bridge", "DamageStats", "DamagePct:1", "Stats (name:weight, comma separated) added to EVERY routed ability, on top of its route's stats. Use for general damage.\nValid names include: DamagePct, MinionDamagePct, AttrSpirit, Dmg_Magical, Dmg_ElementalMagic, Dmg_BloodMagic, Dmg_Staves, Dmg_Scepters, Dmg_Swords, Dmg_Bows ...\nPer-element bonuses (fire/frost/lightning/poison/spirit damage) are NOT readable by name through Path of Valheiman's API.\nSet it empty to add nothing. If PoV already applies DamagePct to your demigod hits, remove DamagePct here to avoid counting it twice.");
			cfgHealingStats = Plugin.Sync("6 - PoV Bridge", "HealingStats", "HealingEffectPct:1", "Stats that boost demigod healing (Eir's abilities and similar).");
			cfgSummonStats = Plugin.Sync("6 - PoV Bridge", "SummonStats", "MinionDamagePct:1", "Extra stats that boost summon damage (wolves, golems, minions). They stack on top of the ability's own route, because NorseDemigods already folds the damage modifier into summons.");
			cfgClaws = Plugin.Sync("6 - PoV Bridge", "ClawSwipe", value: true, "Give every claw weapon the slashing claw swipe (dual-axe animation, 4-hit chain, wide arc) whatever demigod you are. NorseDemigods only does this for Fenrir. A claw weapon here = an Unarmed-skill weapon that deals mostly slash damage. Only affects your own attacks. Takes effect on your next swing.");
			cfgExtraClaws = Plugin.Sync("6 - PoV Bridge", "ExtraClawWeapons", "", "Extra weapon prefab names (comma separated, e.g. FistMyModdedClaw) that should always get the claw swipe, even if they aren't mostly slash damage.");
			cfgExtraClaws.SettingChanged += delegate
			{
				ParseExtraClaws();
			};
			ParseExtraClaws();
			cfgAttributeRoute = Plugin.Sync("7 - PoV Bridge Routes", "AttributeProcs", "Off", "Route for NorseDemigods' passive attribute damage (Bear Strength, Flame Body, Bloody Archer ...). Off = unchanged.\nRoute(s) deciding which Path of Valheiman stats boost this ability. Combine several with commas (e.g. SpellElemental, Spears).\nSpellElemental / SpellBlood = Magical + Elemental (or Blood) magic + Staves + Scepters damage. Staves / Scepters = Magical + only that group.\nEquippedWeapon = the weapon you hold. Swords, Knives, Clubs, Polearms, Spears, Axes, Unarmed, Bows, Crossbows = that weapon's damage.\nNoCategory = only the 'always' stats (Damage > Stats). Off = leave this ability exactly as NorseDemigods made it.\nScoped modes also accept element/delivery tags: Fire, Frost, Lightning, Poison, Spirit, Physical, Projectile, Area, Minion (e.g. 'SpellElemental, Frost, Area'). If no element tag is given, a best guess from the ability's name is used.");
			cfgUnknownRoute = Plugin.Sync("7 - PoV Bridge Routes", "UnknownCaller", "SpellElemental", "Route used when the bridge can't tell which ability asked for damage.\nRoute(s) deciding which Path of Valheiman stats boost this ability. Combine several with commas (e.g. SpellElemental, Spears).\nSpellElemental / SpellBlood = Magical + Elemental (or Blood) magic + Staves + Scepters damage. Staves / Scepters = Magical + only that group.\nEquippedWeapon = the weapon you hold. Swords, Knives, Clubs, Polearms, Spears, Axes, Unarmed, Bows, Crossbows = that weapon's damage.\nNoCategory = only the 'always' stats (Damage > Stats). Off = leave this ability exactly as NorseDemigods made it.\nScoped modes also accept element/delivery tags: Fire, Frost, Lightning, Poison, Spirit, Physical, Projectile, Area, Minion (e.g. 'SpellElemental, Frost, Area'). If no element tag is given, a best guess from the ability's name is used.");
			cfgLogChanges = Plugin.Sync("9 - Debug", "BridgeLogFactorChanges", value: false, "Log the current multipliers and raw stat values whenever they change.");
			cfgLogAllStats = Plugin.Sync("9 - Debug", "BridgeLogAllActiveStats", value: false, "Also log EVERY Path of Valheiman stat that is currently non-zero (whenever that list changes).");
			cfgLogRouting = Plugin.Sync("9 - Debug", "BridgeLogRouting", value: false, "Log which route each ability used and its bonus (first time, and whenever it changes).");
			cfgLogHits = Plugin.Sync("9 - Debug", "BridgeLogHits", value: false, "Log every hit you land, at three stages: as the attack launches it, as it arrives at the target (after other mods such as Path of Valheiman have changed it), and after the target's resistances and armor. Shows exactly where a damage number grows or shrinks. Needs a restart to turn on or off. Turn it off when you're done: it writes a line per stage per hit.");
			cfgLogValues = Plugin.Sync("9 - Debug", "BridgeLogDamageValues", value: false, "For each ability, log the damage modifier NorseDemigods passed in and the value the bridge returned (and calls it ignored, with the reason). Used to find out why a tooltip or hit doesn't change.");
			cfgLogScoped = Plugin.Sync("9 - Debug", "BridgeLogScopedCompare", value: false, "For each ability, log what Path of Valheiman's scoped lookup returns (damage, unscoped damage, 'more' multiplier, crit) next to the named-stat sum. Used to check the Scoped modes before switching to them.");
			string[] knownAbilities = KnownAbilities;
			foreach (string ability in knownAbilities)
			{
				BindAbilityRoute(ability);
			}
			ParseAll();
			cfgAlwaysStats.SettingChanged += delegate
			{
				ParseAll();
			};
			cfgHealingStats.SettingChanged += delegate
			{
				ParseAll();
			};
			cfgSummonStats.SettingChanged += delegate
			{
				ParseAll();
			};
		}

		public static void Start()
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Expected O, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			if (Chainloader.PluginInfos.ContainsKey("OblitusEs.DemigodsPovBridge"))
			{
				Log.LogInfo("The standalone NorseDemigodsPovBridge mod is installed, so the built-in bridge stays off. Remove that mod to use the built-in one.");
				return;
			}
			PatchClaws(new Harmony("OblitusEs.DemigodsPovBridge.claws"));
			PatchHitLog(new Harmony("OblitusEs.DemigodsPovBridge.hitlog"));
			if (!PovHalls.Installed)
			{
				if (cfgEnabled.Value)
				{
					Log.LogWarning("Enabled, but Path of Valheiman is not installed: only the claw swipe is active.");
				}
			}
			else
			{
				if (!ResolvePovApi())
				{
					return;
				}
				Type type = AccessTools.TypeByName("NorseDemigods.Helper");
				if (type == null)
				{
					Log.LogWarning("NorseDemigods.Helper not found. Bridge inactive.");
					return;
				}
				Harmony harmony = new Harmony("OblitusEs.DemigodsPovBridge");
				int num = 0;
				num += Patch(harmony, type, "GetDamageModifier", "DamagePostfix");
				num += Patch(harmony, type, "GetHealingModifier", "HealingPostfix");
				num += Patch(harmony, type, "GetSummonDamageModifier", "SummonPostfix");
				ready = num > 0;
				scopedReady = ResolveScoped();
				if (!scopedReady && cfgMode.Value != "StatNames")
				{
					Log.LogWarning("ScalingMode=" + cfgMode.Value + " needs Path of Valheiman's scoped lookup, which could not be bound. Falling back to StatNames.");
				}
				Log.LogInfo(string.Format("{0}: {1}/3 NorseDemigods modifiers hooked ", cfgEnabled.Value ? "On" : "Off (turn on with 6 - PoV Bridge > Enabled)", num) + "(mode=" + (Scoped() ? cfgMode.Value : "StatNames") + ", scoped lookup " + (scopedReady ? "available" : "unavailable") + ", unit=" + cfgUnit.Value + ", cap=" + (cfgCapEnabled.Value ? ("+" + (cfgMaxBonus.Value * 100f).ToString("0") + "%") : "off") + ").");
				if (cfgEnabled.Value)
				{
					WarnUnknownStats();
				}
			}
		}

		private static bool ResolvePovApi()
		{
			try
			{
				Type type = AccessTools.TypeByName("Valheiman.Api.Stats");
				if (type == null)
				{
					Log.LogWarning("Path of Valheiman API (Valheiman.Api.Stats) not found. Is PathOfValheiman installed and up to date? Bridge inactive.");
					return false;
				}
				MethodInfo methodInfo = AccessTools.Method(type, "Get", new Type[1] { typeof(string) }, (Type[])null);
				if (methodInfo == null || methodInfo.ReturnType != typeof(float))
				{
					Log.LogWarning("Valheiman.Api.Stats.Get(string) -> float not found; PoV's API changed. Bridge inactive.");
					return false;
				}
				povGet = (Func<string, float>)Delegate.CreateDelegate(typeof(Func<string, float>), methodInfo);
				try
				{
					if (AccessTools.Property(type, "Names")?.GetValue(null, null) is IEnumerable enumerable)
					{
						knownStats = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
						knownList = new List<string>();
						foreach (object item in enumerable)
						{
							string text = item as string;
							if (!string.IsNullOrEmpty(text))
							{
								knownStats.Add(text);
								knownList.Add(text);
							}
						}
					}
				}
				catch (Exception)
				{
					knownStats = null;
				}
				return true;
			}
			catch (Exception ex2)
			{
				Log.LogWarning("Could not bind Path of Valheiman API: " + ex2.GetBaseException().Message);
				return false;
			}
		}

		private static int Patch(Harmony harmony, Type helper, string method, string postfixName)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			try
			{
				MethodInfo methodInfo = AccessTools.Method(helper, method, new Type[1] { typeof(Player) }, (Type[])null);
				if (methodInfo == null)
				{
					Log.LogWarning("NorseDemigods.Helper." + method + "(Player) not found; NorseDemigods may have changed.");
					return 0;
				}
				MethodInfo method2 = typeof(PovBridge).GetMethod(postfixName, BindingFlags.Static | BindingFlags.NonPublic);
				harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return 1;
			}
			catch (Exception ex)
			{
				Log.LogWarning("Failed to patch " + method + ": " + ex.GetBaseException().Message);
				return 0;
			}
		}

		private static void DamagePostfix(Player owner, ref float __result)
		{
			if (!Active(owner))
			{
				LogSkipped(owner);
				return;
			}
			float before = __result;
			string caller;
			string text = Identify(out caller);
			string value;
			string label;
			if (text == "Ability")
			{
				value = BindAbilityRoute(caller).Value;
				label = caller;
			}
			else if (text == "Attribute")
			{
				value = cfgAttributeRoute.Value;
				label = caller + " (attribute)";
			}
			else
			{
				value = cfgUnknownRoute.Value;
				label = "(unknown caller)";
			}
			Route route = ParseRoute(value);
			if (route.Off)
			{
				LogRoute(label, value, 1f);
				return;
			}
			string ability = ((text == "Ability" || text == "Attribute") ? caller : null);
			float num = RoutedFactor(route, owner, ability);
			__result *= num;
			LogValue(label, before, __result, num);
			LogRoute(label, value, num);
			LogScopedCompare(label, route, owner, ability);
		}

		private static void HealingPostfix(Player owner, ref float __result)
		{
			if (Active(owner))
			{
				__result *= ToFactor(SumHealing());
			}
		}

		private static void SummonPostfix(Player owner, ref float __result)
		{
			if (Active(owner))
			{
				__result *= ToFactor(SumSummon());
			}
		}

		private static bool Active(Player owner)
		{
			return ready && cfgEnabled.Value && (Object)(object)owner != (Object)null && (Object)(object)owner == (Object)(object)Player.m_localPlayer;
		}

		private static string Identify(out string caller)
		{
			caller = null;
			try
			{
				StackFrame[] frames = new StackTrace(fNeedFileInfo: false).GetFrames();
				if (frames == null)
				{
					return "";
				}
				int num = Math.Min(frames.Length, 16);
				for (int i = 0; i < num; i++)
				{
					MethodBase method = frames[i].GetMethod();
					Type type = ((method == null) ? null : method.DeclaringType);
					if (type == null && method != null)
					{
						Match match = PatchedNdMethod.Match(method.Name ?? "");
						if (match.Success)
						{
							caller = match.Groups[2].Value;
							return (match.Groups[1].Value == "Abilities") ? "Ability" : "Attribute";
						}
						continue;
					}
					while (type != null && type.DeclaringType != null)
					{
						type = type.DeclaringType;
					}
					if (!(type == null))
					{
						string text = type.Namespace;
						if (text == "NorseDemigods.Abilities")
						{
							caller = type.Name;
							return "Ability";
						}
						if (text == "NorseDemigods.Attributes")
						{
							caller = type.Name;
							return "Attribute";
						}
					}
				}
			}
			catch (Exception)
			{
			}
			return "";
		}

		private static ConfigEntry<string> BindAbilityRoute(string ability)
		{
			if (abilityRoutes.TryGetValue(ability, out var value))
			{
				return value;
			}
			value = Config_Bind("7 - PoV Bridge Routes", ability, DefaultRouteFor(ability), "Route(s) deciding which Path of Valheiman stats boost this ability. Combine several with commas (e.g. SpellElemental, Spears).\nSpellElemental / SpellBlood = Magical + Elemental (or Blood) magic + Staves + Scepters damage. Staves / Scepters = Magical + only that group.\nEquippedWeapon = the weapon you hold. Swords, Knives, Clubs, Polearms, Spears, Axes, Unarmed, Bows, Crossbows = that weapon's damage.\nNoCategory = only the 'always' stats (Damage > Stats). Off = leave this ability exactly as NorseDemigods made it.\nScoped modes also accept element/delivery tags: Fire, Frost, Lightning, Poison, Spirit, Physical, Projectile, Area, Minion (e.g. 'SpellElemental, Frost, Area'). If no element tag is given, a best guess from the ability's name is used.");
			abilityRoutes[ability] = value;
			return value;
		}

		private static ConfigEntry<string> Config_Bind(string section, string key, string def, string desc)
		{
			return Plugin.Sync(section, key, def, desc);
		}

		private static string DefaultRouteFor(string ability)
		{
			switch (ability)
			{
			case "AbilitySlash":
			case "AbilityCleave":
			case "AbilityWhirlwind":
			case "AbilityStagger":
			case "AbilityBloodSlash":
				return "EquippedWeapon";
			case "AbilityInfusedArrow":
				return "Bows";
			case "AbilityWolf":
				return "Off";
			default:
				return "SpellElemental";
			}
		}

		private static Route ParseRoute(string text)
		{
			text = text ?? "";
			if (routeCache.TryGetValue(text, out var value))
			{
				return value;
			}
			value = new Route();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			string[] array = text.Split(new char[3] { ',', ';', '+' }, StringSplitOptions.RemoveEmptyEntries);
			foreach (string text2 in array)
			{
				string text3 = text2.Trim();
				if (text3.Length == 0)
				{
					continue;
				}
				if (string.Equals(text3, "Off", StringComparison.OrdinalIgnoreCase))
				{
					value.Off = true;
				}
				else
				{
					if (string.Equals(text3, "NoCategory", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					string[] value2;
					if (string.Equals(text3, "EquippedWeapon", StringComparison.OrdinalIgnoreCase))
					{
						value.Equipped = true;
					}
					else if (RouteStats.TryGetValue(text3, out value2))
					{
						string[] array2 = value2;
						foreach (string item in array2)
						{
							hashSet.Add(item);
						}
						if (RouteSubjects.TryGetValue(text3, out var value3))
						{
							string[] array3 = value3;
							foreach (string item2 in array3)
							{
								value.Subjects.Add(item2);
							}
						}
					}
					else if (ElementTokens.Contains(text3))
					{
						value.Subjects.Add(text3);
						value.ExplicitElement = true;
					}
					else if (TagTokens.Contains(text3))
					{
						value.Subjects.Add(text3);
					}
					else
					{
						Log?.LogWarning("Unknown route '" + text3 + "' (valid: SpellElemental, SpellBlood, Staves, Scepters, EquippedWeapon, Swords, Knives, Clubs, Polearms, Spears, Axes, Unarmed, Bows, Crossbows, NoCategory, Off, Fire, Frost, Lightning, Poison, Spirit, Physical, Projectile, Area, Minion). Ignored.");
					}
				}
			}
			value.Stats.AddRange(hashSet);
			routeCache[text] = value;
			return value;
		}

		private static string HeldSkill(Player player)
		{
			try
			{
				ItemData currentWeapon = ((Humanoid)player).GetCurrentWeapon();
				if (currentWeapon == null || currentWeapon.m_shared == null)
				{
					return null;
				}
				return ((object)Unsafe.As<SkillType, SkillType>(ref currentWeapon.m_shared.m_skillType)/*cast due to .constrained prefix*/).ToString();
			}
			catch (Exception)
			{
				return null;
			}
		}

		private static string EquippedRouteName(Player player)
		{
			string text = HeldSkill(player);
			if (text == null)
			{
				return null;
			}
			string text2 = text;
			string text3 = text2;
			if (!(text3 == "ElementalMagic"))
			{
				if (text3 == "BloodMagic")
				{
					return "SpellBlood";
				}
				return text;
			}
			return "SpellElemental";
		}

		private static string[] EquippedStats(Player player)
		{
			string text = EquippedRouteName(player);
			if (text != null && RouteStats.TryGetValue(text, out var value))
			{
				return value;
			}
			return NoStats;
		}

		private static float RoutedFactor(Route route, Player owner, string ability)
		{
			if (Scoped() && TryScopedSum(route, owner, ability, out var sum, out var crit))
			{
				return ToFactor(sum) * crit;
			}
			return ToFactor(NamedSum(route, owner));
		}

		private static float NamedSum(Route route, Player owner)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			float num = SumRules(alwaysRules, hashSet);
			foreach (string stat in route.Stats)
			{
				if (hashSet.Add(stat))
				{
					num += Read(stat);
				}
			}
			if (route.Equipped)
			{
				string[] array = EquippedStats(owner);
				foreach (string text in array)
				{
					if (hashSet.Add(text))
					{
						num += Read(text);
					}
				}
			}
			return num;
		}

		private static bool Scoped()
		{
			return scopedReady && cfgMode != null && cfgMode.Value != "StatNames";
		}

		private static bool ResolveScoped()
		{
			try
			{
				Type type = AccessTools.TypeByName("PassiveTree.Progress");
				Type type2 = AccessTools.TypeByName("PassiveTree.StatSet");
				tStat = AccessTools.TypeByName("PassiveTree.Stat");
				tSubject = AccessTools.TypeByName("PassiveTree.Subject");
				tSubjects = AccessTools.TypeByName("PassiveTree.Subjects");
				List<string> list = new List<string>();
				if (type == null)
				{
					list.Add("Progress");
				}
				if (type2 == null)
				{
					list.Add("StatSet");
				}
				if (tStat == null)
				{
					list