Decompiled source of DeepNorthMagic v1.18.2

DeepNorthMagic.dll

Decompiled 2 days 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.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace DeepNorthMagic;

[BepInPlugin("zonzcroupi.DeepNorthMagic", "DeepNorthMagic", "1.18.2")]
public class Plugin : BaseUnityPlugin
{
	public const string PluginGuid = "zonzcroupi.DeepNorthMagic";

	public const string PluginName = "DeepNorthMagic";

	public const string PluginVersion = "1.18.2";

	internal static ManualLogSource Log;

	internal static ConfigFile Cfg;

	internal static ConfigEntry<bool> Enabled;

	internal static ConfigEntry<int> MaxSummonSpells;

	internal static ConfigEntry<string> SummonCreatures;

	internal static ConfigEntry<bool> DropScrolls;

	internal static ConfigEntry<int> SummonMaxAlive;

	internal static ConfigEntry<KeyCode> UnsummonKey;

	internal static ConfigEntry<KeyCode> ReloadKey;

	internal static ConfigEntry<bool> AutoReload;

	internal static ConfigEntry<float> UnsummonRadius;

	internal static ConfigEntry<float> SummonEitrMultiplier;

	internal static ConfigEntry<float> SummonHealthMultiplier;

	internal static ConfigEntry<float> TrollDamageMultiplier;

	internal static ConfigEntry<float> SpiritDamageMultiplier;

	internal static ConfigEntry<float> SummonDamageMultiplier;

	internal static ConfigEntry<bool> RemoveHexenSpiral;

	internal static ConfigEntry<string> HexenSpiralNames;

	internal static ConfigEntry<bool> HexenRemoveLoopingFx;

	internal static ConfigEntry<float> HexenFxRadius;

	internal static ConfigEntry<bool> BlackSummons;

	internal static ConfigEntry<float> StaffDamageMultiplier;

	internal static ConfigEntry<bool> StaffNoArmorRequirement;

	internal static ConfigEntry<bool> AllStaffsNoArmorRequirement;

	internal static ConfigEntry<bool> HideNoSpellMessage;

	internal static ConfigEntry<float> RobeRedRange;

	internal static ConfigEntry<float> PatternSensitivity;

	internal static ConfigEntry<float> ArmorMultiplier;

	internal static ConfigEntry<bool> DiscoveryLog;

	internal static ConfigEntry<float> SetEitrBonus;

	internal const string DefaultCreatureList = "ElakingMole|Eyeless One|Eyeless,Elaking,BlobFrost|FrostBlob|Frost Blob|Blob de givre,Barka,JotunWarrior|Krigen,JotunWitch|Hexen,BlobMork";

	private const string PreviousCreatureList0 = "ElakingMole|Eyeless One|Eyeless,Elaking,Barka,JotunWarrior|Krigen,JotunWitch|Hexen";

	private const string PreviousCreatureList = "ElakingMole|Eyeless,Elaking,FrostBlob|BlobFrost|Blob de givre|Blob givré|Blob glacé,Barka,Krigen,Hexen";

	private const string Previous2CreatureList = "ElakingMole|Eyeless One|Eyeless,Elaking,BlobFrost|FrostBlob|Frost Blob|Blob de givre,Barka,JotunWarrior|Krigen,JotunWitch|Hexen,BlobMork|Shapeless Pulp,BlobMorkMini|Tiny Pulp,FallenWarrior|Fallen Warrior";

	private const string OldCreatureList = "Eyeless,Barka,Gammeltroll,Krigen,Hexen";

	private static FileSystemWatcher watcher;

	private static volatile bool reloadRequested;

	private static volatile int reloadTick;

	private static MethodInfo keyDown;

	private static bool keyLookupFailed;

	private void Awake()
	{
		//IL_0131: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Expected O, but got Unknown
		//IL_016e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0178: Expected O, but got Unknown
		//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fe: Expected O, but got Unknown
		//IL_0236: Unknown result type (might be due to invalid IL or missing references)
		//IL_0240: Expected O, but got Unknown
		//IL_02bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c7: Expected O, but got Unknown
		//IL_0348: Unknown result type (might be due to invalid IL or missing references)
		//IL_0352: Expected O, but got Unknown
		//IL_04fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0508: Expected O, but got Unknown
		//IL_0532: Unknown result type (might be due to invalid IL or missing references)
		//IL_0538: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		Cfg = ((BaseUnityPlugin)this).Config;
		Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Enable the mod. Must be identical on the server and on every client.");
		DiscoveryLog = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "DiscoveryLog", true, "Write in the BepInEx log which base items, materials and Deep North creatures were detected.");
		SetEitrBonus = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Balance", "SetBonusMaxEitr", 20f, "Extra max Eitr when the 4 pieces of the set are worn.");
		ArmorMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Balance", "ArmorMultiplier", 1f, "Multiplier applied to the armor value of the Deep North mage set.");
		AllStaffsNoArmorRequirement = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Balance", "AllMagicRevampStaffsNoArmorRequirement", true, "true = the staffs and wands of MagicRevamp can also be equipped without wearing magic armor (the 'not enough magic armor' limit is lifted for them too). Needs a game restart.");
		StaffNoArmorRequirement = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Balance", "StaffNoArmorRequirement", true, "true = the Fracture Staff can be equipped without wearing magic armor (removes the 'not enough magic armor' limit of the game for this staff). Needs a game restart.");
		StaffDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Balance", "StaffDamageMultiplier", 1f, "Multiplier applied to the Fracture Staff damage.");
		RobeRedRange = ((BaseUnityPlugin)this).Config.Bind<float>("8 - Look", "RobeRedHueRange", 14f, new ConfigDescription("Hue range (degrees around pure red) turned white on the robe. Increase if some red stays, decrease if the brown belt turns white.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(4f, 40f), Array.Empty<object>()));
		PatternSensitivity = ((BaseUnityPlugin)this).Config.Bind<float>("8 - Look", "CapePatternSensitivity", 0.18f, new ConfigDescription("Color distance from the cape's main color above which a pixel is a pattern (turned black). Lower = more black, higher = less black.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 0.6f), Array.Empty<object>()));
		HideNoSpellMessage = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "HideNoSpellMessage", true, "Hide the \"no spell scroll in your inventory\" message (MagicRevamp) and the \"out of spells\" message of the game when the staff has no scroll.");
		SummonEitrMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Summons", "EitrCostMultiplier", 1f, "Multiplier applied to the Eitr cost of the summon spells.");
		SpiritDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>(Content.SummonSection("SpiritCaller"), "Power_DamageMultiplier", 1.5f, new ConfigDescription("Power of the spirits summoned with the Spirit Caller scroll: every hit they deal is multiplied by this value (1 = normal damage, 2 = double, 0.5 = half). Applies at once, also to spirits already summoned.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
		TrollDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>(Content.SummonSection("Troll"), "Power_DamageMultiplier", 3f, new ConfigDescription("Power of the summoned troll: every hit it deals is multiplied by this value (1 = normal damage, 2 = double, 0.5 = half). Applies at once, also to trolls already summoned.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
		BlackSummons = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Summons", "BlackSummons", true, "true = the creatures you summon with the scrolls (Eyeless, Elaking, Barka, Krigen, Hexen) are black. Only the summoned ones: the wild creatures are not changed. Needs a game restart.");
		HexenRemoveLoopingFx = ((BaseUnityPlugin)this).Config.Bind<bool>(Content.SummonSection("JotunWitch"), "Hexen_RemoveLoopingEffects", true, "true = every looping particle effect that appears on the summoned Hexen while it lives (the magic spiral around it) is removed. The Hexen has none of its own, so this only removes effects added by the game at runtime. The log lists them with 'Hexen effect'.");
		HexenFxRadius = ((BaseUnityPlugin)this).Config.Bind<float>(Content.SummonSection("JotunWitch"), "Hexen_EffectRadius", 4f, new ConfigDescription("Radius (meters) around the summoned Hexen where independent looping effects (not attached to her) are removed: the magic spiral is such an effect. 0 = off.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
		RemoveHexenSpiral = ((BaseUnityPlugin)this).Config.Bind<bool>(Content.SummonSection("JotunWitch"), "Hexen_RemoveSpiral", true, "true = the summoned Hexen (Jotun witch) loses the magic spiral that surrounds it. Needs a game restart.");
		HexenSpiralNames = ((BaseUnityPlugin)this).Config.Bind<string>(Content.SummonSection("JotunWitch"), "Hexen_SpiralNames", "spiral|swirl|vortex|circle|rune|aura|orbit|ring|magic|glyph|sigil", "Looping particle effects of the Hexen whose name matches one of these words (separated by |) are removed. The log lists all the particle effects of the Hexen with the word 'Hexen particle': add the name of the spiral here if it is not removed.");
		SummonDamageMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Summons", "DamageMultiplier", 1f, new ConfigDescription("Damage multiplier applied to every summoned creature (1 = normal). Each creature also has its own DamageMultiplier in its own section \"4 - Summon: <name>\" (key Power_DamageMultiplier).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 100f), Array.Empty<object>()));
		SummonHealthMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Summons", "HealthMultiplier", 1f, "Multiplier applied to the health of summoned creatures.");
		SummonMaxAlive = ((BaseUnityPlugin)this).Config.Bind<int>("3 - Summons", "MaxAlivePerSpell", 2, "How many creatures of the same spell YOU can have alive at once (each player has their own limit). When you summon one more, your oldest one disappears.");
		MaxSummonSpells = ((BaseUnityPlugin)this).Config.Bind<int>("3 - Summons", "MaxSummonSpells", 20, "Maximum number of summon spells created automatically.");
		if (MaxSummonSpells.Value == 8)
		{
			MaxSummonSpells.Value = 20;
		}
		SummonCreatures = ((BaseUnityPlugin)this).Config.Bind<string>("3 - Summons", "CreatureList", "ElakingMole|Eyeless One|Eyeless,Elaking,BlobFrost|FrostBlob|Frost Blob|Blob de givre,Barka,JotunWarrior|Krigen,JotunWitch|Hexen,BlobMork", "Comma separated creature prefab names (or parts of names) to create summon scrolls for. Use | for alternative names of the same creature. Empty = automatic detection of the creatures that spawn in the Deep North.");
		if (SummonCreatures.Value == "Eyeless,Barka,Gammeltroll,Krigen,Hexen" || SummonCreatures.Value == "ElakingMole|Eyeless One|Eyeless,Elaking,Barka,JotunWarrior|Krigen,JotunWitch|Hexen" || SummonCreatures.Value == "ElakingMole|Eyeless,Elaking,FrostBlob|BlobFrost|Blob de givre|Blob givré|Blob glacé,Barka,Krigen,Hexen" || SummonCreatures.Value == "ElakingMole|Eyeless One|Eyeless,Elaking,BlobFrost|FrostBlob|Frost Blob|Blob de givre,Barka,JotunWarrior|Krigen,JotunWitch|Hexen,BlobMork|Shapeless Pulp,BlobMorkMini|Tiny Pulp,FallenWarrior|Fallen Warrior")
		{
			SummonCreatures.Value = "ElakingMole|Eyeless One|Eyeless,Elaking,BlobFrost|FrostBlob|Frost Blob|Blob de givre,Barka,JotunWarrior|Krigen,JotunWitch|Hexen,BlobMork";
		}
		ReloadKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("1 - General", "ReloadConfigKey", (KeyCode)288, "Key that re-reads this config file and applies it in game, without restarting.");
		AutoReload = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "AutoReloadOnSave", true, "Apply the config automatically as soon as the file is saved while the game is running.");
		UnsummonKey = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("3 - Summons", "UnsummonKey", (KeyCode)121, "Key that dismisses (unsummons) your summoned creatures.");
		UnsummonRadius = ((BaseUnityPlugin)this).Config.Bind<float>("3 - Summons", "UnsummonRadius", 100f, new ConfigDescription("Only summoned creatures closer than this distance (meters) are dismissed. 0 = no limit.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 10000f), Array.Empty<object>()));
		DropScrolls = ((BaseUnityPlugin)this).Config.Bind<bool>("3 - Summons", "DropScrolls", true, "Master switch: creatures of the list can drop their own summon scroll (details per creature in its own section \"4 - Summon: <name>\", keys Loot_*).");
		Harmony val = new Harmony("zonzcroupi.DeepNorthMagic");
		Type[] array = new Type[13]
		{
			typeof(ZNetScene_Awake_Patch),
			typeof(Player_Food_Patch),
			typeof(VisEquipment_AttachItem_Patch),
			typeof(MessageHud_ShowMessage_Patch),
			typeof(Attack_UseAmmo_Patch),
			typeof(ObjectDB_Awake_Patch),
			typeof(ObjectDB_Copy_Patch),
			typeof(Localization_Setup_Patch),
			typeof(Character_OnDeath_Patch),
			typeof(CharacterDrop_OnDeath_Patch),
			typeof(Character_Awake_Patch),
			typeof(Character_Damage_Patch),
			typeof(CharacterDrop_GenerateDropList_Patch)
		};
		foreach (Type type in array)
		{
			try
			{
				val.CreateClassProcessor(type).Patch();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Patch " + type.Name + " failed: " + ex.Message));
			}
		}
		try
		{
			string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
			watcher = new FileSystemWatcher(Path.GetDirectoryName(configFilePath), Path.GetFileName(configFilePath));
			watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite;
			FileSystemEventHandler value = delegate
			{
				if (AutoReload.Value)
				{
					reloadTick = Environment.TickCount;
					reloadRequested = true;
				}
			};
			watcher.Changed += value;
			watcher.Created += value;
			watcher.EnableRaisingEvents = true;
		}
		catch (Exception ex2)
		{
			Log.LogWarning((object)("Config file watcher unavailable (use the reload key): " + ex2.Message));
		}
		Log.LogInfo((object)"DeepNorthMagic 1.18.2 loaded");
	}

	private static bool KeyDown(KeyCode k)
	{
		//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
		if (keyLookupFailed)
		{
			return false;
		}
		if (keyDown == null)
		{
			Type type = Type.GetType("UnityEngine.Input, UnityEngine.InputLegacyModule") ?? Type.GetType("UnityEngine.Input, UnityEngine");
			if (type != null)
			{
				keyDown = type.GetMethod("GetKeyDown", new Type[1] { typeof(KeyCode) });
			}
			if (keyDown == null)
			{
				Log.LogError((object)"UnityEngine.Input.GetKeyDown not found: the keys of the mod are disabled.");
				keyLookupFailed = true;
				return false;
			}
		}
		return (bool)keyDown.Invoke(null, new object[1] { k });
	}

	private void Update()
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Unknown result type (might be due to invalid IL or missing references)
		if (Enabled.Value)
		{
			if (reloadRequested && Environment.TickCount - reloadTick > 700)
			{
				reloadRequested = false;
				Content.ReloadConfig();
			}
			if (KeyDown(ReloadKey.Value))
			{
				Content.ReloadConfig();
			}
			if (KeyDown(UnsummonKey.Value))
			{
				Content.Unsummon();
			}
		}
	}

	private void OnDestroy()
	{
		if (watcher != null)
		{
			watcher.EnableRaisingEvents = false;
			watcher.Dispose();
			watcher = null;
		}
	}
}
[HarmonyPatch(typeof(ZNetScene), "Awake")]
internal static class ZNetScene_Awake_Patch
{
	private static void Prefix(ZNetScene __instance)
	{
		Content.Build(force: true);
		Content.AddToScene(__instance);
	}
}
[HarmonyPatch(typeof(Player), "GetTotalFoodValue")]
internal static class Player_Food_Patch
{
	private static void Postfix(Player __instance, ref float eitr)
	{
		try
		{
			eitr += Content.EquipmentEitr(__instance);
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(VisEquipment), "AttachItem")]
internal static class VisEquipment_AttachItem_Patch
{
	private static void Prefix(int __0, ref int __1)
	{
		if (Content.WhiteStyle.Count == 0 || (Object)(object)ObjectDB.instance == (Object)null)
		{
			return;
		}
		try
		{
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(__0);
			if ((Object)(object)itemPrefab != (Object)null && Content.WhiteStyle.TryGetValue(((Object)itemPrefab).name, out var value))
			{
				__1 = value;
			}
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(Attack), "UseAmmo")]
internal static class Attack_UseAmmo_Patch
{
	private static readonly FieldInfo FWeapon = AccessTools.Field(typeof(Attack), "m_weapon");

	private static readonly FieldInfo FChar = AccessTools.Field(typeof(Attack), "m_character");

	private static readonly FieldInfo FAmmo = AccessTools.Field(typeof(Attack), "m_ammoItem");

	private static bool Prefix(Attack __instance, out ItemData ammoItem, ref bool __result)
	{
		ammoItem = null;
		if (Content.UnlimitedScrolls.Count == 0)
		{
			return true;
		}
		try
		{
			object? value = FWeapon.GetValue(__instance);
			ItemData val = (ItemData)((value is ItemData) ? value : null);
			object? value2 = FChar.GetValue(__instance);
			Humanoid val2 = (Humanoid)((value2 is Humanoid) ? value2 : null);
			if (val == null || (Object)(object)val2 == (Object)null || string.IsNullOrWhiteSpace(val.m_shared.m_ammoType))
			{
				return true;
			}
			ItemData ammoItem2 = val2.GetAmmoItem();
			if (ammoItem2 == null || !val2.GetInventory().ContainsItem(ammoItem2) || ammoItem2.m_shared.m_ammoType != val.m_shared.m_ammoType)
			{
				ammoItem2 = val2.GetInventory().GetAmmoItem(val.m_shared.m_ammoType, (string)null);
			}
			if (ammoItem2 == null || (Object)(object)ammoItem2.m_dropPrefab == (Object)null || !Content.UnlimitedScrolls.Contains(((Object)ammoItem2.m_dropPrefab).name))
			{
				return true;
			}
			FAmmo.SetValue(__instance, ammoItem2);
			ammoItem = ammoItem2;
			__result = true;
			return false;
		}
		catch
		{
			ammoItem = null;
			return true;
		}
	}
}
[HarmonyPatch(typeof(MessageHud), "ShowMessage")]
internal static class MessageHud_ShowMessage_Patch
{
	private static string localizedNoSpell;

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

	private static bool Prefix(string text)
	{
		try
		{
			if (!string.IsNullOrEmpty(text) && (text.IndexOf("armor", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("armour", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("armure", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("magic", StringComparison.OrdinalIgnoreCase) >= 0 || text.IndexOf("cantequip", StringComparison.OrdinalIgnoreCase) >= 0) && logged.Add(text))
			{
				Plugin.Log.LogInfo((object)("Message about armor/equip: '" + text + "'\n" + new StackTrace(1, fNeedFileInfo: false)));
			}
		}
		catch
		{
		}
		if (!Plugin.HideNoSpellMessage.Value || string.IsNullOrEmpty(text))
		{
			return true;
		}
		try
		{
			if (text.IndexOf("bmr_nospellinventory", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			if (text.IndexOf("$ammo_spells", StringComparison.OrdinalIgnoreCase) >= 0 && text.IndexOf("msg_outof", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
			if (localizedNoSpell == null && Localization.instance != null)
			{
				string text2 = Localization.instance.Localize("$bmr_nospellinventory");
				localizedNoSpell = ((text2.Length > 0 && text2[0] != '[') ? text2 : "");
			}
			if (!string.IsNullOrEmpty(localizedNoSpell) && text.IndexOf(localizedNoSpell, StringComparison.OrdinalIgnoreCase) >= 0)
			{
				return false;
			}
		}
		catch
		{
		}
		return true;
	}
}
[HarmonyPatch(typeof(CharacterDrop), "OnDeath")]
internal static class CharacterDrop_OnDeath_Patch
{
	private static readonly FieldInfo FChar = AccessTools.Field(typeof(CharacterDrop), "m_character");

	private static void Postfix(CharacterDrop __instance)
	{
		try
		{
			Character ch = (Character)((FChar != null) ? ((object)/*isinst with value type is only supported in some contexts*/) : ((object)((Component)__instance).GetComponent<Character>()));
			Content.DropForDeadCreature(ch);
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(Character), "OnDeath")]
internal static class Character_OnDeath_Patch
{
	private static void Postfix(Character __instance)
	{
		try
		{
			Content.DropForDeadCreature(__instance);
		}
		catch
		{
		}
	}
}
public class DNM_StripFx : MonoBehaviour
{
	private Regex rx;

	private bool allLoop;

	private float next;

	private readonly HashSet<int> seen = new HashSet<int>();

	private readonly HashSet<Renderer> initial = new HashSet<Renderer>();

	private float nextWorld;

	private readonly List<GameObject> removedRoots = new List<GameObject>();

	private void Start()
	{
		allLoop = Plugin.HexenRemoveLoopingFx.Value;
		try
		{
			rx = new Regex(Plugin.HexenSpiralNames.Value, RegexOptions.IgnoreCase);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("HexenSpiralNames is not valid: " + ex.Message));
		}
		if (!Plugin.RemoveHexenSpiral.Value)
		{
			((Behaviour)this).enabled = false;
			return;
		}
		Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<Renderer>(true);
		foreach (Renderer item in componentsInChildren)
		{
			if (0 == 0)
			{
				initial.Add(item);
			}
		}
	}

	private void ScanWorld()
	{
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		float value = Plugin.HexenFxRadius.Value;
		if (value <= 0f || Time.time < nextWorld)
		{
			return;
		}
		nextWorld = Time.time + 1f;
		ParticleSystem[] array = Object.FindObjectsOfType<ParticleSystem>();
		foreach (ParticleSystem val in array)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			Transform root = ((Component)val).transform.root;
			if ((Object)(object)root == (Object)(object)((Component)this).transform)
			{
				continue;
			}
			float num = Vector3.Distance(((Component)val).transform.position, ((Component)this).transform.position);
			if (num > value || (Object)(object)((Component)root).GetComponentInChildren<Character>(true) != (Object)null || (Object)(object)((Component)root).GetComponentInChildren<Piece>(true) != (Object)null || (Object)(object)((Component)root).GetComponentInChildren<ItemDrop>(true) != (Object)null)
			{
				continue;
			}
			MainModule main = val.main;
			bool loop = ((MainModule)(ref main)).loop;
			bool flag = (Object)(object)root == (Object)(object)((Component)val).transform || ((Object)root).name.EndsWith("(Clone)", StringComparison.Ordinal);
			bool flag2 = loop && flag && allLoop;
			if (seen.Add(((Object)val).GetInstanceID()))
			{
				Plugin.Log.LogInfo((object)("Hexen effect (world): root='" + ((Object)root).name + "' particles='" + ((Object)val).name + "' distance=" + num.ToString("0.0") + " loop=" + loop + " independent=" + flag + (flag2 ? " -> removed" : "")));
			}
			if (!flag2)
			{
				continue;
			}
			try
			{
				GameObject gameObject = ((Component)root).gameObject;
				ZNetView component = gameObject.GetComponent<ZNetView>();
				if ((Object)(object)component != (Object)null && component.IsValid())
				{
					if (!component.IsOwner())
					{
						component.ClaimOwnership();
					}
					ZNetScene.instance.Destroy(gameObject);
				}
				else
				{
					Object.Destroy((Object)(object)gameObject);
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Hexen effect not removed: " + ex.Message));
			}
		}
	}

	private string PathOf(Transform t)
	{
		string text = ((Object)t).name;
		Transform parent = t.parent;
		while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)((Component)this).transform)
		{
			text = ((Object)parent).name + "/" + text;
			parent = parent.parent;
		}
		return text;
	}

	private void Update()
	{
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		if (Time.time < next)
		{
			return;
		}
		next = Time.time + 0.4f;
		ParticleSystem[] componentsInChildren = ((Component)this).GetComponentsInChildren<ParticleSystem>(true);
		foreach (ParticleSystem val in componentsInChildren)
		{
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			string text = PathOf(((Component)val).transform);
			MainModule main = val.main;
			bool loop = ((MainModule)(ref main)).loop;
			bool flag = loop && (allLoop || (rx != null && rx.IsMatch(text)));
			if (seen.Add(((Object)val).GetInstanceID()))
			{
				Plugin.Log.LogInfo((object)("Hexen effect (particles): " + text + " loop=" + loop + (flag ? " -> removed" : "")));
			}
			if (flag)
			{
				ParticleSystemRenderer component = ((Component)val).GetComponent<ParticleSystemRenderer>();
				if ((Object)(object)component != (Object)null)
				{
					component.enabled = false;
				}
				val.Stop(true, (ParticleSystemStopBehavior)0);
				Object.Destroy((Object)(object)val);
			}
		}
		ScanWorld();
		Renderer[] componentsInChildren2 = ((Component)this).GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val2 in componentsInChildren2)
		{
			if (!((Object)(object)val2 == (Object)null) && !initial.Contains(val2))
			{
				string text2 = PathOf(((Component)val2).transform);
				bool flag2 = rx != null && rx.IsMatch(text2);
				if (seen.Add(((Object)val2).GetInstanceID()))
				{
					Plugin.Log.LogInfo((object)("Hexen effect (new renderer): " + text2 + (flag2 ? " -> hidden" : "")));
				}
				if (flag2)
				{
					val2.enabled = false;
				}
			}
		}
	}
}
internal static class MagicRevampEquipWrap
{
	internal static string StaffName;

	private static MethodInfo mrEquip;

	private static MethodInfo mrUpdate;

	private static int[] updateArgMap;

	private static int logged;

	private static bool wrapped;

	private static readonly FieldInfo FRight = AccessTools.Field(typeof(Humanoid), "m_rightItem");

	private static readonly FieldInfo FLeft = AccessTools.Field(typeof(Humanoid), "m_leftItem");

	private static bool IsOurStaff(ItemData item)
	{
		if ((Object)(object)item.m_dropPrefab != (Object)null && ((Object)item.m_dropPrefab).name == "DNM_FractureStaff")
		{
			return true;
		}
		return StaffName != null && item.m_shared != null && item.m_shared.m_name == StaffName;
	}

	private static bool IsStaff(ItemData item)
	{
		//IL_0054: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		//IL_005a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Invalid comparison between Unknown and I4
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0061: Invalid comparison between Unknown and I4
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0066: Invalid comparison between Unknown and I4
		if (item == null || item.m_shared == null)
		{
			return false;
		}
		if (IsOurStaff(item))
		{
			return Plugin.StaffNoArmorRequirement.Value;
		}
		if (!Plugin.AllStaffsNoArmorRequirement.Value)
		{
			return false;
		}
		ItemType itemType = item.m_shared.m_itemType;
		if ((int)itemType != 3 && (int)itemType != 14 && (int)itemType != 22)
		{
			return false;
		}
		if (item.m_shared.m_ammoType == "$ammo_spells")
		{
			return true;
		}
		return (Object)(object)item.m_dropPrefab != (Object)null && ((Object)item.m_dropPrefab).name.StartsWith("BMR_", StringComparison.OrdinalIgnoreCase);
	}

	private static bool PrefixEquip(Humanoid __instance, ItemData item, ref bool __result)
	{
		if (mrEquip == null)
		{
			return true;
		}
		try
		{
			if (IsStaff(item))
			{
				return true;
			}
			if (logged < 6 && item != null && item.m_shared != null)
			{
				logged++;
				Plugin.Log.LogInfo((object)("MagicRevamp equip check called for '" + item.m_shared.m_name + "' (prefab " + (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : "?") + ")"));
			}
			object[] array = new object[3] { __instance, item, __result };
			object obj = mrEquip.Invoke(null, array);
			__result = (bool)array[2];
			return !(mrEquip.ReturnType == typeof(bool)) || (bool)obj;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("MagicRevamp equip check failed: " + ((ex.InnerException != null) ? ex.InnerException.Message : ex.Message)));
			return true;
		}
	}

	private static void PostfixUpdate(Humanoid __instance, object[] __args)
	{
		if (mrUpdate == null || (Object)(object)__instance == (Object)null)
		{
			return;
		}
		List<ItemData> list = new List<ItemData>();
		object obj = null;
		object obj2 = null;
		bool flag = false;
		bool flag2 = false;
		try
		{
			if (Plugin.StaffNoArmorRequirement.Value || Plugin.AllStaffsNoArmorRequirement.Value)
			{
				Inventory inventory = __instance.GetInventory();
				if (inventory != null)
				{
					foreach (ItemData allItem in inventory.GetAllItems())
					{
						if (allItem != null && allItem.m_equipped && IsStaff(allItem))
						{
							list.Add(allItem);
							allItem.m_equipped = false;
						}
					}
				}
				if (list.Count > 0)
				{
					if (FRight != null)
					{
						obj = FRight.GetValue(__instance);
						if (obj != null && list.Contains((ItemData)((obj is ItemData) ? obj : null)))
						{
							FRight.SetValue(__instance, null);
							flag = true;
						}
					}
					if (FLeft != null)
					{
						obj2 = FLeft.GetValue(__instance);
						if (obj2 != null && list.Contains((ItemData)((obj2 is ItemData) ? obj2 : null)))
						{
							FLeft.SetValue(__instance, null);
							flag2 = true;
						}
					}
				}
			}
			ParameterInfo[] parameters = mrUpdate.GetParameters();
			object[] array = new object[parameters.Length];
			for (int i = 0; i < parameters.Length; i++)
			{
				if (parameters[i].Name == "__instance")
				{
					array[i] = __instance;
				}
				else if (updateArgMap[i] >= 0 && __args != null && updateArgMap[i] < __args.Length)
				{
					array[i] = __args[updateArgMap[i]];
				}
				else
				{
					array[i] = (parameters[i].ParameterType.IsValueType ? Activator.CreateInstance(parameters[i].ParameterType) : null);
				}
			}
			mrUpdate.Invoke(null, array);
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("MagicRevamp equipment update failed: " + ((ex.InnerException != null) ? ex.InnerException.Message : ex.Message)));
		}
		finally
		{
			foreach (ItemData item in list)
			{
				item.m_equipped = true;
			}
			if (flag)
			{
				FRight.SetValue(__instance, obj);
			}
			if (flag2)
			{
				FLeft.SetValue(__instance, obj2);
			}
		}
	}

	internal static void Install()
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Expected O, but got Unknown
		//IL_017f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0186: Expected O, but got Unknown
		//IL_0332: Unknown result type (might be due to invalid IL or missing references)
		//IL_0339: Expected O, but got Unknown
		if (wrapped)
		{
			return;
		}
		try
		{
			Harmony val = new Harmony("zonzcroupi.DeepNorthMagic.equipwrap");
			BindingFlags bindingAttr = BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
			MethodInfo[] methods = typeof(Humanoid).GetMethods(bindingAttr);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name != "EquipItem" && methodInfo.Name != "UpdateEquipment")
				{
					continue;
				}
				Patches patchInfo = Harmony.GetPatchInfo((MethodBase)methodInfo);
				if (patchInfo == null)
				{
					continue;
				}
				if (methodInfo.Name == "EquipItem")
				{
					foreach (Patch prefix in patchInfo.Prefixes)
					{
						if (prefix.PatchMethod.Name != "EquipItem_Prefix" || prefix.PatchMethod.DeclaringType == null || !prefix.PatchMethod.DeclaringType.FullName.StartsWith("MagicRevamp", StringComparison.Ordinal))
						{
							continue;
						}
						if (prefix.PatchMethod.GetParameters().Length != 3)
						{
							Plugin.Log.LogWarning((object)"MagicRevamp equip check has an unexpected signature: not replaced.");
							break;
						}
						mrEquip = prefix.PatchMethod;
						val.Unpatch((MethodBase)methodInfo, prefix.PatchMethod);
						HarmonyMethod val2 = new HarmonyMethod(typeof(MagicRevampEquipWrap).GetMethod("PrefixEquip", BindingFlags.Static | BindingFlags.NonPublic));
						val2.priority = prefix.priority;
						val2.before = prefix.before;
						val2.after = prefix.after;
						val.Patch((MethodBase)methodInfo, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
						wrapped = true;
						Plugin.Log.LogInfo((object)"MagicRevamp equip check replaced (equip).");
						break;
					}
					continue;
				}
				bool flag = false;
				foreach (Patch postfix in patchInfo.Postfixes)
				{
					if (flag || postfix.PatchMethod.Name != "UpdateEquipment_Postfix" || postfix.PatchMethod.DeclaringType == null || !postfix.PatchMethod.DeclaringType.FullName.StartsWith("MagicRevamp", StringComparison.Ordinal))
					{
						continue;
					}
					mrUpdate = postfix.PatchMethod;
					ParameterInfo[] parameters = methodInfo.GetParameters();
					ParameterInfo[] parameters2 = mrUpdate.GetParameters();
					updateArgMap = new int[parameters2.Length];
					for (int j = 0; j < parameters2.Length; j++)
					{
						updateArgMap[j] = -1;
						for (int k = 0; k < parameters.Length; k++)
						{
							if (parameters[k].Name == parameters2[j].Name)
							{
								updateArgMap[j] = k;
							}
						}
					}
					val.Unpatch((MethodBase)methodInfo, postfix.PatchMethod);
					HarmonyMethod val3 = new HarmonyMethod(typeof(MagicRevampEquipWrap).GetMethod("PostfixUpdate", BindingFlags.Static | BindingFlags.NonPublic));
					val3.priority = postfix.priority;
					val3.before = postfix.before;
					val3.after = postfix.after;
					val.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val3, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					flag = true;
					Plugin.Log.LogInfo((object)("MagicRevamp equip check replaced (equipment update, " + parameters2.Length + " parameter(s))."));
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("MagicRevamp equip check not replaced: " + ex.Message));
		}
	}
}
[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
internal static class CharacterDrop_GenerateDropList_Patch
{
	private static bool Prefix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
	{
		try
		{
			if ((Object)(object)__instance != (Object)null && ((Object)((Component)__instance).gameObject).name.StartsWith("DNM_Friendly_", StringComparison.Ordinal))
			{
				__result = new List<KeyValuePair<GameObject, int>>();
				return false;
			}
		}
		catch
		{
		}
		return true;
	}
}
[HarmonyPatch(typeof(Character), "Damage")]
internal static class Character_Damage_Patch
{
	private static void Prefix(HitData hit)
	{
		try
		{
			if (hit == null)
			{
				return;
			}
			Character attacker = hit.GetAttacker();
			if ((Object)(object)attacker == (Object)null)
			{
				return;
			}
			string text = ((Object)((Component)attacker).gameObject).name;
			if (text.StartsWith("DNM_Friendly_", StringComparison.Ordinal))
			{
				int num = text.IndexOf("(Clone)", StringComparison.Ordinal);
				if (num > 0)
				{
					text = text.Substring(0, num);
				}
				float num2 = Plugin.SummonDamageMultiplier.Value;
				ConfigEntry<float> value;
				if (text == "DNM_Friendly_Troll")
				{
					num2 *= Plugin.TrollDamageMultiplier.Value;
				}
				else if (Content.IsSpirit(text))
				{
					num2 *= Plugin.SpiritDamageMultiplier.Value;
				}
				else if (Content.FriendlyDamage.TryGetValue(text, out value))
				{
					num2 *= value.Value;
				}
				if (!Mathf.Approximately(num2, 1f))
				{
					((DamageTypes)(ref hit.m_damage)).Modify(num2);
				}
			}
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(Character), "Awake")]
internal static class Character_Awake_Patch
{
	internal const string OwnerKey = "dnm_summoner";

	internal const string TimeKey = "dnm_summon_time";

	private static void Postfix(Character __instance)
	{
		try
		{
			if ((Object)(object)__instance == (Object)null || !((Object)((Component)__instance).gameObject).name.StartsWith("DNM_Friendly_", StringComparison.Ordinal))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null)
			{
				return;
			}
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (!((Object)(object)component == (Object)null) && component.IsValid() && component.IsOwner())
			{
				ZDO zDO = component.GetZDO();
				if (zDO.GetLong("dnm_summoner", 0L) == 0)
				{
					zDO.Set("dnm_summoner", localPlayer.GetPlayerID());
					zDO.Set("dnm_summon_time", ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTime().Ticks : DateTime.UtcNow.Ticks);
					Content.EnforceSummonLimit(__instance, localPlayer.GetPlayerID());
				}
			}
		}
		catch
		{
		}
	}
}
[HarmonyPatch(typeof(ObjectDB), "Awake")]
internal static class ObjectDB_Awake_Patch
{
	private static void Postfix(ObjectDB __instance)
	{
		Content.OnObjectDB(__instance);
	}
}
[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
internal static class ObjectDB_Copy_Patch
{
	private static void Postfix(ObjectDB __instance)
	{
		Content.OnObjectDB(__instance);
	}
}
[HarmonyPatch(typeof(Localization), "SetupLanguage")]
internal static class Localization_Setup_Patch
{
	private static void Postfix(Localization __instance)
	{
		Content.ApplyWords(__instance);
	}
}
internal static class Content
{
	internal sealed class RecipeSettings
	{
		public bool Enabled = true;

		public string Spec = "";

		public int Amount = 1;

		public int StationLevel = 1;

		public bool RequireStation = true;

		public int MaxQuality = 1;

		public bool AllowQuality;

		public Func<RecipeSettings> Reread;
	}

	private sealed class TopMesh
	{
		public MeshFilter Filter;

		public MeshRenderer Rend;

		public Mesh Mesh;

		public Vector3[] Pos;

		public Vector3[] Nrm;

		public int[][] Tris;
	}

	private sealed class PartRule
	{
		public string[] Keys;

		public Recolor Mode;
	}

	internal enum LookKind
	{
		RedToWhite,
		WhiteKeepHorns,
		CapeWhite,
		RobeMix,
		CapeAsh
	}

	private enum Recolor
	{
		RedToWhite,
		AllWhite,
		WhiteKeepHorns,
		Ice,
		StaffIceHeart,
		PatternBlack,
		RobeMix,
		Black,
		BlackFull,
		WoodToWhite,
		CandleMix,
		CrownMix,
		CrownBlue
	}

	internal sealed class LootSpec
	{
		public GameObject Item;

		public float Chance;

		public int Min;

		public int Max;

		public bool ScaleWithLevel;

		public bool OnePerPlayer;
	}

	private static bool built;

	private static bool itemsBuilt;

	private static bool failed;

	private static Transform container;

	private static Dictionary<string, GameObject> index;

	private static Dictionary<string, ItemDrop> itemsByNorm;

	private static readonly List<GameObject> created = new List<GameObject>();

	private static readonly List<GameObject> itemPrefabs = new List<GameObject>();

	private static readonly List<Recipe> recipes = new List<Recipe>();

	private static readonly List<Action> reloaders = new List<Action>();

	private static readonly List<StatusEffect> effects = new List<StatusEffect>();

	private static readonly Dictionary<string, Func<string>> words = new Dictionary<string, Func<string>>();

	private static Dictionary<string, string> translations = new Dictionary<string, string>();

	private static CraftingStation station;

	internal static readonly Dictionary<string, float> EitrBonus = new Dictionary<string, float>();

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

	private static readonly Color IceTint = new Color(0.55f, 0.82f, 1f, 1f);

	private static readonly Color StaffTint = new Color(0.4f, 0.75f, 1f, 1f);

	private static readonly Color WandTint = new Color(0.65f, 0.6f, 1f, 1f);

	private static readonly Color SpellTint = new Color(0.5f, 0.95f, 0.95f, 1f);

	private static bool applying;

	private static Localization currentLoc;

	private static readonly string[] LocalizationDirs = BuildLocalizationDirs();

	private static readonly List<Recipe> pendingStation = new List<Recipe>();

	private static Dictionary<string, ItemDrop> localItems;

	private static readonly Dictionary<string, string> itemAliases = new Dictionary<string, string>
	{
		{ "bloodgold", "gold" },
		{ "orsang", "gold" },
		{ "timberwood", "frostwood" },
		{ "timber", "frostwood" },
		{ "sapinnordique", "frostwood" },
		{ "bloodstone", "gemstonered" },
		{ "pierredesang", "gemstonered" }
	};

	private const string DefaultArmorSpec = "Gold|Bloodgold:5:1,MooseHide:5:1,Eitr:15:5,Frostwood|Timberwood:10:2";

	private static Texture2D iceTexture;

	private static bool extensionsListed;

	private static SE_Stats featherEffect;

	private static StatusEffect capeBaseEffect;

	private static bool capeBaseCaptured;

	private static bool missingCreature;

	private static readonly string[] excludedCreatures = new string[11]
	{
		"gammeltroll", "fallenwarrior", "guerrierdechu", "blobmork", "shapelesspulp", "patesansforme", "tinypulp", "petitepate", "blobfrost", "frostblob",
		"blobdegivre"
	};

	internal const int GameCapOff = 1000;

	internal static readonly Dictionary<string, ConfigEntry<float>> FriendlyDamage = new Dictionary<string, ConfigEntry<float>>();

	private static Dictionary<string, Recipe> gameRecipes;

	internal static readonly Dictionary<string, int> WhiteStyle = new Dictionary<string, int>();

	private static readonly Dictionary<string, Texture2D> texCache = new Dictionary<string, Texture2D>();

	private static readonly Dictionary<string, Material> matCache = new Dictionary<string, Material>();

	private static Color patBase = new Color(0.5f, 0.5f, 0.5f, 1f);

	private static float patLum = 0.5f;

	private static int boneHits;

	private static Color crownBlueRef = new Color(0.25f, 0.45f, 0.95f, 1f);

	private static readonly List<GameObject> scrollPrefabs = new List<GameObject>();

	private static bool scrollScaleHooked;

	private static readonly Dictionary<Transform, Vector3[]> scrollPartBase = new Dictionary<Transform, Vector3[]>();

	private static readonly Dictionary<BoxCollider, Vector3[]> scrollBoxBase = new Dictionary<BoxCollider, Vector3[]>();

	internal static readonly HashSet<string> UnlimitedScrolls = new HashSet<string>();

	private static readonly List<SpawnAbility> spiritAbilities = new List<SpawnAbility>();

	private static bool spiritHooked;

	private static ConfigEntry<int> spiritLevel;

	private static readonly Dictionary<string, GameObject> spiritClones = new Dictionary<string, GameObject>();

	internal static readonly Dictionary<string, List<LootSpec>> Loot = new Dictionary<string, List<LootSpec>>();

	private static readonly FieldInfo FLastHit = AccessTools.Field(typeof(Character), "m_lastHit");

	private static readonly HashSet<ZDOID> dropped = new HashSet<ZDOID>();

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

	private static int unmatchedLogged;

	private static string Tr(string key, string en)
	{
		string value;
		return (translations.TryGetValue(key, out value) && !string.IsNullOrEmpty(value)) ? value : en;
	}

	private static string FormatTr(string key, string en, params object[] args)
	{
		try
		{
			return string.Format(Tr(key, en), args);
		}
		catch
		{
			return string.Format(en, args);
		}
	}

	internal static float EquipmentEitr(Player player)
	{
		if (EitrBonus.Count == 0 || (Object)(object)player == (Object)null)
		{
			return 0f;
		}
		float num = 0f;
		int num2 = 0;
		foreach (ItemData equippedItem in ((Humanoid)player).GetInventory().GetEquippedItems())
		{
			if (equippedItem != null && !((Object)(object)equippedItem.m_dropPrefab == (Object)null) && EitrBonus.TryGetValue(((Object)equippedItem.m_dropPrefab).name, out var value))
			{
				num += value * (1f + 0.1f * (float)Mathf.Max(0, equippedItem.m_quality - 1));
				num2++;
			}
		}
		if (num2 >= 4)
		{
			num += Plugin.SetEitrBonus.Value;
		}
		return num;
	}

	internal static void OnObjectDB(ObjectDB db)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		if (!Plugin.Enabled.Value || (Object)(object)db == (Object)null)
		{
			return;
		}
		Scene activeScene = SceneManager.GetActiveScene();
		string name = ((Scene)(ref activeScene)).name;
		int num = ((db.m_items != null) ? db.m_items.Count : (-1));
		Plugin.Log.LogInfo((object)("ObjectDB ready in scene '" + name + "': " + num + " game items, mod content built: " + (built ? "all" : (itemsBuilt ? "armor and staff" : "nothing yet"))));
		if (name == "main")
		{
			if (!built)
			{
				Build(force: true);
			}
			if (!built)
			{
				return;
			}
		}
		else
		{
			if (!itemsBuilt)
			{
				if (num <= 0)
				{
					return;
				}
				BuildItems(final: false);
			}
			if (!itemsBuilt)
			{
				return;
			}
		}
		try
		{
			foreach (GameObject itemPrefab in itemPrefabs)
			{
				if (!db.m_items.Contains(itemPrefab))
				{
					db.m_items.Add(itemPrefab);
				}
			}
			foreach (Recipe recipe in recipes)
			{
				if (!db.m_recipes.Contains(recipe))
				{
					db.m_recipes.Add(recipe);
				}
			}
			foreach (StatusEffect effect in effects)
			{
				if (!db.m_StatusEffects.Contains(effect))
				{
					db.m_StatusEffects.Add(effect);
				}
			}
			Invoke(db, "UpdateRegisters");
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("ObjectDB registration failed: " + ex));
		}
		ApplyWords(Localization.instance);
	}

	internal static void AddToScene(ZNetScene zns)
	{
		if (!built || (Object)(object)zns == (Object)null)
		{
			return;
		}
		HashSet<string> hashSet = new HashSet<string>();
		foreach (GameObject prefab in zns.m_prefabs)
		{
			if ((Object)(object)prefab != (Object)null)
			{
				hashSet.Add(((Object)prefab).name);
			}
		}
		foreach (GameObject item in created)
		{
			if (hashSet.Add(((Object)item).name))
			{
				zns.m_prefabs.Add(item);
			}
		}
	}

	private static string[] BuildLocalizationDirs()
	{
		List<string> list = new List<string>();
		try
		{
			string directoryName = Path.GetDirectoryName(typeof(Content).Assembly.Location);
			if (!string.IsNullOrEmpty(directoryName))
			{
				list.Add(Path.Combine(directoryName, "Localization"));
				list.Add(directoryName);
			}
			list.Add(Path.Combine(Paths.ConfigPath, "DeepNorthMagic"));
		}
		catch
		{
		}
		return list.ToArray();
	}

	private static Dictionary<string, string> LoadTranslations(string lang)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>();
		try
		{
			string[] localizationDirs = LocalizationDirs;
			foreach (string path in localizationDirs)
			{
				string text = Path.Combine(path, "DeepNorthMagic." + lang + ".yml");
				if (!File.Exists(text))
				{
					text = Path.Combine(path, lang + ".yml");
				}
				if (!File.Exists(text))
				{
					continue;
				}
				string[] array = File.ReadAllLines(text, Encoding.UTF8);
				foreach (string text2 in array)
				{
					string text3 = text2.Trim();
					if (text3.Length == 0 || text3[0] == '#')
					{
						continue;
					}
					int num = text3.IndexOf(':');
					if (num > 0)
					{
						string key = text3.Substring(0, num).Trim();
						string text4 = text3.Substring(num + 1).Trim();
						if (text4.Length >= 2 && text4[0] == '"' && text4[text4.Length - 1] == '"')
						{
							text4 = Unescape(text4.Substring(1, text4.Length - 2));
						}
						else if (text4.Length >= 2 && text4[0] == '\'' && text4[text4.Length - 1] == '\'')
						{
							text4 = text4.Substring(1, text4.Length - 2).Replace("''", "'");
						}
						dictionary[key] = text4;
					}
				}
				Plugin.Log.LogInfo((object)("Translation loaded: " + text + " (" + dictionary.Count + " texts)"));
				break;
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Translation file not read: " + ex.Message));
		}
		return dictionary;
	}

	private static string Unescape(string v)
	{
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < v.Length; i++)
		{
			if (v[i] == '\\' && i + 1 < v.Length)
			{
				char c = v[++i];
				stringBuilder.Append((c == 'n') ? '\n' : c);
			}
			else
			{
				stringBuilder.Append(v[i]);
			}
		}
		return stringBuilder.ToString();
	}

	private static string LocalizeName(string s)
	{
		try
		{
			return (currentLoc != null) ? currentLoc.Localize(s) : s;
		}
		catch
		{
			return s;
		}
	}

	internal static void ApplyWords(Localization loc)
	{
		if (words.Count == 0 || loc == null || applying)
		{
			return;
		}
		applying = true;
		currentLoc = loc;
		try
		{
			string lang = "English";
			try
			{
				lang = loc.GetSelectedLanguage();
			}
			catch
			{
			}
			translations = LoadTranslations(lang);
			foreach (KeyValuePair<string, Func<string>> word in words)
			{
				try
				{
					Invoke(loc, "AddWord", word.Key, word.Value());
				}
				catch
				{
				}
			}
		}
		finally
		{
			applying = false;
		}
	}

	internal static void Build(bool force)
	{
		if (built || failed || !Plugin.Enabled.Value || !force)
		{
			return;
		}
		BuildItems(final: true);
		if (!itemsBuilt || failed)
		{
			return;
		}
		try
		{
			BuildIndex();
			localItems = null;
			EnsureContainer();
			BuildTable();
			AssignStation();
			try
			{
				BuildExtensions();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogError((object)("Table upgrades failed: " + ex));
			}
			BuildSummons();
			try
			{
				BuildTrollScroll();
			}
			catch (Exception ex2)
			{
				Plugin.Log.LogError((object)("Troll scroll failed: " + ex2));
			}
			try
			{
				BuildSpiritCallerScroll();
			}
			catch (Exception ex3)
			{
				Plugin.Log.LogError((object)("Spirit Caller scroll failed: " + ex3));
			}
			AddTableToHammer();
			built = true;
			SortConfig();
			Plugin.Log.LogInfo((object)("Created " + itemPrefabs.Count + " items, " + recipes.Count + " recipes"));
		}
		catch (Exception ex4)
		{
			failed = true;
			Plugin.Log.LogError((object)("Build failed, mod content disabled: " + ex4));
		}
	}

	internal static void BuildItems(bool final)
	{
		if (itemsBuilt || failed || !Plugin.Enabled.Value)
		{
			return;
		}
		try
		{
			BuildIndex();
			EnsureContainer();
			BuildArmor();
			BuildWeapons();
			if (itemPrefabs.Count == 0 && !final)
			{
				Plugin.Log.LogWarning((object)"Armor and staff: no base item available yet, will try again.");
				effects.Clear();
				EitrBonus.Clear();
			}
			else
			{
				itemsBuilt = true;
				Plugin.Log.LogInfo((object)("Armor and staff built (" + itemPrefabs.Count + " items)"));
			}
		}
		catch (Exception ex)
		{
			failed = true;
			Plugin.Log.LogError((object)("Build failed, mod content disabled: " + ex));
		}
	}

	private static void AssignStation()
	{
		if ((Object)(object)station == (Object)null)
		{
			return;
		}
		foreach (Recipe item in pendingStation)
		{
			item.m_craftingStation = station;
			item.m_repairStation = station;
		}
		pendingStation.Clear();
	}

	private static void BuildIndex()
	{
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		index = new Dictionary<string, GameObject>();
		itemsByNorm = new Dictionary<string, ItemDrop>();
		GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>();
		foreach (GameObject val in array)
		{
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val.transform.parent != (Object)null))
			{
				Scene scene = val.scene;
				if (!((Scene)(ref scene)).IsValid() && (!index.TryGetValue(((Object)val).name, out var value) || Score(val) > Score(value)))
				{
					index[((Object)val).name] = val;
				}
			}
		}
		foreach (KeyValuePair<string, GameObject> item in index)
		{
			ItemDrop component = item.Value.GetComponent<ItemDrop>();
			if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null)
			{
				itemsByNorm[Norm(item.Key)] = component;
			}
		}
		Plugin.Log.LogInfo((object)("Prefab index: " + index.Count + " prefabs, " + itemsByNorm.Count + " items"));
	}

	private static int Score(GameObject go)
	{
		int num = 0;
		if ((Object)(object)go.GetComponent<ZNetView>() != (Object)null)
		{
			num++;
		}
		if ((Object)(object)go.GetComponent<ItemDrop>() != (Object)null)
		{
			num++;
		}
		if ((Object)(object)go.GetComponent<Character>() != (Object)null)
		{
			num++;
		}
		if ((Object)(object)go.GetComponent<Piece>() != (Object)null)
		{
			num++;
		}
		return num;
	}

	private static void EnsureContainer()
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_001d: Expected O, but got Unknown
		if (!((Object)(object)container != (Object)null))
		{
			GameObject val = new GameObject("DeepNorthMagic_Prefabs");
			val.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)val);
			container = val.transform;
		}
	}

	private static string Norm(string s)
	{
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char c in s)
		{
			if (char.IsLetterOrDigit(c))
			{
				stringBuilder.Append(char.ToLowerInvariant(c));
			}
		}
		return stringBuilder.ToString();
	}

	private static GameObject Clone(GameObject src, string name)
	{
		GameObject val = Object.Instantiate<GameObject>(src, container, false);
		((Object)val).name = name;
		created.Add(val);
		return val;
	}

	private static void Invoke(object target, string method, params object[] args)
	{
		MethodInfo methodInfo = AccessTools.Method(target.GetType(), method, (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			Warn("Method not found: " + method);
		}
		else
		{
			methodInfo.Invoke(target, args);
		}
	}

	private static void SetField(object target, string field, object value)
	{
		FieldInfo fieldInfo = AccessTools.Field(target.GetType(), field);
		if (fieldInfo == null)
		{
			Warn("Field not found: " + field);
		}
		else
		{
			fieldInfo.SetValue(target, value);
		}
	}

	private static void Warn(string msg)
	{
		if (warned.Add(msg))
		{
			Plugin.Log.LogWarning((object)msg);
		}
	}

	private static void Info(string msg)
	{
		if (Plugin.DiscoveryLog.Value)
		{
			Plugin.Log.LogInfo((object)msg);
		}
	}

	private static void L(string key, string en)
	{
		words[key] = () => Tr(key, en);
	}

	private static void BuildLocalItems()
	{
		localItems = new Dictionary<string, ItemDrop>();
		try
		{
			Localization instance = Localization.instance;
			if (instance == null)
			{
				return;
			}
			foreach (ItemDrop value in itemsByNorm.Values)
			{
				string name = value.m_itemData.m_shared.m_name;
				if (!string.IsNullOrEmpty(name))
				{
					string text = Norm(instance.Localize(name));
					if (text.Length > 0 && !localItems.ContainsKey(text))
					{
						localItems[text] = value;
					}
				}
			}
		}
		catch (Exception ex)
		{
			Warn("Localized item names unavailable: " + ex.Message);
		}
	}

	private static ItemDrop FindItem(string token)
	{
		string text = Norm(token);
		if (text.Length == 0)
		{
			return null;
		}
		if (itemsByNorm.TryGetValue(text, out var value))
		{
			return value;
		}
		if (localItems == null)
		{
			BuildLocalItems();
		}
		if (localItems.TryGetValue(text, out value))
		{
			return value;
		}
		string text2 = null;
		foreach (string key in itemsByNorm.Keys)
		{
			if (key.Contains(text) && (text2 == null || key.Length < text2.Length))
			{
				text2 = key;
			}
		}
		if (text2 != null)
		{
			return itemsByNorm[text2];
		}
		string text3 = null;
		foreach (string key2 in localItems.Keys)
		{
			if (key2.Contains(text) && (text3 == null || key2.Length < text3.Length))
			{
				text3 = key2;
			}
		}
		return (text3 != null) ? localItems[text3] : null;
	}

	private static ItemDrop FindItemExact(string token)
	{
		string text = Norm(token);
		if (text.Length == 0)
		{
			return null;
		}
		if (itemAliases.TryGetValue(text, out var value) && itemsByNorm.TryGetValue(value, out var value2))
		{
			return value2;
		}
		if (itemsByNorm.TryGetValue(text, out value2))
		{
			return value2;
		}
		if (localItems == null)
		{
			BuildLocalItems();
		}
		if (localItems.TryGetValue(text, out value2))
		{
			return value2;
		}
		return null;
	}

	private static GameObject FindBaseItem(ItemType type, string hint, params string[] names)
	{
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		foreach (string key in names)
		{
			if (index.TryGetValue(key, out var value))
			{
				ItemDrop component = value.GetComponent<ItemDrop>();
				if ((Object)(object)component != (Object)null)
				{
					return value;
				}
			}
		}
		if (hint != null)
		{
			foreach (KeyValuePair<string, GameObject> item in index)
			{
				ItemDrop component2 = item.Value.GetComponent<ItemDrop>();
				if ((Object)(object)component2 == (Object)null || component2.m_itemData.m_shared.m_itemType != type || item.Key.IndexOf(hint, StringComparison.OrdinalIgnoreCase) < 0)
				{
					continue;
				}
				return item.Value;
			}
		}
		return null;
	}

	private static List<Requirement> ParseReqs(string spec)
	{
		//IL_024a: Unknown result type (might be due to invalid IL or missing references)
		//IL_024f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0257: Unknown result type (might be due to invalid IL or missing references)
		//IL_025f: Unknown result type (might be due to invalid IL or missing references)
		//IL_026b: Expected O, but got Unknown
		//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0201: Unknown result type (might be due to invalid IL or missing references)
		//IL_020d: Expected O, but got Unknown
		List<Requirement> list = new List<Requirement>();
		if (string.IsNullOrEmpty(spec))
		{
			return list;
		}
		string[] array = spec.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
		foreach (string text in array)
		{
			string[] array2 = text.Trim().Split(new char[1] { ':' });
			if (array2.Length < 2 || !int.TryParse(array2[1], out var result) || result <= 0)
			{
				continue;
			}
			int result2 = 0;
			if (array2.Length > 2)
			{
				int.TryParse(array2[2], out result2);
			}
			ItemDrop val = null;
			string[] array3 = array2[0].Split(new char[1] { '|' });
			string[] array4 = array3;
			foreach (string text2 in array4)
			{
				val = FindItemExact(text2.Trim());
				if ((Object)(object)val != (Object)null)
				{
					break;
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				string[] array5 = array3;
				foreach (string text3 in array5)
				{
					val = FindItem(text3.Trim());
					if ((Object)(object)val != (Object)null)
					{
						Info("Ingredient '" + text3.Trim() + "' matched loosely to " + ((Object)((Component)val).gameObject).name + " (" + val.m_itemData.m_shared.m_name + ")");
						break;
					}
				}
			}
			if ((Object)(object)val == (Object)null)
			{
				Warn("Ingredient not found in this game version: '" + array2[0].Trim() + "' (edit the recipe in the config file)");
				continue;
			}
			list.Add(new Requirement
			{
				m_resItem = val,
				m_amount = result,
				m_amountPerLevel = result2,
				m_recover = true
			});
		}
		if (list.Count == 0)
		{
			ItemDrop val2 = FindItem("Eitr");
			if ((Object)(object)val2 != (Object)null)
			{
				list.Add(new Requirement
				{
					m_resItem = val2,
					m_amount = 20,
					m_recover = true
				});
			}
		}
		return list;
	}

	private static Sprite TintSprite(Sprite s, Color tint)
	{
		//IL_001d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Expected O, but got Unknown
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_018a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0199: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_010c: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)s == (Object)null)
		{
			return null;
		}
		try
		{
			Texture2D texture = s.texture;
			Rect textureRect = s.textureRect;
			RenderTexture temporary = RenderTexture.GetTemporary(((Texture)texture).width, ((Texture)texture).height, 0, (RenderTextureFormat)0, (RenderTextureReadWrite)2);
			Graphics.Blit((Texture)(object)texture, temporary);
			RenderTexture active = RenderTexture.active;
			RenderTexture.active = temporary;
			Texture2D val = new Texture2D((int)((Rect)(ref textureRect)).width, (int)((Rect)(ref textureRect)).height, (TextureFormat)4, false);
			val.ReadPixels(new Rect(((Rect)(ref textureRect)).x, ((Rect)(ref textureRect)).y, ((Rect)(ref textureRect)).width, ((Rect)(ref textureRect)).height), 0, 0);
			Color[] pixels = val.GetPixels();
			Color val3 = default(Color);
			for (int i = 0; i < pixels.Length; i++)
			{
				Color val2 = pixels[i];
				if (!(val2.a <= 0f))
				{
					float num = Mathf.Clamp01(((Color)(ref val2)).grayscale * 1.35f);
					((Color)(ref val3))..ctor(tint.r * num, tint.g * num, tint.b * num, val2.a);
					pixels[i] = Color.Lerp(val2, val3, 0.6f);
					pixels[i].a = val2.a;
				}
			}
			val.SetPixels(pixels);
			val.Apply();
			RenderTexture.active = active;
			RenderTexture.ReleaseTemporary(temporary);
			return Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f), s.pixelsPerUnit);
		}
		catch (Exception ex)
		{
			Warn("Icon tint failed: " + ex.Message);
			return s;
		}
	}

	private static void Tint(GameObject go, Color tint, float amount)
	{
		//IL_0095: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: Expected O, but got Unknown
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: 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_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		Renderer[] componentsInChildren = go.GetComponentsInChildren<Renderer>(true);
		foreach (Renderer val in componentsInChildren)
		{
			string name = ((object)val).GetType().Name;
			if (name.IndexOf("Particle", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Trail", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Line", StringComparison.OrdinalIgnoreCase) >= 0)
			{
				continue;
			}
			Material[] sharedMaterials = val.sharedMaterials;
			Material[] array = (Material[])(object)new Material[sharedMaterials.Length];
			for (int j = 0; j < sharedMaterials.Length; j++)
			{
				Material val2 = sharedMaterials[j];
				if (!((Object)(object)val2 == (Object)null))
				{
					Material val3 = new Material(val2);
					if (val3.HasProperty("_Color"))
					{
						val3.SetColor("_Color", Color.Lerp(val3.GetColor("_Color"), tint, amount));
					}
					if (val3.HasProperty("_EmissionColor"))
					{
						val3.SetColor("_EmissionColor", tint * 0.2f);
					}
					array[j] = val3;
				}
			}
			val.sharedMaterials = array;
		}
	}

	private static ItemDrop MakeItem(string id, GameObject baseGo, Color tint, string en, string descEn, bool doTint = true)
	{
		//IL_007b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = Clone(baseGo, id);
		ItemDrop component = val.GetComponent<ItemDrop>();
		component.m_itemData.m_dropPrefab = val;
		SharedData shared = component.m_itemData.m_shared;
		string text = id.ToLowerInvariant();
		shared.m_name = "$" + text;
		shared.m_description = "$" + text + "_desc";
		L(text, en);
		L(text + "_desc", descEn);
		if (doTint)
		{
			Tint(val, tint, 0.55f);
			if (shared.m_icons != null)
			{
				for (int i = 0; i < shared.m_icons.Length; i++)
				{
					shared.m_icons[i] = TintSprite(shared.m_icons[i], tint);
				}
			}
		}
		itemPrefabs.Add(val);
		return component;
	}

	internal static string SummonSection(string prefab)
	{
		return SafeSection("4 - " + prefab switch
		{
			"Troll" => "Summon: Troll (unlimited scroll)", 
			"SpiritCaller" => "Summon: Spirit Caller (unlimited scroll)", 
			"ElakingMole" => "Summon: Eyeless (ElakingMole)", 
			"JotunWarrior" => "Summon: Krigen (JotunWarrior)", 
			"JotunWitch" => "Summon: Hexen (JotunWitch)", 
			_ => "Summon: " + prefab, 
		});
	}

	private static string SafeSection(string s)
	{
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char value in s)
		{
			if ("=\n\t\\\"'[]".IndexOf(value) < 0)
			{
				stringBuilder.Append(value);
			}
		}
		return stringBuilder.ToString().Trim();
	}

	private static RecipeSettings ReadRecipe(string title, string defSpec, int defAmount, int defMaxQuality, bool allowQuality, string sharedSpec = null, string section = null, string pfx = "")
	{
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_0173: Expected O, but got Unknown
		//IL_01a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Expected O, but got Unknown
		//IL_0216: Unknown result type (might be due to invalid IL or missing references)
		//IL_0220: Expected O, but got Unknown
		string text = section ?? SafeSection("Recipe: " + title);
		RecipeSettings recipeSettings = new RecipeSettings();
		recipeSettings.AllowQuality = allowQuality;
		recipeSettings.Reread = () => ReadRecipe(title, defSpec, defAmount, defMaxQuality, allowQuality, sharedSpec, section, pfx);
		ConfigFile cfg = Plugin.Cfg;
		recipeSettings.Enabled = cfg.Bind<bool>(text, pfx + "Enabled", true, "false = no crafting recipe for this item (it still exists and can drop or be spawned).").Value;
		if (sharedSpec != null)
		{
			recipeSettings.Spec = cfg.Bind<string>(text, pfx + "IngredientsOverride", "", "Leave empty to use the shared recipe of the armor set (section \"Recipe: Armor set (all pieces)\"). Fill with the same format to give this piece its own recipe.").Value;
			if (string.IsNullOrEmpty(recipeSettings.Spec))
			{
				recipeSettings.Spec = sharedSpec;
			}
		}
		else
		{
			recipeSettings.Spec = cfg.Bind<string>(text, pfx + "Ingredients", defSpec, "Craft cost: Name:amount:amountPerUpgradeLevel, comma separated. The third number is the cost of the upgrade levels. Use | for alternative names. Names are matched loosely against prefab names and displayed names, in any game language. Empty = no recipe.").Value;
		}
		recipeSettings.Amount = cfg.Bind<int>(text, pfx + "CraftAmount", defAmount, new ConfigDescription("Number of items produced by one craft.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 100), Array.Empty<object>())).Value;
		recipeSettings.StationLevel = cfg.Bind<int>(text, pfx + "MinStationLevel", 1, new ConfigDescription("Minimum level of the crafting station.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())).Value;
		recipeSettings.RequireStation = cfg.Bind<bool>(text, pfx + "RequireStation", true, "false = can be crafted anywhere, without the Rune Table.").Value;
		if (allowQuality)
		{
			recipeSettings.MaxQuality = cfg.Bind<int>(text, pfx + "MaxQuality", defMaxQuality, new ConfigDescription("Maximum quality (upgrade level). 1 = cannot be upgraded.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>())).Value;
		}
		else
		{
			recipeSettings.MaxQuality = 1;
		}
		return recipeSettings;
	}

	private static void AddRecipe(ItemDrop item, RecipeSettings s)
	{
		Recipe r = ScriptableObject.CreateInstance<Recipe>();
		((Object)r).name = "Recipe_" + ((Object)((Component)item).gameObject).name;
		r.m_item = item;
		r.m_resources = (Requirement[])(object)new Requirement[0];
		Action<RecipeSettings> apply = delegate(RecipeSettings cs)
		{
			List<Requirement> list = (cs.Enabled ? ParseReqs(cs.Spec) : new List<Requirement>());
			r.m_enabled = cs.Enabled && list.Count > 0;
			if (!cs.Enabled)
			{
				Info("Recipe disabled by config: " + ((Object)((Component)item).gameObject).name);
			}
			else if (list.Count == 0)
			{
				Info("No ingredients: no recipe for " + ((Object)((Component)item).gameObject).name);
			}
			r.m_resources = list.ToArray();
			r.m_amount = Mathf.Max(1, cs.Amount);
			r.m_minStationLevel = Mathf.Max(1, cs.StationLevel);
			r.m_craftingStation = (cs.RequireStation ? station : null);
			r.m_repairStation = (cs.RequireStation ? station : null);
			if (cs.RequireStation && (Object)(object)station == (Object)null && !pendingStation.Contains(r))
			{
				pendingStation.Add(r);
			}
			if (cs.AllowQuality)
			{
				item.m_itemData.m_shared.m_maxQuality = cs.MaxQuality;
			}
		};
		apply(s);
		recipes.Add(r);
		reloaders.Add(delegate
		{
			apply((s.Reread != null) ? s.Reread() : s);
		});
	}

	private static string TableCfg(string def)
	{
		return Plugin.Cfg.Bind<string>("6 - Crafting table: Northern Rune Table", "Table_Ingredients", def, "Build cost: Name:amount, comma separated. Use | for alternative names (e.g. Timberwood:10). Names are matched loosely against prefab names and displayed names, in any game language.").Value;
	}

	private static GameObject FindRuneTable()
	{
		GameObject val = null;
		GameObject val2 = null;
		foreach (KeyValuePair<string, GameObject> item in index)
		{
			if (item.Key.StartsWith("DNM_"))
			{
				continue;
			}
			Piece component = item.Value.GetComponent<Piece>();
			if (!((Object)(object)component == (Object)null))
			{
				bool flag = item.Key.IndexOf("magetable_ext", StringComparison.OrdinalIgnoreCase) >= 0;
				bool flag2 = (component.m_name ?? "").IndexOf("rune", StringComparison.OrdinalIgnoreCase) >= 0 && item.Key.IndexOf("mage", StringComparison.OrdinalIgnoreCase) >= 0;
				if (flag || flag2)
				{
					Info("Galdr extension candidate: " + item.Key + " (" + component.m_name + ")");
				}
				if (flag2 && (Object)(object)val2 == (Object)null)
				{
					val2 = item.Value;
				}
				if (flag && ((Object)(object)val == (Object)null || item.Key.Length < ((Object)val).name.Length))
				{
					val = item.Value;
				}
			}
		}
		return val2 ?? val;
	}

	private static void StripColliders(GameObject go)
	{
		Component[] componentsInChildren = go.GetComponentsInChildren<Component>(true);
		foreach (Component val in componentsInChildren)
		{
			if ((Object)(object)val != (Object)null && ((object)val).GetType().Name.EndsWith("Collider", StringComparison.Ordinal))
			{
				Object.DestroyImmediate((Object)(object)val);
			}
		}
	}

	private static Texture2D IceTexture()
	{
		//IL_0225: Unknown result type (might be due to invalid IL or missing references)
		//IL_022b: Expected O, but got Unknown
		//IL_0191: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01af: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e2: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)iceTexture != (Object)null)
		{
			return iceTexture;
		}
		Color[] array = (Color[])(object)new Color[65536];
		Color val = default(Color);
		((Color)(ref val))..ctor(0.6f, 0.79f, 0.95f, 1f);
		Color val2 = default(Color);
		((Color)(ref val2))..ctor(0.91f, 0.97f, 1f, 1f);
		for (int i = 0; i < 256; i++)
		{
			for (int j = 0; j < 256; j++)
			{
				float num = (float)j / 256f;
				float num2 = (float)i / 256f;
				float num3 = Mathf.PerlinNoise(num * 4f + 11.3f, num2 * 4f + 5.7f) * 0.55f + Mathf.PerlinNoise(num * 12f + 3.1f, num2 * 12f + 8.9f) * 0.3f + Mathf.PerlinNoise(num * 40f + 1.7f, num2 * 40f + 2.3f) * 0.15f;
				float num4 = Mathf.Abs(Mathf.PerlinNoise(num * 7f + 40f, num2 * 7f + 17f) - 0.5f);
				float num5 = Mathf.Abs(Mathf.PerlinNoise(num * 15f + 90f, num2 * 15f + 63f) - 0.5f);
				float num6 = Mathf.Clamp01(1f - num4 / 0.016f) * 0.7f + Mathf.Clamp01(1f - num5 / 0.01f) * 0.4f;
				Color val3 = Color.Lerp(val, val2, Mathf.Clamp01(num3 * 1.25f - 0.1f));
				val3 = Color.Lerp(val3, Color.white, Mathf.Clamp01(num6) * 0.7f);
				val3.a = 1f;
				array[i * 256 + j] = val3;
			}
		}
		Texture2D val4 = new Texture2D(256, 256, (TextureFormat)4, true);
		val4.SetPixels(array);
		val4.Apply(true, false);
		((Texture)val4).wrapMode = (TextureWrapMode)0;
		((Texture)val4).filterMode = (FilterMode)1;
		((Object)val4).name = "dnm_ice";
		iceTexture = val4;
		return val4;
	}

	private static Material IceMaterial(Material like)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Expected O, but got Unknown
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_013b: Unknown result type (might be due to invalid IL or missing references)
		Material val = new Material(like);
		((Object)val).name = "dnm_ice_top";
		val.mainTexture = (Texture)(object)IceTexture();
		if (val.HasProperty("_Color"))
		{
			val.SetColor("_Color", new Color(0.88f, 0.96f, 1f, 1f));
		}
		if (val.HasProperty("_Glossiness"))
		{
			val.SetFloat("_Glossiness", 0.9f);
		}
		if (val.HasProperty("_Smoothness"))
		{
			val.SetFloat("_Smoothness", 0.9f);
		}
		if (val.HasProperty("_Metallic"))
		{
			val.SetFloat("_Metallic", 0.05f);
		}
		if (val.HasProperty("_MetallicGlossMap"))
		{
			val.SetTexture("_MetallicGlossMap", (Texture)null);
		}
		if (val.HasProperty("_BumpMap"))
		{
			val.SetTexture("_BumpMap", (Texture)null);
		}
		if (val.HasProperty("_EmissionMap"))
		{
			val.SetTexture("_EmissionMap", (Texture)null);
		}
		if (val.HasProperty("_EmissionColor"))
		{
			val.SetColor("_EmissionColor", new Color(0.03f, 0.06f, 0.09f, 1f));
		}
		return val;
	}

	private static void ApplyIceTop(GameObject table, List<GameObject> visuals)
	{
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_011e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0123: Unknown result type (might be due to invalid IL or missing references)
		//IL_0128: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
		List<TopMesh> list = new List<TopMesh>();
		float num = float.MaxValue;
		float num2 = float.MinValue;
		foreach (GameObject visual in visuals)
		{
			MeshFilter[] componentsInChildren = visual.GetComponentsInChildren<MeshFilter>(true);
			foreach (MeshFilter val in componentsInChildren)
			{
				MeshRenderer component = ((Component)val).GetComponent<MeshRenderer>();
				Mesh sharedMesh = val.sharedMesh;
				if ((Object)(object)component == (Object)null || (Object)(object)sharedMesh == (Object)null)
				{
					continue;
				}
				if (!sharedMesh.isReadable)
				{
					Warn("Table mesh '" + ((Object)sharedMesh).name + "' is not readable: its top cannot be turned into ice.");
					continue;
				}
				Matrix4x4 val2 = table.transform.worldToLocalMatrix * ((Component)val).transform.localToWorldMatrix;
				Vector3[] vertices = sharedMesh.vertices;
				Vector3[] normals = sharedMesh.normals;
				TopMesh topMesh = new TopMesh
				{
					Filter = val,
					Rend = component,
					Mesh = sharedMesh
				};
				topMesh.Pos = (Vector3[])(object)new Vector3[vertices.Length];
				for (int j = 0; j < vertices.Length; j++)
				{
					topMesh.Pos[j] = ((Matrix4x4)(ref val2)).MultiplyPoint3x4(vertices[j]);
					num = Mathf.Min(num, topMesh.Pos[j].y);
					num2 = Mathf.Max(num2, topMesh.Pos[j].y);
				}
				topMesh.Nrm = (Vector3[])(object)new Vector3[(normals.Length == vertices.Length) ? normals.Length : 0];
				for (int k = 0; k < topMesh.Nrm.Length; k++)
				{
					Vector3[] nrm = topMesh.Nrm;
					int num3 = k;
					Vector3 val3 = ((Matrix4x4)(ref val2)).MultiplyVector(normals[k]);
					nrm[num3] = ((Vector3)(ref val3)).normalized;
				}
				topMesh.Tris = new int[sharedMesh.subMeshCount][];
				for (int l = 0; l < sharedMesh.subMeshCount; l++)
				{
					topMesh.Tris[l] = sharedMesh.GetTriangles(l);
				}
				list.Add(topMesh);
			}
		}
		if (list.Count == 0)
		{
			Warn("No readable mesh found on the rune table: no ice top.");
			return;
		}
		float num4 = num + 0.45f * (num2 - num);
		Dictionary<int, float> dictionary = new Dictionary<int, float>();
		foreach (TopMesh item in list)
		{
			int[][] tris = item.Tris;
			foreach (int[] array in tris)
			{
				for (int n = 0; n + 2 < array.Length; n += 3)
				{
					if (TopFace(item, array[n], array[n + 1], array[n + 2], out var h, out var a) && !(h < num4))
					{
						int key = Mathf.RoundToInt(h / 0.02f);
						dictionary.TryGetValue(key, out var value);
						dictionary[key] = value + a;
					}
				}
			}
		}
		if (dictionary.Count == 0)
		{
			Warn("No flat top surface found on the rune table: no ice top.");
			return;
		}
		int num5 = 0;
		float num6 = -1f;
		foreach (KeyValuePair<int, float> item2 in dictionary)
		{
			if (item2.Value > num6)
			{
				num6 = item2.Value;
				num5 = item2.Key;
			}
		}
		int num7 = 0;
		foreach (TopMesh item3 in list)
		{
			int subMeshCount = item3.Mesh.subMeshCount;
			List<int> list2 = new List<int>();
			List<int>[] array2 = new List<int>[subMeshCount];
			for (int num8 = 0; num8 < subMeshCount; num8++)
			{
				array2[num8] = new List<int>();
				int[] array3 = item3.Tris[num8];
				for (int num9 = 0; num9 + 2 < array3.Length; num9 += 3)
				{
					float h2;
					float a2;
					List<int> list3 = ((TopFace(item3, array3[num9], array3[num9 + 1], array3[num9 + 2], out h2, out a2) && Mathf.Abs(Mathf.RoundToInt(h2 / 0.02f) - num5) <= 1) ? list2 : array2[num8]);
					list3.Add(array3[num9]);
					list3.Add(array3[num9 + 1]);
					list3.Add(array3[num9 + 2]);
				}
			}
			if (list2.Count == 0)
			{
				continue;
			}
			Mesh val4 = Object.Instantiate<Mesh>(item3.Mesh);
			((Object)val4).name = ((Object)item3.Mesh).name + "_dnm_ice";
			val4.subMeshCount = subMeshCount + 1;
			for (int num10 = 0; num10 < subMeshCount; num10++)
			{
				val4.SetTriangles(array2[num10].ToArray(), num10);
			}
			val4.SetTriangles(list2.ToArray(), subMeshCount);
			Material[] sharedMaterials = ((Renderer)item3.Rend).sharedMaterials;
			if (sharedMaterials.Length != 0)
			{
				Material[] array4 = (Material[])(object)new Material[subMeshCount + 1];
				for (int num11 = 0; num11 < subMeshCount; num11++)
				{
					array4[num11] = sharedMaterials[Mathf.Min(num11, sharedMaterials.Length - 1)];
				}
				array4[subMeshCount] = IceMaterial(sharedMaterials[Mathf.Min(subMeshCount - 1, sharedMaterials.Length - 1)]);
				item3.Filter.sharedMesh = val4;
				((Renderer)item3.Rend).sharedMaterials = array4;
				num7 += list2.Count / 3;
			}
		}
		Info("Rune table: " + num7 + " triangles of the top surface turned into ice");
	}

	private static bool TopFace(TopMesh t, int i0, int i1, int i2, out float h, out float a)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = t.Pos[i0];
		Vector3 val2 = t.Pos[i1];
		Vector3 val3 = t.Pos[i2];
		h = (val.y + val2.y + val3.y) / 3f;
		Vector3 val4 = Vector3.Cross(val2 - val, val3 - val);
		a = ((Vector3)(ref val4)).magnitude * 0.5f;
		if (a < 1E-07f)
		{
			return false;
		}
		float num = ((t.Nrm.Length == 0) ? (Mathf.Abs(val4.y) / ((Vector3)(ref val4)).magnitude) : ((t.Nrm[i0].y + t.Nrm[i1].y + t.Nrm[i2].y) / 3f));
		return num > 0.85f;
	}

	private static bool ReskinWithRuneTable(GameObject table, Piece piece, GameObject rune)
	{
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_011a: Expected O, but got Unknown
		List<GameObject> list = new List<GameObject>();
		Renderer[] componentsInChildren = table.GetComponentsInChildren<Renderer>(true);
		Behaviour[] componentsInChildren2 = table.GetComponentsInChildren<Behaviour>(true);
		foreach (Transform item in ((IEnumerable)rune.transform).Cast<Transform>().ToList())
		{
			if ((Object)(object)((Component)item).GetComponentInChildren<Renderer>(true) != (Object)null || (Object)(object)((Component)item).GetComponentInChildren<Light>(true) != (Object)null)
			{
				GameObject val = Object.Instantiate<GameObject>(((Component)item).gameObject, table.transform, false);
				((Object)val).name = "dnm_" + ((Object)item).name;
				StripColliders(val);
				list.Add(val);
			}
		}
		if (list.Count == 0)
		{
			MeshRenderer component = rune.GetComponent<MeshRenderer>();
			MeshFilter component2 = rune.GetComponent<MeshFilter>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null)
			{
				return false;
			}
			GameObject val2 = new GameObject("dnm_rune_model");
			val2.transform.SetParent(table.transform, false);
			val2.AddComponent<MeshFilter>().sharedMesh = component2.sharedMesh;
			((Renderer)val2.AddComponent<MeshRenderer>()).sharedMaterials = ((Renderer)component).sharedMaterials;
			list.Add(val2);
		}
		Renderer[] array = componentsInChildren;
		foreach (Renderer val3 in array)
		{
			val3.enabled = false;
		}
		Behaviour[] array2 = componentsInChildren2;
		foreach (Behaviour val4 in array2)
		{
			string name = ((object)val4).GetType().Name;
			switch (name)
			{
			default:
				if (!name.StartsWith("LightFlicker") && !name.StartsWith("LightLod"))
				{
					continue;
				}
				break;
			case "Light":
			case "AudioSource":
			case "ZSFX":
				break;
			}
			val4.enabled = false;
		}
		foreach (GameObject item2 in list)
		{
			RecolorObject(item2, Recolor.AllWhite, "DNM_Table");
		}
		try
		{
			ApplyIceTop(table, list);
		}
		catch (Exception ex)
		{
			Warn("Ice top of the table failed (plain white kept): " + ex.Message);
		}
		Piece component3 = rune.GetComponent<Piece>();
		if ((Object)(object)component3 != (Object)null && (Object)(object)component3.m_icon != (Object)null)
		{
			Sprite icon = (piece.m_icon = RecolorSprite(component3.m_icon, Recolor.AllWhite));
			if ((Object)(object)station != (Object)null)
			{
				station.m_icon = icon;
			}
		}
		return true;
	}

	private static GameObject FindBlackForge()
	{
		if (index.TryGetValue("blackforge", out var value) && (Object)(object)value.GetComponent<CraftingStation>() != (Object)null)
		{
			return value;
		}
		foreach (KeyValuePair<string, GameObject> item in index)
		{
			if (!item.Key.StartsWith("DNM_") && !((Object)(object)item.Value.GetComponent<CraftingStation>() == (Object)null))
			{
				string text = NormNoAccent(item.Key);
				if (!text.Contains("ext") && (text.Contains("blackforge") || text.Contains("forgenoire")))
				{
					return item.Value;
				}
			}
		}
		foreach (KeyValuePair<string, GameObject> item2 in index)
		{
			if (item2.Key.StartsWith("DNM_") || (Object)(object)item2.Value.GetComponent<CraftingStation>() == (Object)null)
			{
				continue;
			}
			Piece component = item2.Value.GetComponent<Piece>();
			if (!((Object)(object)component == (Object)null))
			{
				string text2 = PieceNames(item2.Value, component);
				if ((text2.Contains("blackforge") || text2.Contains("forgenoire")) && !text2.Contains("ext"))
				{
					return item2.Value;
				}
			}
		}
		return null;
	}

	private static bool IsAudioComponent(Component c)
	{
		if ((Object)(object)c == (Object)null)
		{
			return false;
		}
		string name = ((object)c).GetType().Name;
		return name == "AudioSource" || name == "ZSFX";
	}

	private static void ApplyBlackForgeSounds(GameObject table, Piece piece, CraftingStation st)
	{
		GameObject val = FindBlackForge();
		if ((Object)(object)val == (Object)null)
		{
			Warn("Black forge not found: the table keeps the sounds of its base.");
			return;
		}
		CraftingStation component = val.GetComponent<CraftingStation>();
		st.m_craftItemEffects = component.m_craftItemEffects;
		st.m_craftItemDoneEffects = component.m_craftItemDoneEffects;
		st.m_repairItemDoneEffects = component.m_repairItemDoneEffects;
		Piece component2 = val.GetComponent<Piece>();
		if ((Object)(object)component2 != (Object)null)
		{
			piece.m_placeEffect = component2.m_placeEffect;
		}
		WearNTear component3 = val.GetComponent<WearNTear>();
		WearNTear component4 = table.GetComponent<WearNTear>();
		if ((Object)(object)component3 != (Object)null && (Object)(object)component4 != (Object)null)
		{
			component4.m_destroyedEffect = component3.m_destroyedEffect;
			component4.m_hitEffect = component3.m_hitEffect;
			component4.m_switchEffect = component3.m_switchEffect;
		}
		Behaviour[] componentsInChildren = table.GetComponentsInChildren<Behaviour>(true);
		foreach (Behaviour val2 in componentsInChildren)
		{
			if (IsAudioComponent((Component)(object)val2))
			{
				val2.enabled = false;
			}
		}
		GameObject val3 = Object.Instantiate<GameObject>(val, table.transform, false);
		((Object)val3).name = "dnm_blackforge_sfx";
		for (int j = 0; j < 2; j++)
		{
			Component[] componentsInChildren2 = val3.GetComponentsInChildren<Component>(true);
			foreach (Component val4 in componentsInChildren2)
			{
				if (val4 is MonoBehaviour && !IsAudioComponent(val4))
				{
					Object.DestroyImmediate((Object)(object)val4);
				}
			}
		}
		Component[] componentsInChildren3 = val3.GetComponentsInChildren<Component>(true);
		foreach (Component val5 in componentsInChildren3)
		{
			if (val5 is Renderer)
			{
				Object.DestroyImmediate((Object)(object)val5);
			}
		}
		for (int m = 0; m < 2; m++)
		{
			Component[] componentsInChildren4 = val3.GetComponentsInChildren<Component>(true);
			foreach (Component val6 in componentsInChildren4)
			{
				if ((Object)(object)val6 != (Object)null && !(val6 is Transform) && !IsAudioComponent(val6))
				{
					Object.DestroyImmediate((Object)(object)val6);
				}
			}
		}
		HashSet<Transform> hashSet = new HashSet<Transform>();
		int num = 0;
		Component[] componentsInChildren5 = val3.GetComponentsInChildren<Component>(true);
		foreach (Component val7 in componentsInChildren5)
		{
			if (IsAudioComponent(val7))
			{
				num++;
				Transform val8 = val7.transform;
				while ((Object)(object)val8 != (Object)null && (Object)(object)val8 != (Object)(object)table.transform)
				{
					hashSet.Add(val8);
					val8 = val8.parent;
				}
			}
		}
		List<Transform> list = val3.GetComponentsInChildren<Transform>(true).ToList();
		for (int num3 = list.Count - 1; num3 >= 0; num3--)
		{
			Transform val9 = list[num3];
			if ((Object)(object)val9 != (Object)null && (Object)(object)val9 != (Object)(object)val3.transform && !hashSet.Contains(val9))
			{
				Object.DestroyImmediate((Object)(object)((Component)val9).gameObject);
			}
		}
		Info("Table sounds copied from " + ((Object)val).name + " (" + num + " ambient sound sources)");
	}

	private static void BuildTable()
	{
		//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b5: Unknown result type (might be due to invalid IL or missing references)
		GameObject value = null;
		string[] array = new string[4] { "piece_cauldron", "piece_magetable", "piece_artisanstation", "forge" };
		foreach (string key in array)
		{
			if (index.TryGetValue(key, out value) && (Object)(object)value.GetComponent<CraftingStation>() != (Object)null)
			{
				break;
			}
			value = null;
		}
		if ((Object)(object)value == (Object)null)
		{
			Warn("No base crafting station found: items will be craftable without a station.");
			return;
		}
		GameObject val = Clone(value, "DNM_Table");
		Piece component = val.GetComponent<Piece>();
		station = val.GetComponent<CraftingStation>();
		component.m_name = "$dnm_table";
		component.m_description = "$dnm_table_desc";
		component.m_category = (PieceCategory)1;
		station.m_name = "$dnm_table";
		component.m_craftingStation = null;
		SetField(station, "m_craftRequireFire", false);
		SetField(station, "m_craftRequireRoof", false);
		component.m_resources = ParseReqs(TableCfg("Gold|Bloodgold:10,Frostwood|Timberwood:10,Eitr:20")).ToArray();
		try
		{
			GameObject val2 = FindRuneTable();
			if ((Object)(object)val2 != (Object)null && ReskinWithRuneTable(val, component, val2))
			{
				Info("Rune table model: " + ((Object)val2).name);
			}
			else
			{
				Warn("Rune table model not found: the Galdr table model is used, retextured white.");
				RecolorObject(val, Recolor.AllWhite, "DNM_Table");
			}
			Light[] componentsInChildren = val.GetComponentsInChildren<Light>(true);
			foreach (Light val3 in componentsInChildren)
			{
				val3.color = Color.Lerp(val3.color, new Color(0.85f, 0.95f, 1f, 1f), 0.85f);
			}
		}
		catch (Exception ex)
		{
			Warn("White retexture of the table failed (simple tint used): " + ex.Message);
			Tint(val, IceTint, 0.5f);
		}
		if (Plugin.Cfg.Bind<bool>("6 - Crafting table: Northern Rune Table", "Table_BlackForgeSounds", true, "true = the table uses the sounds of the Black Forge (crafting, repair, building, hits, destruction, ambient). false = the sounds of its base piece. Needs a game restart.").Value)
		{
			try
			{
				ApplyBlackForgeSounds(val, component, station);
			}
			catch (Exception ex2)
			{
				Warn("Black forge sounds failed (base sounds kept): " + ex2.Message);
			}
		}
		Info("Rune table base: " + ((Object)value).name);
		L("dnm_table", "Deep North Rune Table");
		L("dnm_table_desc", "A snow-white rune table with an icy top, to craft the Northern war archmage equipment, the Fracture Staff and summon scrolls.");
	}

	private static string PieceNames(GameObject go, Piece pc)
	{
		string text = pc.m_name ?? "";
		try
		{
			if (Localization.instance != null)
			{
				text = Localization.instance.Localize(text);
			}
		}
		catch
		{
		}
		return NormNoAccent(((Object)go).name + " " + pc.m_name + " " + text);
	}

	private static GameObject FindExtension(params string[] keys)
	{
		if (!extensionsListed)
		{
			extensionsListed = true;
			foreach (KeyValuePair<string, GameObject> item in index)
			{
				Piece component = item.Value.GetComponent<Piece>();
				if ((Object)(object)component != (Object)null && (Object)(object)item.Value.GetComponent<StationExtension>() != (Object)null && !item.Key.StartsWith("DNM_"))
				{
					Info("Station extension in the game: " + item.Key + " (" + component.m_name + ")");
				}
			}
		}
		foreach (KeyValuePair<string, GameObject> item2 in index)
		{
			if (item2.Key.StartsWith("DNM_"))
			{
				continue;
			}
			Piece component2 = item2.Value.GetComponent<Piece>();
			if ((Object)(object)component2 == (Object)null || (Object)(object)item2.Value.GetComponent<StationExtension>() == (Object)null)
			{
				continue;
			}
			string text = PieceNames(item2.Value, component2);
			foreach (string value in keys)
			{
				if (text.Contains(value))
				{
					return item2.Value;
				}
			}
		}
		return null;
	}

	private static string PartKey(Renderer r, Transform root)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append(((Object)r).name).Append(' ').Append(MeshName(r));
		Transform parent = ((Component)r).transform.parent;
		while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root)
		{
			stringBuilder.Append(' ').Append(((Object)parent).name);
			parent = parent.parent;
		}
		Material[] sharedMaterials = r.sharedMaterials;
		foreach (Material val in sharedMaterials)
		{
			if ((Object)(object)val != (Object)null)
			{
				stringBuilder.Append(' ').Append(((Object)val).name);
			}
		}
		return NormNoAccent(stringBuilder.ToString());
	}

	private static void RecolorParts(GameObject go, string id, List<PartRule> rules, Recolor? defaultMode, Recolor? fallbackAll)
	{
		List<Renderer> list = (from r in go.GetComponentsInChildren<Renderer>(true)
			where !IsEffectRenderer(r)
			select r).ToList();
		Recolor?[] array = new Recolor?[list.Count];
		bool flag = false;
		for (int num = 0; num < list.Count; num++)
		{
			string text = PartKey(list[num], go.transform);
			foreach (PartRule rule in rules)
			{
				bool flag2 = false;
				string[] keys = rule.Keys;
				foreach (string value in keys)
				{
					if (text.Contains(value))
					{
						flag2 = true;
					}
				}
				if (flag2)
				{
					array[num] = rule.Mode;
					flag = true;
					break;
				}
			}
		}
		for (int num3 = 0; num3 < list.Count; num3++)
		{
			Recolor? recolor = array[num3];
			if (!recolor.HasValue)
			{
				recolor = (flag ? defaultMode : fallbackAll);
			}
			Renderer val = list[num3];
			Plugin.Log.LogInfo((object)(id + " part '" + ((Object)val).name + "' mesh '" + MeshName(val) + "' materials [" + string.Join(",", (from x in val.sharedMaterials
				where (Object)(object)x != (Object)null
				select ((Object)x).name).ToArray()) + "] -> " + ((!recolor.HasValue) ? "unchanged" : recolor.ToString())));
			if (recolor.HasValue)
			{
				Material[] sharedMaterials = val.sharedMaterials;
				Material[] array2 = (Material[])(object)new Material[sharedMaterials.Length];
				for (int num4 = 0; num4 < sharedMaterials.Length; num4++)
				{
					array2[num4] = (((Object)(object)sharedMaterials[num4] != (Object)null) ? RecolorMaterial(sharedMaterials[num4], recolor.Value) : null);
				}
				val.sharedMaterials = array2;
			}
		}
	}

	private static bool IsViolet(Color c)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		RgbToHsv(c.r, c.g, c.b, out var h, out var s, out var v);
		return h >= 250f && h <= 335f && s > 0.2f && v > 0.2f;
	}

	private static void IceFlames(GameObject go)
	{
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0046: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0083: Unknown result type (might be due to invalid IL or missing references)
		//IL_0087: 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_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
		Light[] componentsInChildren = go.GetComponentsInChildren<Light>(true);
		foreach (Light val in componentsInChildren)
		{
			if (IsViolet(val.color))
			{
				val.color = Color.Lerp(val.color, new Color(0.7f, 0.9f, 1f, 1f), 0.9f);
			}
		}
		ParticleSystem[] componentsInChildren2 = go.GetComponentsInChildren<ParticleSystem>(true);
		foreach (ParticleSystem val2 in componentsInChildren2)
		{
			try
			{
				MainModule main = val2.main;
				MinMaxGradient startColor = ((MainModule)(ref main)).startColor;
				Color color = ((MinMaxGradient)(ref startColor)).color;
				if (IsViolet(color))
				{
					((MainModule)(ref main)).startColor = new MinMaxGradient(new Color(0.65f, 0.88f, 1f, color.a));
				}
			}
			catch
			{
			}
		}
	}

	private static string UpgradeCfg(string en, string def)
	{
		return Plugin.Cfg.Bind<string>("6 - Crafting table: Northern Rune Table", "Upgrade_" + en.Replace(" ", "") + "_Ingredients", def, "Build cost: Name:amount, comma separated. Use | for alternative names. The upgrade must be built near the Deep North Rune Table and adds one level to it.").Value;
	}

	private static bool BuildUpgrade(string id, string en, string descEn, string[] keys, string defCost, Recolor iconMode, Action<GameObject, string> look)
	{
		//IL_007d: Unknown result type (might be due to invalid IL or missing references)
		GameObject val = FindExtension(keys);
		if ((Object)(object)val == (Object)null)
		{
			Warn("Upgrade '" + en + "': no matching station extension found in the game (see the 'Station extension in the game' lines).");
			return false;
		}
		GameObject val2 = Clone(val, id);
		Piece component = val2.GetComponent<Piece>();
		StationExtension component2 = val2.GetComponent<StationExtension>();
		string text = id.ToLowerInvariant();
		component.m_name = "$" + text;
		component.m_description = "$" + text + "_desc";
		component.m_category = (PieceCategory)1;
		component.m_craftingStation = station;
		component2.m_craftingStation = station;
		component.m_resources = ParseReqs(UpgradeCfg(en, defCost)).ToArray();
		try
		{
			component.m_icon = RecolorSprite(component.m_icon, iconMode);
		}
		catch (Exception ex)
		{
			Warn("Icon of " + en + " not recolored: " + ex.Message);
		}
		try
		{
			look(val2, id);
		}
		catch (Exception ex2)
		{