Decompiled source of Galdr v0.8.6

plugins/Galdr/Galdr.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Galdr;

public static class ConfigSync
{
	private static readonly List<ConfigEntryBase> Entries = new List<ConfigEntryBase>();

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

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

	public static bool HasServerValues => Overrides.Count > 0;

	private static string Key(ConfigEntryBase e)
	{
		return e.Definition.Section + "/" + e.Definition.Key;
	}

	public static void Register(ConfigEntryBase e)
	{
		Entries.Add(e);
	}

	public static bool TryGetFloat(ConfigEntryBase e, out float v)
	{
		v = 0f;
		if (Overrides.Count == 0)
		{
			return false;
		}
		string key = Key(e);
		if (FloatCache.TryGetValue(key, out v))
		{
			return true;
		}
		if (!Overrides.TryGetValue(key, out var value))
		{
			return false;
		}
		if (!float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
		{
			return false;
		}
		FloatCache[key] = v;
		return true;
	}

	public static bool TryGetString(ConfigEntryBase e, out string v)
	{
		v = null;
		if (Overrides.Count > 0)
		{
			return Overrides.TryGetValue(Key(e), out v);
		}
		return false;
	}

	public static void Reset()
	{
		Overrides.Clear();
		FloatCache.Clear();
	}

	public static void RequestFromServer()
	{
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Expected O, but got Unknown
		if (!((Object)(object)ZNet.instance == (Object)null) && ZRoutedRpc.instance != null && !ZNet.instance.IsServer())
		{
			ZPackage val = new ZPackage();
			val.Write("0.8.6");
			ZRoutedRpc.instance.InvokeRoutedRPC(0L, "Galdr_CfgReq", new object[1] { val });
		}
	}

	public static void OnRequest(long sender, ZPackage req)
	{
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Expected O, but got Unknown
		if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
		{
			return;
		}
		string text = "?";
		try
		{
			text = req.ReadString();
		}
		catch
		{
		}
		if (text != "0.8.6")
		{
			Plugin.Log.LogWarning((object)("A player joined with Galdr " + text + ", the server runs 0.8.6"));
		}
		ZPackage val = new ZPackage();
		val.Write(Entries.Count);
		foreach (ConfigEntryBase entry in Entries)
		{
			val.Write(Key(entry));
			val.Write(entry.GetSerializedValue());
		}
		val.Write("0.8.6");
		ZRoutedRpc.instance.InvokeRoutedRPC(sender, "Galdr_Cfg", new object[1] { val });
	}

	public static void OnConfig(long sender, ZPackage pkg)
	{
		if ((Object)(object)ZNet.instance == (Object)null || ZNet.instance.IsServer())
		{
			return;
		}
		Reset();
		int num = pkg.ReadInt();
		for (int i = 0; i < num; i++)
		{
			string key = pkg.ReadString();
			string value = pkg.ReadString();
			Overrides[key] = value;
		}
		Plugin.Log.LogInfo((object)("Balance received from the server: " + num + " values"));
		string text = "?";
		try
		{
			text = pkg.ReadString();
		}
		catch
		{
		}
		if (text != "0.8.6")
		{
			Plugin.Log.LogWarning((object)("Galdr version differs: server " + text + ", you 0.8.6"));
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer != (Object)null)
			{
				((Character)localPlayer).Message((MessageType)2, Lang.F("sync.version_mismatch", text, "0.8.6"), 0, (Sprite)null, false);
			}
		}
	}
}
public class SyncedFloat
{
	public readonly ConfigEntry<float> Entry;

	public float Value
	{
		get
		{
			if (!ConfigSync.TryGetFloat((ConfigEntryBase)(object)Entry, out var v))
			{
				return Entry.Value;
			}
			return v;
		}
	}

	public SyncedFloat(ConfigEntry<float> entry)
	{
		Entry = entry;
		ConfigSync.Register((ConfigEntryBase)(object)entry);
	}
}
public class SyncedString
{
	public readonly ConfigEntry<string> Entry;

	public string Value
	{
		get
		{
			if (!ConfigSync.TryGetString((ConfigEntryBase)(object)Entry, out var v))
			{
				return Entry.Value;
			}
			return v;
		}
	}

	public SyncedString(ConfigEntry<string> entry)
	{
		Entry = entry;
		ConfigSync.Register((ConfigEntryBase)(object)entry);
	}
}
public static class GaldrConfig
{
	public static ConfigEntry<KeyCode> CastKey;

	public static ConfigEntry<KeyCode> TreeKey;

	public static ConfigEntry<string> ModelPath;

	public static ConfigEntry<string> VoiceLanguage;

	public static ConfigEntry<string> ExtraStopWords;

	public static ConfigEntry<string> MicDevice;

	public static ConfigEntry<float> MatchThreshold;

	public static ConfigEntry<float> InstantThreshold;

	public static ConfigEntry<float> NoiseGate;

	public static ConfigEntry<float> BindConsistency;

	public static ConfigEntry<float> BindConflict;

	public static ConfigEntry<bool> ShowHeard;

	public static ConfigEntry<bool> HideWords;

	public static ConfigEntry<bool> SpellBar;

	public static SyncedFloat LoadoutSlots;

	public static ConfigEntry<string> HandAnim;

	public static ConfigEntry<bool> QuickCast;

	public static ConfigEntry<bool> Loudness;

	public static ConfigEntry<bool> ReadyChime;

	public static ConfigEntry<bool> CrosshairWord;

	public static ConfigEntry<bool> HandGlow;

	public static ConfigEntry<bool> LearnWords;

	public static ConfigEntry<bool> SuggestWords;

	public static ConfigEntry<string> ListenMode;

	public static ConfigEntry<KeyCode> ConfirmKey;

	public static SyncedFloat ShoutBonus;

	public static SyncedFloat RestedRegen;

	public static SyncedFloat AltarRegen;

	public static SyncedFloat RuneFoes;

	public static ConfigEntry<bool> RequireStaff;

	public static ConfigEntry<float> UiScale;

	public static ConfigEntry<float> HudX;

	public static ConfigEntry<float> HudY;

	public static ConfigEntry<bool> DebugCheat;

	public static ConfigEntry<bool> TestCast;

	public static ConfigEntry<bool> BookEnabled;

	public static ConfigEntry<float> BookCheckSeconds;

	public static ConfigEntry<float> BookLeaseHours;

	public static ConfigEntry<float> BookExtra;

	public static SyncedFloat NotesChance;

	public static SyncedFloat TrickChance;

	public static SyncedFloat TrophyFirstMult;

	public static SyncedFloat TrophyRepeatMult;

	public static ConfigEntry<KeyCode> TestCastKey;

	public static readonly Dictionary<int, SyncedFloat> KillByBiome = new Dictionary<int, SyncedFloat>();

	public static SyncedFloat Star1Mult;

	public static SyncedFloat Star2Mult;

	public static SyncedFloat HealPerKnowledge;

	public static SyncedFloat RuneStoneReward;

	public static SyncedFloat SummonShare;

	public static SyncedFloat CastPerTier;

	public static SyncedFloat[] BossFirst = new SyncedFloat[7];

	public static SyncedFloat[] BossRepeat = new SyncedFloat[7];

	public static SyncedFloat[] RankBase = new SyncedFloat[7];

	public static SyncedFloat[] OpenPrice = new SyncedFloat[7];

	public static SyncedFloat ForeignMult;

	public static SyncedFloat Threshold;

	public static SyncedFloat CoreRankBase;

	public static SyncedString[] BossGate = new SyncedString[7];

	public static SyncedFloat PowerPerRank;

	public static SyncedFloat ManaPerRank;

	public static SyncedFloat CooldownPerRank;

	public static SyncedFloat ManaBase;

	public static SyncedFloat ManaRegen;

	public static SyncedFloat CoreManaPerRank;

	public static SyncedFloat CoreRegenPerRank;

	public static SyncedFloat CoreMightPerRank;

	public static SyncedFloat MagicArmor;

	public static SyncedFloat PvpControl;

	public static SyncedFloat UseEitr;

	public static SyncedFloat Resonance;

	public static SyncedFloat BossPercentMult;

	public static SyncedFloat AllowPvp;

	public static SyncedFloat UseWeakness;

	public static SyncedFloat SkillBonus;

	public static SyncedFloat ProjectileSpeed;

	public static SyncedFloat Misfires;

	public static SyncedString RespecItem;

	public static SyncedFloat RespecAmount;

	public static SyncedFloat RemoveVanillaMagic;

	public const int Meadows = 1;

	public const int Swamp = 2;

	public const int Mountain = 4;

	public const int BlackForest = 8;

	public const int Plains = 16;

	public const int AshLands = 32;

	public const int DeepNorth = 64;

	public const int Ocean = 256;

	public const int Mistlands = 512;

	public static readonly string[] BossTokens = new string[7] { "$enemy_eikthyr", "$enemy_gdking", "$enemy_bonemass", "$enemy_dragon", "$enemy_goblinking", "$enemy_seekerqueen", "$enemy_fader" };

	public static readonly string[] BossKeys = new string[7] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" };

	private static ConfigFile _cfg;

	private static readonly Dictionary<string, float> OldDefaults = new Dictionary<string, float>
	{
		{ "3_1/Power", 15f },
		{ "3_1/Cooldown", 4f },
		{ "3_2/Power", 12f },
		{ "3_2/ManaCost", 25f },
		{ "3_2/Cooldown", 10f },
		{ "3_5/Power", 4f }
	};

	public static string BossTitle(int i)
	{
		return Progression.BossName(BossKeys[i]);
	}

	private static SyncedFloat F(string section, string key, float def, string desc)
	{
		return new SyncedFloat(_cfg.Bind<float>(section, key, def, desc));
	}

	private static string D(string key, params object[] args)
	{
		return Lang.F("cfg." + key, args);
	}

	public static void Init(ConfigFile cfg)
	{
		_cfg = cfg;
		CastKey = cfg.Bind<KeyCode>("1. Controls", "CastKey", (KeyCode)326, D("CastKey"));
		TreeKey = cfg.Bind<KeyCode>("1. Controls", "GaldrWindowKey", (KeyCode)107, D("GaldrWindowKey"));
		RequireStaff = cfg.Bind<bool>("1. Controls", "RequireStaff", false, D("RequireStaff"));
		UiScale = cfg.Bind<float>("1. Controls", "UiScale", 1f, D("UiScale"));
		HudX = cfg.Bind<float>("1. Controls", "HudX", 30f, D("HudX"));
		HudY = cfg.Bind<float>("1. Controls", "HudY", 175f, D("HudY"));
		SpellBar = cfg.Bind<bool>("1. Controls", "WordBar", true, D("WordBar"));
		LoadoutSlots = F("1. Controls", "LoadoutSlots", 4f, D("LoadoutSlots"));
		HandAnim = cfg.Bind<string>("1. Controls", "CastAnimationWithoutStaff", "throw_bomb", D("CastAnimationWithoutStaff"));
		QuickCast = cfg.Bind<bool>("1. Controls", "QuickCast", true, D("QuickCast"));
		ReadyChime = cfg.Bind<bool>("1. Controls", "ReadyChime", true, D("ReadyChime"));
		CrosshairWord = cfg.Bind<bool>("1. Controls", "WordAtCrosshair", true, D("WordAtCrosshair"));
		HandGlow = cfg.Bind<bool>("1. Controls", "ListeningGlow", true, D("ListeningGlow"));
		ListenMode = cfg.Bind<string>("1. Controls", "ListenMode", "Hold", D("ListenMode"));
		ConfirmKey = cfg.Bind<KeyCode>("1. Controls", "ConfirmWordKey", (KeyCode)121, D("ConfirmWordKey"));
		ModelPath = cfg.Bind<string>("2. Voice", "ModelPath", "", D("ModelPath"));
		VoiceLanguage = cfg.Bind<string>("2. Voice", "VoiceLanguage", "auto", D("VoiceLanguage"));
		MicDevice = cfg.Bind<string>("2. Voice", "Microphone", "", D("Microphone"));
		MatchThreshold = cfg.Bind<float>("2. Voice", "MatchThreshold", 0.72f, D("MatchThreshold"));
		InstantThreshold = cfg.Bind<float>("2. Voice", "InstantThreshold", 0.9f, D("InstantThreshold"));
		NoiseGate = cfg.Bind<float>("2. Voice", "NoiseGate", 0f, D("NoiseGate"));
		Loudness = cfg.Bind<bool>("2. Voice", "WhisperAndShout", true, D("WhisperAndShout"));
		LearnWords = cfg.Bind<bool>("2. Voice", "LearnWords", true, D("LearnWords"));
		SuggestWords = cfg.Bind<bool>("2. Voice", "SuggestWords", true, D("SuggestWords"));
		BindConsistency = cfg.Bind<float>("2. Voice", "BindConsistency", 0.6f, D("BindConsistency"));
		BindConflict = cfg.Bind<float>("2. Voice", "BindConflict", 0.75f, D("BindConflict"));
		ExtraStopWords = cfg.Bind<string>("2. Voice", "ExtraStopWords", "", D("ExtraStopWords"));
		ShowHeard = cfg.Bind<bool>("2. Voice", "ShowHeard", true, D("ShowHeard"));
		HideWords = cfg.Bind<bool>("2. Voice", "HideWords", false, D("HideWords"));
		Misfires = new SyncedFloat(cfg.Bind<float>("5. Effects", "Misfires", 1f, D("Misfires")));
		DebugCheat = cfg.Bind<bool>("9. Debug", "KnowledgeButton", false, D("KnowledgeButton"));
		TestCast = cfg.Bind<bool>("9. Debug", "CastWithoutVoice", false, D("CastWithoutVoice"));
		TestCastKey = cfg.Bind<KeyCode>("9. Debug", "TestCastKey", (KeyCode)327, D("TestCastKey"));
		BookEnabled = cfg.Bind<bool>("7. Galdrabok", "Enabled", true, D("Enabled"));
		BookCheckSeconds = cfg.Bind<float>("7. Galdrabok", "CheckSeconds", 180f, D("CheckSeconds"));
		BookLeaseHours = cfg.Bind<float>("7. Galdrabok", "LostAfterHours", 168f, D("LostAfterHours"));
		BookExtra = cfg.Bind<float>("7. Galdrabok", "ExtraBooks", 0f, D("ExtraBooks"));
		NotesChance = new SyncedFloat(cfg.Bind<float>("7. Galdrabok", "NotesChance", 0.12f, D("NotesChance")));
		TrickChance = new SyncedFloat(cfg.Bind<float>("7. Galdrabok", "TechniqueChance", 0.05f, D("TechniqueChance")));
		TrophyFirstMult = new SyncedFloat(cfg.Bind<float>("7. Galdrabok", "FirstTrophy", 10f, D("FirstTrophy")));
		TrophyRepeatMult = new SyncedFloat(cfg.Bind<float>("7. Galdrabok", "RepeatTrophy", 1f, D("RepeatTrophy")));
		RuneFoes = F("8. Foes", "RuneFoes", 1f, D("RuneFoes"));
		KillByBiome[1] = F("3. Knowledge", "KillMeadows", 2f, D("kill", D("biome.Meadows")));
		KillByBiome[8] = F("3. Knowledge", "KillBlackForest", 4f, D("kill", D("biome.BlackForest")));
		KillByBiome[2] = F("3. Knowledge", "KillSwamp", 7f, D("kill", D("biome.Swamp")));
		KillByBiome[4] = F("3. Knowledge", "KillMountain", 10f, D("kill", D("biome.Mountain")));
		KillByBiome[16] = F("3. Knowledge", "KillPlains", 14f, D("kill", D("biome.Plains")));
		KillByBiome[512] = F("3. Knowledge", "KillMistlands", 18f, D("kill", D("biome.Mistlands")));
		KillByBiome[32] = F("3. Knowledge", "KillAshlands", 24f, D("kill", D("biome.Ashlands")));
		KillByBiome[64] = F("3. Knowledge", "KillDeepNorth", 24f, D("kill", D("biome.DeepNorth")));
		KillByBiome[256] = F("3. Knowledge", "KillOcean", 10f, D("kill", D("biome.Ocean")));
		Star1Mult = F("3. Knowledge", "Star1Mult", 2f, D("Star1Mult"));
		Star2Mult = F("3. Knowledge", "Star2Mult", 3f, D("Star2Mult"));
		HealPerKnowledge = F("3. Knowledge", "HealPerKnowledge", 0f, D("HealPerKnowledge"));
		RuneStoneReward = F("3. Knowledge", "RuneStone", 10f, D("RuneStone"));
		SummonShare = F("3. Knowledge", "SummonShare", 0.5f, D("SummonShare"));
		CastPerTier = F("3. Knowledge", "PerCast", 1f, D("PerCast"));
		float[] array = new float[7] { 80f, 150f, 250f, 400f, 600f, 800f, 1000f };
		float[] array2 = new float[7] { 20f, 40f, 60f, 100f, 150f, 200f, 250f };
		for (int i = 0; i < 7; i++)
		{
			BossFirst[i] = F("3. Knowledge", "Boss" + (i + 1) + "First", array[i], D("boss_first", BossTitle(i)));
			BossRepeat[i] = F("3. Knowledge", "Boss" + (i + 1) + "Repeat", array2[i], D("boss_repeat", BossTitle(i)));
		}
		float[] array3 = new float[7] { 0f, 5f, 8f, 12f, 18f, 25f, 35f };
		float[] array4 = new float[7] { 0f, 60f, 60f, 120f, 200f, 350f, 700f };
		for (int j = 1; j <= 6; j++)
		{
			string text = ((j == 6) ? "Hybrid" : ("T" + j));
			RankBase[j] = F("4. Prices", "RankBase" + text, array3[j], D("rank_base", text));
			OpenPrice[j] = F("4. Prices", "Unlock" + text, array4[j], (j == 1) ? D("unlock_t1") : D("unlock", text));
		}
		string[] array5 = new string[7] { "", "", "", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking" };
		for (int k = 3; k <= 6; k++)
		{
			string text2 = ((k == 6) ? "Hybrid" : ("T" + k));
			BossGate[k] = new SyncedString(cfg.Bind<string>("4. Prices", "BossFor" + text2, array5[k], D("boss_gate", text2)));
		}
		ForeignMult = F("4. Prices", "ForeignMult", 2f, D("ForeignMult"));
		Threshold = F("4. Prices", "Threshold", 9f, D("Threshold"));
		CoreRankBase = F("4. Prices", "CoreRankBase", 10f, D("CoreRankBase"));
		RespecItem = new SyncedString(cfg.Bind<string>("4. Prices", "RespecItem", "$item_greydwarfeye", D("RespecItem")));
		RespecAmount = F("4. Prices", "RespecAmount", 10f, D("RespecAmount"));
		PowerPerRank = F("5. Effects", "PowerPerRank", 0.06f, D("PowerPerRank"));
		ManaPerRank = F("5. Effects", "ManaPerRank", 0.04f, D("ManaPerRank"));
		CooldownPerRank = F("5. Effects", "CooldownPerRank", 0.04f, D("CooldownPerRank"));
		ManaBase = F("5. Effects", "ManaBase", 40f, D("ManaBase"));
		ManaRegen = F("5. Effects", "ManaRegen", 0.6f, D("ManaRegen"));
		CoreManaPerRank = F("5. Effects", "CoreManaPerRank", 8f, D("CoreManaPerRank"));
		CoreRegenPerRank = F("5. Effects", "CoreRegenPerRank", 0.1f, D("CoreRegenPerRank"));
		CoreMightPerRank = F("5. Effects", "CoreMightPerRank", 0.02f, D("CoreMightPerRank"));
		RemoveVanillaMagic = F("5. Effects", "RemoveVanillaMagic", 1f, D("RemoveVanillaMagic"));
		UseEitr = F("5. Effects", "EitrToMana", 1f, D("EitrToMana"));
		Resonance = F("5. Effects", "WorldResonance", 0.25f, D("WorldResonance"));
		BossPercentMult = F("5. Effects", "BossPercentMult", 0.2f, D("BossPercentMult"));
		AllowPvp = F("5. Effects", "PvP", 1f, D("PvP"));
		MagicArmor = F("5. Effects", "MagicArmor", 0.5f, D("MagicArmor"));
		PvpControl = F("5. Effects", "PvpControl", 0.5f, D("PvpControl"));
		ShoutBonus = F("5. Effects", "ShoutBonus", 0.1f, D("ShoutBonus"));
		RestedRegen = F("5. Effects", "RestedManaRegen", 0.5f, D("RestedManaRegen"));
		AltarRegen = F("5. Effects", "AltarManaRegen", 1f, D("AltarManaRegen"));
		ProjectileSpeed = F("5. Effects", "ProjectileSpeed", 15f, D("ProjectileSpeed"));
		UseWeakness = F("5. Effects", "MobWeakness", 1f, D("MobWeakness"));
		SkillBonus = F("5. Effects", "SkillBonus", 0.25f, D("SkillBonus"));
		foreach (SpellDef item in SpellDefs.All)
		{
			string section = "6. Spell " + item.Id;
			item.CfgPower = F(section, "Power", item.Power, D("spell_power", item.PowerLabel));
			item.CfgMana = F(section, "ManaCost", item.Mana, D("spell_mana"));
			item.CfgCooldown = F(section, "Cooldown", item.Cooldown, D("spell_cooldown"));
			Rebalance(item.CfgPower, item.Id + "/Power", item.Power);
			Rebalance(item.CfgMana, item.Id + "/ManaCost", item.Mana);
			Rebalance(item.CfgCooldown, item.Id + "/Cooldown", item.Cooldown);
		}
	}

	private static void Rebalance(SyncedFloat f, string key, float now)
	{
		if (f != null && OldDefaults.TryGetValue(key, out var value) && Mathf.Abs(f.Entry.Value - value) < 0.0001f && Mathf.Abs(value - now) > 0.0001f)
		{
			f.Entry.Value = now;
			Plugin.Log.LogInfo((object)("Rebalanced " + key + ": " + value + " -> " + now));
		}
	}

	public static float KillReward(int biome)
	{
		if (KillByBiome.TryGetValue(biome, out var value))
		{
			return value.Value;
		}
		return KillByBiome[1].Value;
	}

	public static HashSet<string> StopWordSet()
	{
		HashSet<string> hashSet = new HashSet<string>();
		foreach (string item in VoiceLang.StopWords())
		{
			string text = WordMatcher.Normalize(item);
			if (text.Length > 0)
			{
				hashSet.Add(text);
			}
		}
		return hashSet;
	}

	public static string Fmt(float v)
	{
		return v.ToString((v % 1f == 0f) ? "0" : "0.#", CultureInfo.InvariantCulture);
	}
}
public static class Lang
{
	public sealed class Pack
	{
		public string Code = "";

		public string Name = "";

		public string File = "";

		public string Dir = "";

		public string ModelDir;

		public readonly List<string> Game = new List<string>();

		public readonly List<string> StopWords = new List<string>();

		public readonly List<KeyValuePair<string, string>> Fold = new List<KeyValuePair<string, string>>();

		public readonly Dictionary<string, string> S = new Dictionary<string, string>();

		public string Title
		{
			get
			{
				if (Name.Length <= 0)
				{
					return Code.ToUpperInvariant();
				}
				return Name;
			}
		}
	}

	public static readonly List<Pack> Packs = new List<Pack>();

	public static ConfigEntry<string> Setting;

	private static Pack _ui;

	private static int _mainThread = -1;

	private static readonly Regex FileRe = new Regex("^Galdr([A-Za-z]{2,8})\\.lang$", RegexOptions.IgnoreCase);

	private static readonly FieldInfo LocInstance = AccessTools.Field(typeof(Localization), "m_instance");

	private static string _uiFor;

	private static float _uiCheck = -1f;

	private static bool OnMainThread
	{
		get
		{
			if (_mainThread >= 0)
			{
				return Thread.CurrentThread.ManagedThreadId == _mainThread;
			}
			return true;
		}
	}

	public static int Version { get; private set; }

	public static Pack Ui
	{
		get
		{
			string text = ((Setting != null) ? (Setting.Value ?? "").Trim() : "auto");
			if (text.Length > 0 && !text.Equals("auto", StringComparison.OrdinalIgnoreCase))
			{
				Pack pack = Find(text);
				if (pack != null)
				{
					return pack;
				}
			}
			if (!OnMainThread)
			{
				return _ui ?? Fallback();
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			if (_ui != null && realtimeSinceStartup < _uiCheck)
			{
				return _ui;
			}
			_uiCheck = realtimeSinceStartup + 1f;
			string text2 = null;
			try
			{
				if (LocInstance != null && LocInstance.GetValue(null) != null)
				{
					text2 = Localization.instance.GetSelectedLanguage();
				}
			}
			catch
			{
			}
			if (text2 == null)
			{
				return _ui ?? Fallback();
			}
			if (_ui != null && text2 == _uiFor)
			{
				return _ui;
			}
			_uiFor = text2;
			Pack ui = _ui;
			_ui = ByGame(text2) ?? Fallback();
			if (ui != null && ui != _ui)
			{
				Version++;
			}
			Plugin.Log.LogInfo((object)("Interface language: " + ((_ui != null) ? _ui.Code : "none") + " (game: " + text2 + ")"));
			return _ui;
		}
	}

	public static void Load()
	{
		_mainThread = Thread.CurrentThread.ManagedThreadId;
		Packs.Clear();
		_ui = null;
		try
		{
			string pluginPath = Paths.PluginPath;
			if (Directory.Exists(pluginPath))
			{
				Walk(pluginPath, 0);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Language packs: " + ex.Message));
		}
		List<string> list = new List<string>();
		foreach (Pack pack in Packs)
		{
			list.Add(pack.Code + " (" + pack.S.Count + " strings" + ((pack.ModelDir != null) ? ", voice" : "") + ")");
		}
		Plugin.Log.LogInfo((object)("Language packs: " + ((list.Count == 0) ? "none" : string.Join(", ", list.ToArray()))));
	}

	private static void Walk(string dir, int depth)
	{
		if (depth > 4)
		{
			return;
		}
		try
		{
			string[] files = Directory.GetFiles(dir, "Galdr*.lang");
			foreach (string text in files)
			{
				if (FileRe.IsMatch(Path.GetFileName(text)))
				{
					Parse(text);
				}
			}
			files = Directory.GetDirectories(dir);
			foreach (string text2 in files)
			{
				if (!Directory.Exists(Path.Combine(text2, "am")) || !Directory.Exists(Path.Combine(text2, "conf")))
				{
					Walk(text2, depth + 1);
				}
			}
		}
		catch
		{
		}
	}

	private static void Parse(string file)
	{
		Pack pack = new Pack
		{
			File = file,
			Dir = Path.GetDirectoryName(file)
		};
		Match match = FileRe.Match(Path.GetFileName(file));
		pack.Code = match.Groups[1].Value.ToLowerInvariant();
		string text = null;
		string[] array = File.ReadAllLines(file, Encoding.UTF8);
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].TrimStart(new char[1] { '\ufeff' });
			if (text2.Trim().Length == 0 || text2.TrimStart(Array.Empty<char>()).StartsWith("#"))
			{
				continue;
			}
			int num = text2.IndexOf('=');
			if (num <= 0)
			{
				continue;
			}
			string text3 = text2.Substring(0, num).Trim();
			string text4 = Value(text2.Substring(num + 1));
			if (text3.StartsWith("@"))
			{
				switch (text3.ToLowerInvariant())
				{
				case "@code":
					if (text4.Trim().Length > 0)
					{
						pack.Code = text4.Trim().ToLowerInvariant();
					}
					break;
				case "@name":
					pack.Name = text4.Trim();
					break;
				case "@game":
				{
					string[] array2 = text4.Split(new char[1] { ',' });
					foreach (string text7 in array2)
					{
						if (text7.Trim().Length > 0)
						{
							pack.Game.Add(text7.Trim());
						}
					}
					break;
				}
				case "@stopwords":
				{
					string[] array2 = text4.Split(new char[1] { ',' });
					foreach (string text6 in array2)
					{
						if (text6.Trim().Length > 0)
						{
							pack.StopWords.Add(text6.Trim());
						}
					}
					break;
				}
				case "@fold":
				{
					string[] array2 = text4.Split(new char[1] { ',' });
					foreach (string text5 in array2)
					{
						int num2 = text5.IndexOf('>');
						if (num2 > 0)
						{
							pack.Fold.Add(new KeyValuePair<string, string>(text5.Substring(0, num2).Trim(), text5.Substring(num2 + 1).Trim()));
						}
					}
					break;
				}
				case "@model":
					text = text4.Trim();
					break;
				}
			}
			else
			{
				pack.S[text3] = text4;
			}
		}
		if (!string.IsNullOrEmpty(text) && IsModel(Path.Combine(pack.Dir, text)))
		{
			pack.ModelDir = Path.Combine(pack.Dir, text);
		}
		else
		{
			try
			{
				array = Directory.GetDirectories(pack.Dir);
				foreach (string text8 in array)
				{
					if (IsModel(text8))
					{
						pack.ModelDir = text8;
						break;
					}
				}
			}
			catch
			{
			}
		}
		if (Find(pack.Code) != null)
		{
			Plugin.Log.LogWarning((object)("Language pack " + pack.Code + " found twice, using the first: " + Find(pack.Code).File));
		}
		else
		{
			Packs.Add(pack);
		}
	}

	public static bool IsModel(string dir)
	{
		if (Directory.Exists(dir) && Directory.Exists(Path.Combine(dir, "am")))
		{
			return Directory.Exists(Path.Combine(dir, "conf"));
		}
		return false;
	}

	private static string Value(string v)
	{
		v = v.TrimEnd(new char[1] { '\r' });
		if (v.StartsWith(" "))
		{
			v = v.Substring(1);
		}
		string text = v.TrimEnd(Array.Empty<char>());
		v = ((text.Length < 2 || text[0] != '"' || text[text.Length - 1] != '"') ? text : text.Substring(1, text.Length - 2));
		if (v.IndexOf('\\') < 0)
		{
			return v;
		}
		StringBuilder stringBuilder = new StringBuilder(v.Length);
		for (int i = 0; i < v.Length; i++)
		{
			char c = v[i];
			if (c == '\\' && i + 1 < v.Length)
			{
				char c2 = v[++i];
				stringBuilder.Append(c2 switch
				{
					't' => '\t', 
					'n' => '\n', 
					_ => c2, 
				});
			}
			else
			{
				stringBuilder.Append(c);
			}
		}
		return stringBuilder.ToString();
	}

	public static void Bind(ConfigFile cfg)
	{
		List<string> list = new List<string>();
		foreach (Pack pack in Packs)
		{
			list.Add(pack.Code);
		}
		Setting = cfg.Bind<string>("0. Language", "Language", "auto", F("cfg.Language", (list.Count > 0) ? string.Join(", ", list.ToArray()) : T("cfg.Language_none")));
	}

	public static Pack Find(string code)
	{
		if (string.IsNullOrEmpty(code))
		{
			return null;
		}
		foreach (Pack pack in Packs)
		{
			if (string.Equals(pack.Code, code, StringComparison.OrdinalIgnoreCase))
			{
				return pack;
			}
		}
		return null;
	}

	private static Pack ByGame(string g)
	{
		if (string.IsNullOrEmpty(g))
		{
			return null;
		}
		foreach (Pack pack in Packs)
		{
			foreach (string item in pack.Game)
			{
				if (string.Equals(item, g, StringComparison.OrdinalIgnoreCase))
				{
					return pack;
				}
			}
		}
		return null;
	}

	private static Pack Fallback()
	{
		Pack pack = Find("en");
		if (pack == null)
		{
			if (Packs.Count <= 0)
			{
				return null;
			}
			pack = Packs[0];
		}
		return pack;
	}

	public static string T(string key)
	{
		string text = Raw(key);
		if (text.IndexOf('{') >= 0 || text.IndexOf('}') >= 0)
		{
			return text.Replace("{{", "{").Replace("}}", "}");
		}
		return text;
	}

	private static string Raw(string key)
	{
		Pack ui = Ui;
		if (ui != null && ui.S.TryGetValue(key, out var value))
		{
			return value;
		}
		Pack pack = Find("en");
		if (pack != null && pack != ui && pack.S.TryGetValue(key, out value))
		{
			return value;
		}
		foreach (Pack pack2 in Packs)
		{
			if (pack2.S.TryGetValue(key, out value))
			{
				return value;
			}
		}
		return key;
	}

	public static string F(string key, params object[] args)
	{
		string text = Raw(key);
		if (args == null || args.Length == 0)
		{
			return text.Replace("{{", "{").Replace("}}", "}");
		}
		StringBuilder stringBuilder = new StringBuilder(text.Length + 32);
		for (int i = 0; i < text.Length; i++)
		{
			char c = text[i];
			if (c == '{' && i + 1 < text.Length && text[i + 1] == '{')
			{
				stringBuilder.Append('{');
				i++;
				continue;
			}
			if (c == '}' && i + 1 < text.Length && text[i + 1] == '}')
			{
				stringBuilder.Append('}');
				i++;
				continue;
			}
			if (c == '{')
			{
				int num = text.IndexOf('}', i + 1);
				if (num > i && int.TryParse(text.Substring(i + 1, num - i - 1), out var result))
				{
					stringBuilder.Append((result >= 0 && result < args.Length && args[result] != null) ? args[result].ToString() : "");
					i = num;
					continue;
				}
			}
			stringBuilder.Append(c);
		}
		return stringBuilder.ToString();
	}

	public static string Plural(int n, string key)
	{
		string[] array = T(key).Split(new char[1] { '|' });
		if (array.Length < 2)
		{
			return array[0];
		}
		int num = Math.Abs(n);
		if (array.Length == 2)
		{
			if (num != 1)
			{
				return array[1];
			}
			return array[0];
		}
		int num2 = num % 10;
		int num3 = num % 100;
		if (num2 == 1 && num3 != 11)
		{
			return array[0];
		}
		if (num2 >= 2 && num2 <= 4 && (num3 < 12 || num3 > 14))
		{
			return array[1];
		}
		return array[2];
	}

	public static string Word(string w)
	{
		return F("fmt.word", w);
	}

	public static bool Has(string key)
	{
		foreach (Pack pack in Packs)
		{
			if (pack.S.ContainsKey(key))
			{
				return true;
			}
		}
		return false;
	}
}
public sealed class Tx
{
	public readonly string Key;

	private readonly object[] _args;

	public Tx(string key, params object[] args)
	{
		Key = key;
		_args = args;
	}

	public override string ToString()
	{
		return Lang.F(Key, _args);
	}

	public static implicit operator string(Tx t)
	{
		return t?.ToString();
	}
}
public static class ArmorSets
{
	private class Set
	{
		public string Name;

		public Element Element;

		public bool Mage;

		public string[] Keys;
	}

	public const float PowerPerPiece = 0.05f;

	public const float FullSetManaCut = 0.15f;

	public const int FullSet = 3;

	public const float MagePowerPerPiece = 0.03f;

	public const float MageRegenPerPiece = 0.1f;

	public const float WardPerPiece = 0.08f;

	public const float MageWardPerPiece = 0.04f;

	public const float FirePerkPower = 0.1f;

	public const float LightningPerkCd = 0.15f;

	public const float IcePerkTaken = 0.1f;

	public const float DarkPerkLeech = 0.15f;

	public const float LifePerkRegen = 0.15f;

	private static readonly Set[] Sets = new Set[11]
	{
		new Set
		{
			Name = "bronze",
			Element = Element.Fire,
			Keys = new string[2] { "HelmetBronze", "ArmorBronze" }
		},
		new Set
		{
			Name = "flametal",
			Element = Element.Fire,
			Keys = new string[1] { "Flametal" }
		},
		new Set
		{
			Name = "troll",
			Element = Element.Lightning,
			Keys = new string[2] { "TrollLeather", "CapeTrollHide" }
		},
		new Set
		{
			Name = "fenris",
			Element = Element.Lightning,
			Keys = new string[1] { "Fenring" }
		},
		new Set
		{
			Name = "iron",
			Element = Element.Ice,
			Keys = new string[2] { "HelmetIron", "ArmorIron" }
		},
		new Set
		{
			Name = "wolf",
			Element = Element.Ice,
			Keys = new string[3] { "ArmorWolf", "HelmetDrake", "CapeWolf" }
		},
		new Set
		{
			Name = "rags",
			Element = Element.Dark,
			Keys = new string[1] { "ArmorRags" }
		},
		new Set
		{
			Name = "carapace",
			Element = Element.Dark,
			Keys = new string[1] { "Carapace" }
		},
		new Set
		{
			Name = "leather",
			Element = Element.Life,
			Keys = new string[3] { "ArmorLeather", "HelmetLeather", "CapeDeerHide" }
		},
		new Set
		{
			Name = "root",
			Element = Element.Life,
			Keys = new string[2] { "ArmorRoot", "HelmetRoot" }
		},
		new Set
		{
			Name = "mage",
			Mage = true,
			Keys = new string[3] { "ArmorMage", "HelmetMage", "CapeFeather" }
		}
	};

	private static readonly int[] Pieces = new int[5];

	private static int _magePieces;

	private static float _nextCount;

	private static Player _countFor;

	private const string MarkTag = "<color=#ffca5e>";

	private static readonly Dictionary<SharedData, KeyValuePair<string, string>> Orig = new Dictionary<SharedData, KeyValuePair<string, string>>();

	private static string Mark => "<color=#ffca5e>" + Lang.T("armor.mark") + "</color>";

	private static Set Match(string prefab)
	{
		if (string.IsNullOrEmpty(prefab))
		{
			return null;
		}
		Set[] sets = Sets;
		foreach (Set set in sets)
		{
			string[] keys = set.Keys;
			foreach (string value in keys)
			{
				if (prefab.IndexOf(value, StringComparison.OrdinalIgnoreCase) >= 0)
				{
					return set;
				}
			}
		}
		return null;
	}

	private static bool IsArmor(SharedData sh)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: 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_0009: Invalid comparison between Unknown and I4
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Invalid comparison between Unknown and I4
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0012: Invalid comparison between Unknown and I4
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Invalid comparison between Unknown and I4
		ItemType itemType = sh.m_itemType;
		if ((int)itemType != 6 && (int)itemType != 7 && (int)itemType != 11)
		{
			return (int)itemType == 17;
		}
		return true;
	}

	private static void Count(Player p)
	{
		if ((Object)(object)p == (Object)(object)_countFor && Time.time < _nextCount)
		{
			return;
		}
		_countFor = p;
		_nextCount = Time.time + 0.5f;
		for (int i = 0; i < 5; i++)
		{
			Pieces[i] = 0;
		}
		_magePieces = 0;
		if ((Object)(object)p == (Object)null || ((Humanoid)p).GetInventory() == null)
		{
			return;
		}
		foreach (ItemData equippedItem in ((Humanoid)p).GetInventory().GetEquippedItems())
		{
			if (equippedItem == null || equippedItem.m_shared == null || !IsArmor(equippedItem.m_shared))
			{
				continue;
			}
			Set set = Match(((Object)(object)equippedItem.m_dropPrefab != (Object)null) ? ((Object)equippedItem.m_dropPrefab).name : null);
			if (set != null)
			{
				if (set.Mage)
				{
					_magePieces++;
				}
				else
				{
					Pieces[(int)set.Element]++;
				}
			}
		}
	}

	private static int PiecesFor(SpellDef d)
	{
		if (d == null)
		{
			return 0;
		}
		if (!d.IsHybrid)
		{
			if (d.Element >= Element.Hybrid)
			{
				return 0;
			}
			return Pieces[(int)d.Element];
		}
		return Mathf.Max(Pieces[(int)d.HybA], Pieces[(int)d.HybB]);
	}

	private static bool Perk(SpellDef d, Element e)
	{
		if (d == null || Pieces[(int)e] < 3)
		{
			return false;
		}
		if (!d.IsHybrid)
		{
			return d.Element == e;
		}
		if (d.HybA != e)
		{
			return d.HybB == e;
		}
		return true;
	}

	public static float PowerMult(SpellDef d)
	{
		Count(Player.m_localPlayer);
		return 1f + (float)PiecesFor(d) * 0.05f + (float)_magePieces * 0.03f + (Perk(d, Element.Fire) ? 0.1f : 0f);
	}

	public static float CooldownMult(SpellDef d)
	{
		Count(Player.m_localPlayer);
		if (!Perk(d, Element.Lightning))
		{
			return 1f;
		}
		return 0.85f;
	}

	public static float DamageTakenMult(Player p)
	{
		Count(p);
		if (Pieces[2] < 3)
		{
			return 1f;
		}
		return 0.9f;
	}

	public static float WardMult(Player p, DmgType? type)
	{
		Count(p);
		int num = 0;
		if (type.HasValue)
		{
			num = type.Value switch
			{
				DmgType.Fire => Pieces[0], 
				DmgType.Frost => Pieces[2], 
				DmgType.Lightning => Pieces[1], 
				_ => Pieces[3] + Pieces[4], 
			};
		}
		float num2 = Mathf.Min(0.4f, (float)num * 0.08f) + (float)_magePieces * 0.04f;
		return Mathf.Clamp(1f - num2, 0.5f, 1f);
	}

	public static void OnSpellDamage(CastContext c, float amount)
	{
		if (c == null || (Object)(object)c.P == (Object)null || (Object)(object)c.P != (Object)(object)Player.m_localPlayer || amount <= 0f)
		{
			return;
		}
		Count(c.P);
		if (Perk(c.D, Element.Dark))
		{
			MagicController instance = MagicController.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				instance.Mana = Mathf.Min(instance.MaxMana(), instance.Mana + amount * 0.15f);
			}
		}
	}

	public static float ManaMult(SpellDef d)
	{
		Count(Player.m_localPlayer);
		if (PiecesFor(d) < 3)
		{
			return 1f;
		}
		return 0.85f;
	}

	public static float RegenBonus(Player p)
	{
		Count(p);
		return (float)_magePieces * 0.1f + ((Pieces[4] >= 3) ? 0.15f : 0f);
	}

	public static int HeavyForeign(Player p)
	{
		if ((Object)(object)p == (Object)null || ((Humanoid)p).GetInventory() == null)
		{
			return 0;
		}
		MagicController instance = MagicController.Instance;
		int num = (((Object)(object)instance != (Object)null && instance.Data != null) ? instance.Data.native : (-1));
		int num2 = 0;
		foreach (ItemData equippedItem in ((Humanoid)p).GetInventory().GetEquippedItems())
		{
			if (equippedItem != null && equippedItem.m_shared != null && IsArmor(equippedItem.m_shared) && !(equippedItem.m_shared.m_movementModifier >= -0.001f))
			{
				Set set = Match(((Object)(object)equippedItem.m_dropPrefab != (Object)null) ? ((Object)equippedItem.m_dropPrefab).name : null);
				if (set == null || (!set.Mage && set.Element != (Element)num))
				{
					num2++;
				}
			}
		}
		return num2;
	}

	public static void Relocalize()
	{
		foreach (KeyValuePair<SharedData, KeyValuePair<string, string>> item in new List<KeyValuePair<SharedData, KeyValuePair<string, string>>>(Orig))
		{
			item.Key.m_description = item.Value.Key;
			Append(item.Key, item.Value.Value);
		}
	}

	public static void Describe(ObjectDB db)
	{
		if ((Object)(object)db == (Object)null || db.m_items == null)
		{
			return;
		}
		foreach (GameObject item in db.m_items)
		{
			ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val == (Object)null) && val.m_itemData != null && val.m_itemData.m_shared != null)
			{
				SharedData shared = val.m_itemData.m_shared;
				if (IsArmor(shared) && (shared.m_description == null || !shared.m_description.Contains("<color=#ffca5e>")))
				{
					Orig[shared] = new KeyValuePair<string, string>(shared.m_description, ((Object)item).name);
					Append(shared, ((Object)item).name);
				}
			}
		}
	}

	private static void Append(SharedData sh, string prefabName)
	{
		Set set = Match(prefabName);
		bool flag = sh.m_movementModifier < -0.001f && (set == null || !set.Mage);
		if (set == null)
		{
			if (flag)
			{
				sh.m_description = sh.m_description + "\n\n" + Lang.F("armorsets.heavy_line", Mark, Mathf.RoundToInt(5f));
			}
			return;
		}
		string text;
		if (set.Mage)
		{
			text = Lang.F("armorsets.mage_set", Pct(0.03f), Pct(0.1f), Pct(0.04f));
		}
		else
		{
			Element e = ((set.Element == Element.Life) ? Element.Dark : set.Element);
			string text2 = Lang.F("armorsets.piece", SpellDefs.ElementName(set.Element), Pct(0.05f), SpellDefs.ElementName(e), Pct(0.08f));
			object[] obj = new object[2]
			{
				Mathf.RoundToInt(15.000001f),
				null
			};
			int element = (int)set.Element;
			obj[1] = Lang.T("armorsets.perk." + element);
			text = text2 + "\n" + Lang.F("armorsets.set3", obj);
		}
		if (flag)
		{
			text = text + "\n" + Lang.F("armorsets.heavy_foreign", Mathf.RoundToInt(5f));
		}
		sh.m_description = sh.m_description + "\n\n" + Mark + " " + text;
	}

	private static string Pct(float f)
	{
		return "<color=orange>" + Mathf.RoundToInt(f * 100f) + "%</color>";
	}

	public static string Summary(SpellDef d)
	{
		float num = PowerMult(d);
		float num2 = ManaMult(d);
		float num3 = CooldownMult(d);
		bool flag = Perk(d, Element.Dark);
		if (num <= 1.0001f && num2 >= 0.9999f && num3 >= 0.9999f && !flag)
		{
			return null;
		}
		string text = Lang.F("armorsets.summary_power", Mathf.RoundToInt((num - 1f) * 100f));
		if (num2 < 0.9999f)
		{
			text += Lang.F("armorsets.summary_mana", Mathf.RoundToInt((1f - num2) * 100f));
		}
		if (num3 < 0.9999f)
		{
			text += Lang.F("armorsets.cooldown", Mathf.RoundToInt((1f - num3) * 100f));
		}
		if (flag)
		{
			text += Lang.F("armorsets.leech", Mathf.RoundToInt(15.000001f));
		}
		return text;
	}
}
public static class Chronicle
{
	private const string Red = "<color=#6b1d0c>";

	private const string End = "</color>";

	private static PlayerMagicData D
	{
		get
		{
			if (!((Object)(object)MagicController.Instance != (Object)null))
			{
				return null;
			}
			return MagicController.Instance.Data;
		}
	}

	private static void Dirty()
	{
		if ((Object)(object)MagicController.Instance != (Object)null)
		{
			MagicController.Instance.MarkDirtySoft();
		}
	}

	public static void Cast(SpellDef d, float mana, MagicController.Loud loud)
	{
		PlayerMagicData d2 = D;
		if (d2 != null && d != null)
		{
			d2.casts.TryGetValue(d.Id, out var value);
			d2.casts[d.Id] = value + 1;
			d2.manaSpent += Mathf.Max(0f, mana);
			switch (loud)
			{
			case MagicController.Loud.Shout:
				d2.shouts++;
				break;
			case MagicController.Loud.Whisper:
				d2.whispers++;
				break;
			}
			if (d2.firstCast == 0L)
			{
				d2.firstCast = DateTime.UtcNow.Ticks;
			}
			Dirty();
		}
	}

	public static void Misfire(float mana)
	{
		PlayerMagicData d = D;
		if (d != null)
		{
			d.misfires++;
			d.manaSpent += Mathf.Max(0f, mana);
			Dirty();
		}
	}

	public static void Hit(SpellDef d, float amount)
	{
		PlayerMagicData d2 = D;
		if (d2 != null && d != null && !(amount <= d2.bestHit))
		{
			d2.bestHit = amount;
			d2.bestHitSpell = d.Id;
			Dirty();
		}
	}

	public static void Kill()
	{
		PlayerMagicData d = D;
		if (d != null)
		{
			d.kills++;
			Dirty();
		}
	}

	private static string Name(string id)
	{
		SpellDef spellDef = SpellDefs.Get(id);
		if (spellDef != null)
		{
			return spellDef.Name;
		}
		return "?";
	}

	public static BookText.Page Page()
	{
		PlayerMagicData d = D;
		if (d == null || d.casts.Count == 0)
		{
			return null;
		}
		int num = 0;
		string text = null;
		int num2 = 0;
		int[] array = new int[5];
		int num3 = 0;
		foreach (KeyValuePair<string, int> cast in d.casts)
		{
			num += cast.Value;
			if (cast.Value > num2)
			{
				num2 = cast.Value;
				text = cast.Key;
			}
			SpellDef spellDef = SpellDefs.Get(cast.Key);
			if (spellDef != null)
			{
				if (spellDef.IsHybrid)
				{
					num3 += cast.Value;
				}
				else
				{
					array[(int)spellDef.Element] += cast.Value;
				}
			}
		}
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("<i>").Append(Lang.T("chron.intro")).Append("</i>\n\n");
		if (d.firstCast > 0)
		{
			stringBuilder.Append(Lang.F("chron.since", new DateTime(d.firstCast, DateTimeKind.Utc).ToLocalTime().ToString(Lang.T("fmt.date"), CultureInfo.InvariantCulture))).Append('\n');
		}
		stringBuilder.Append(Lang.F("chron.casts", "<color=#6b1d0c>", num, "</color>")).Append('\n');
		if (text != null)
		{
			stringBuilder.Append(Lang.F("chron.favorite", "<color=#6b1d0c>", Name(text), "</color>", num2, Lang.Plural(num2, "plural.times"))).Append('\n');
		}
		stringBuilder.Append('\n');
		Element[] ring = SpellDefs.Ring;
		foreach (Element element in ring)
		{
			if (array[(int)element] > 0)
			{
				stringBuilder.Append(Lang.F("chron.element", SpellDefs.ElementName(element), array[(int)element])).Append('\n');
			}
		}
		if (num3 > 0)
		{
			stringBuilder.Append(Lang.F("chron.element", Lang.T("booktext.two_elements"), num3)).Append('\n');
		}
		stringBuilder.Append('\n');
		stringBuilder.Append(Lang.F("chron.kills", "<color=#6b1d0c>", d.kills, "</color>")).Append('\n');
		if (d.bestHit > 0f && !string.IsNullOrEmpty(d.bestHitSpell))
		{
			stringBuilder.Append(Lang.F("chron.best", "<color=#6b1d0c>", Mathf.RoundToInt(d.bestHit), "</color>", Name(d.bestHitSpell))).Append('\n');
		}
		stringBuilder.Append(Lang.F("chron.mana", Mathf.RoundToInt(d.manaSpent))).Append('\n');
		if (d.shouts > 0 || d.whispers > 0)
		{
			stringBuilder.Append(Lang.F("chron.loud", d.shouts, d.whispers)).Append('\n');
		}
		if (d.quick > 0)
		{
			stringBuilder.Append(Lang.F("chron.quick", d.quick)).Append('\n');
		}
		if (d.misfires > 0)
		{
			stringBuilder.Append(Lang.F("chron.misfires", d.misfires)).Append('\n');
		}
		return new BookText.Page
		{
			Title = Lang.T("chron.title"),
			Body = stringBuilder.ToString().TrimEnd(Array.Empty<char>()),
			Small = true
		};
	}
}
public static class Foci
{
	public class Fx
	{
		public Element El;

		public float Power;

		public float Cooldown;

		public float Regen;

		public float OthersPower;

		public bool HasWeak;

		public Element Weak;

		public float WeakPower;

		public float KillMana;

		public float HpRegen = 1f;
	}

	public static readonly Dictionary<string, Fx> ById = new Dictionary<string, Fx>
	{
		{
			"GaldrFocus_Fire1",
			new Fx
			{
				El = Element.Fire,
				Power = 0.2f,
				HasWeak = true,
				Weak = Element.Ice,
				WeakPower = 0.3f
			}
		},
		{
			"GaldrFocus_Fire2",
			new Fx
			{
				El = Element.Fire,
				Power = 0.3f,
				Cooldown = 0.1f,
				HasWeak = true,
				Weak = Element.Ice,
				WeakPower = 0.5f
			}
		},
		{
			"GaldrFocus_Lightning1",
			new Fx
			{
				El = Element.Lightning,
				Power = 0.15f,
				Cooldown = 0.15f,
				Regen = 0.15f
			}
		},
		{
			"GaldrFocus_Lightning2",
			new Fx
			{
				El = Element.Lightning,
				Power = 0.25f,
				Cooldown = 0.2f,
				Regen = 0.25f
			}
		},
		{
			"GaldrFocus_Ice1",
			new Fx
			{
				El = Element.Ice,
				Power = 0.2f,
				HasWeak = true,
				Weak = Element.Fire,
				WeakPower = 0.3f
			}
		},
		{
			"GaldrFocus_Ice2",
			new Fx
			{
				El = Element.Ice,
				Power = 0.3f,
				Cooldown = 0.1f,
				HasWeak = true,
				Weak = Element.Fire,
				WeakPower = 0.5f
			}
		},
		{
			"GaldrFocus_Dark1",
			new Fx
			{
				El = Element.Dark,
				Power = 0.2f,
				KillMana = 3f,
				HpRegen = 0.75f
			}
		},
		{
			"GaldrFocus_Dark2",
			new Fx
			{
				El = Element.Dark,
				Power = 0.3f,
				KillMana = 6f,
				HpRegen = 0.6f,
				HasWeak = true,
				Weak = Element.Life,
				WeakPower = 0.3f
			}
		},
		{
			"GaldrFocus_Life1",
			new Fx
			{
				El = Element.Life,
				Power = 0.25f,
				OthersPower = 0.15f
			}
		},
		{
			"GaldrFocus_Life2",
			new Fx
			{
				El = Element.Life,
				Power = 0.35f,
				Cooldown = 0.1f,
				OthersPower = 0.25f
			}
		}
	};

	private static Fx _cur;

	private static float _next;

	private static Player _for;

	public static Fx Current()
	{
		Player localPlayer = Player.m_localPlayer;
		if ((Object)(object)localPlayer == (Object)(object)_for && Time.time < _next)
		{
			return _cur;
		}
		_for = localPlayer;
		_next = Time.time + 0.5f;
		_cur = null;
		if ((Object)(object)localPlayer == (Object)null || ((Humanoid)localPlayer).GetInventory() == null)
		{
			return null;
		}
		foreach (ItemData equippedItem in ((Humanoid)localPlayer).GetInventory().GetEquippedItems())
		{
			ManaFoods.Def def = ManaFoods.Of(equippedItem);
			if (def != null && def.Kind == ManaFoods.Kind.Focus && ById.TryGetValue(def.Id, out var value))
			{
				_cur = value;
				break;
			}
		}
		return _cur;
	}

	public static bool Equipped(Element e)
	{
		Fx fx = Current();
		if (fx != null)
		{
			return fx.El == e;
		}
		return false;
	}

	private static bool Touches(SpellDef d, Element e)
	{
		if (!d.IsHybrid)
		{
			return d.Element == e;
		}
		if (d.HybA != e)
		{
			return d.HybB == e;
		}
		return true;
	}

	public static float PowerMult(SpellDef d)
	{
		Fx fx = Current();
		if (fx == null || d == null || d.Tier == 0)
		{
			return 1f;
		}
		float num = 1f;
		num = ((!Touches(d, fx.El)) ? (num - fx.OthersPower) : (num + fx.Power));
		if (fx.HasWeak && Touches(d, fx.Weak) && !Touches(d, fx.El))
		{
			num -= fx.WeakPower;
		}
		return Mathf.Max(0.2f, num);
	}

	public static float CooldownMult(SpellDef d)
	{
		Fx fx = Current();
		if (fx == null || d == null || d.Tier == 0 || !Touches(d, fx.El))
		{
			return 1f;
		}
		return 1f - fx.Cooldown;
	}

	public static float RegenPenalty()
	{
		return Current()?.Regen ?? 0f;
	}

	public static void OnKill()
	{
		Fx fx = Current();
		MagicController instance = MagicController.Instance;
		if (fx != null && !(fx.KillMana <= 0f) && !((Object)(object)instance == (Object)null) && instance.Data != null)
		{
			instance.Mana = Mathf.Min(instance.MaxMana(), instance.Mana + fx.KillMana);
		}
	}

	public static string Summary(SpellDef d)
	{
		if (Current() == null || d == null)
		{
			return null;
		}
		float num = PowerMult(d);
		float num2 = CooldownMult(d);
		if (Mathf.Abs(num - 1f) < 0.001f && num2 > 0.999f)
		{
			return null;
		}
		string text = Lang.F("foci.summary", (num >= 1f) ? "+" : "−", Mathf.RoundToInt(Mathf.Abs(num - 1f) * 100f));
		if (num2 < 0.999f)
		{
			text += Lang.F("foci.summary_cooldown", Mathf.RoundToInt((1f - num2) * 100f));
		}
		return text;
	}

	public static string Describe(string id)
	{
		if (!ById.TryGetValue(id, out var value))
		{
			return "";
		}
		string text = SpellDefs.ElementName(value.El).ToLowerInvariant();
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append(Lang.F("foci.line_power", text, Mathf.RoundToInt(value.Power * 100f)));
		if (value.Cooldown > 0f)
		{
			stringBuilder.Append(Lang.F("foci.line_cooldown", Mathf.RoundToInt(value.Cooldown * 100f)));
		}
		if (value.KillMana > 0f)
		{
			stringBuilder.Append(Lang.F("foci.line_killmana", value.KillMana));
		}
		stringBuilder.Append("\n<color=#d06050>");
		List<string> list = new List<string>();
		if (value.HasWeak)
		{
			list.Add(Lang.F("foci.line_weak", SpellDefs.ElementName(value.Weak).ToLowerInvariant(), Mathf.RoundToInt(value.WeakPower * 100f)));
		}
		if (value.OthersPower > 0f)
		{
			list.Add(Lang.F("foci.line_others", Mathf.RoundToInt(value.OthersPower * 100f)));
		}
		if (value.Regen > 0f)
		{
			list.Add(Lang.F("foci.line_mana_slower", Mathf.RoundToInt(value.Regen * 100f)));
		}
		if (value.HpRegen < 1f)
		{
			list.Add(Lang.F("foci.line_hp_slower", Mathf.RoundToInt((1f - value.HpRegen) * 100f)));
		}
		if (value.El == Element.Fire)
		{
			list.Add(Lang.T("foci.rain_dulls_fire_even_more"));
		}
		stringBuilder.Append(string.Join("\n", list.ToArray())).Append("</color>");
		return stringBuilder.ToString();
	}

	public static StatusEffect EquipEffect(string id, string name)
	{
		if (!ById.TryGetValue(id, out var value) || value.HpRegen >= 1f)
		{
			return null;
		}
		SE_Stats obj = ScriptableObject.CreateInstance<SE_Stats>();
		((Object)obj).name = "SE_" + id;
		((StatusEffect)obj).m_name = name;
		((StatusEffect)obj).m_tooltip = Lang.T("foci.health_regenerates_slower");
		obj.m_healthRegenMultiplier = value.HpRegen;
		((StatusEffect)obj).m_icon = GameArt.Element(value.El);
		return (StatusEffect)(object)obj;
	}
}
public static class ManaFoods
{
	public enum Kind
	{
		Food,
		Charm,
		Scroll,
		Book,
		Notes,
		Potion,
		Focus,
		Trick
	}

	public class Def
	{
		public string Id;

		public string Name;

		public string Source;

		public string Station;

		public string Icon;

		public string Desc;

		public string[] IconSrc;

		public Color Tint = Color.white;

		public Color Glow = Color.clear;

		public float TintAmt;

		public float GlowStr;

		public Kind Kind;

		public float Mana;

		public float Regen;

		public float Health;

		public float Stamina;

		public float HpRegen;

		public float Duration;

		public float Knowledge;

		public int MaxQuality = 1;

		public int StationLevel = 1;

		public string[] Res;

		public Element El;

		public int Level = 1;

		public string Trick;

		public GameObject Prefab;
	}

	private static readonly Color ManaTint = new Color(0.45f, 0.62f, 1f);

	private static readonly Color ManaGlow = new Color(0.4f, 0.6f, 1f, 0.8f);

	public static readonly List<Def> All = new List<Def>
	{
		new Def
		{
			Id = "GaldrFood_Meadows",
			Source = "QueensJam",
			Station = "piece_workbench",
			Icon = "food_meadows",
			Mana = 20f,
			Regen = 0.15f,
			Health = 12f,
			Stamina = 10f,
			HpRegen = 1f,
			Duration = 900f,
			Tint = ManaTint,
			TintAmt = 0.3f,
			Glow = ManaGlow,
			GlowStr = 1.3f,
			Res = new string[2] { "Dandelion:4", "Raspberry:3" }
		},
		new Def
		{
			Id = "GaldrFood_BlackForest",
			Source = "CarrotSoup",
			Station = "piece_cauldron",
			Icon = "food_blackforest",
			Mana = 35f,
			Regen = 0.25f,
			Health = 16f,
			Stamina = 14f,
			HpRegen = 1f,
			Duration = 1200f,
			Tint = ManaTint,
			TintAmt = 0.4f,
			Glow = ManaGlow,
			GlowStr = 1.3f,
			Res = new string[3] { "Mushroom:2", "Blueberries:3", "Thistle:1" }
		},
		new Def
		{
			Id = "GaldrFood_Swamp",
			Source = "BlackSoup",
			Station = "piece_cauldron",
			Icon = "food_swamp",
			Mana = 50f,
			Regen = 0.35f,
			Health = 21f,
			Stamina = 18f,
			HpRegen = 2f,
			Duration = 1500f,
			Tint = new Color(0.7f, 0.45f, 1f),
			TintAmt = 0.3f,
			Glow = ManaGlow,
			GlowStr = 1.3f,
			Res = new string[3] { "Bloodbag:1", "Turnip:2", "Thistle:2" }
		},
		new Def
		{
			Id = "GaldrFood_Mountain",
			Source = "OnionSoup",
			Station = "piece_cauldron",
			Icon = "food_mountain",
			Mana = 70f,
			Regen = 0.45f,
			Health = 26f,
			Stamina = 22f,
			HpRegen = 2f,
			Duration = 1500f,
			Tint = new Color(0.55f, 0.85f, 1f),
			TintAmt = 0.45f,
			Glow = new Color(0.55f, 0.85f, 1f, 0.85f),
			GlowStr = 1.4f,
			Res = new string[3] { "Honey:2", "Onion:2", "FreezeGland:1" }
		},
		new Def
		{
			Id = "GaldrFood_Plains",
			Source = "Bread",
			Station = "piece_cauldron",
			Icon = "food_plains",
			Mana = 90f,
			Regen = 0.55f,
			Health = 31f,
			Stamina = 26f,
			HpRegen = 3f,
			Duration = 1800f,
			Tint = ManaTint,
			TintAmt = 0.25f,
			Glow = ManaGlow,
			GlowStr = 1.5f,
			Res = new string[3] { "BarleyFlour:2", "Cloudberry:3", "Honey:1" }
		},
		new Def
		{
			Id = "GaldrFood_Meadows2",
			Source = "Raspberry",
			Station = "piece_workbench",
			Icon = "food_meadows2",
			Mana = 12f,
			Regen = 0.3f,
			Health = 9f,
			Stamina = 9f,
			HpRegen = 1f,
			Duration = 900f,
			Tint = ManaTint,
			TintAmt = 0.25f,
			Glow = ManaGlow,
			GlowStr = 1.2f,
			Res = new string[2] { "Raspberry:6", "Honey:1" }
		},
		new Def
		{
			Id = "GaldrFood_BlackForest2",
			Source = "MushroomYellow",
			Station = "piece_cauldron",
			Icon = "food_blackforest2",
			Mana = 20f,
			Regen = 0.45f,
			Health = 13f,
			Stamina = 12f,
			HpRegen = 1f,
			Duration = 1200f,
			Tint = ManaTint,
			TintAmt = 0.35f,
			Glow = ManaGlow,
			GlowStr = 1.2f,
			Res = new string[3] { "Mushroom:2", "MushroomYellow:2", "Thistle:1" }
		},
		new Def
		{
			Id = "GaldrFood_Swamp2",
			Source = "TurnipStew",
			Station = "piece_cauldron",
			Icon = "food_swamp2",
			Mana = 30f,
			Regen = 0.6f,
			Health = 17f,
			Stamina = 15f,
			HpRegen = 2f,
			Duration = 1500f,
			Tint = new Color(0.6f, 0.5f, 1f),
			TintAmt = 0.3f,
			Glow = ManaGlow,
			GlowStr = 1.2f,
			Res = new string[3] { "Turnip:3", "Mushroom:2", "Thistle:1" }
		},
		new Def
		{
			Id = "GaldrFood_Mountain2",
			Source = "Eyescream",
			Station = "piece_cauldron",
			Icon = "food_mountain2",
			Mana = 40f,
			Regen = 0.75f,
			Health = 21f,
			Stamina = 18f,
			HpRegen = 2f,
			Duration = 1500f,
			Tint = new Color(0.55f, 0.85f, 1f),
			TintAmt = 0.4f,
			Glow = new Color(0.55f, 0.85f, 1f, 0.85f),
			GlowStr = 1.3f,
			Res = new string[3] { "FreezeGland:1", "GreydwarfEye:3", "Honey:1" }
		},
		new Def
		{
			Id = "GaldrFood_Plains2",
			Source = "BloodPudding",
			Station = "piece_cauldron",
			Icon = "food_plains2",
			Mana = 55f,
			Regen = 0.9f,
			Health = 25f,
			Stamina = 21f,
			HpRegen = 3f,
			Duration = 1800f,
			Tint = new Color(1f, 0.75f, 0.4f),
			TintAmt = 0.35f,
			Glow = ManaGlow,
			GlowStr = 1.3f,
			Res = new string[3] { "Cloudberry:4", "Barley:2", "Honey:1" }
		},
		new Def
		{
			Id = "GaldrFood_Mistlands",
			Source = "MagicallyStuffedShroom",
			Station = "piece_cauldron",
			Icon = "food_mistlands",
			Mana = 120f,
			Regen = 0.7f,
			Health = 36f,
			Stamina = 30f,
			HpRegen = 3f,
			Duration = 1800f,
			Tint = new Color(0.7f, 0.55f, 1f),
			TintAmt = 0.3f,
			Glow = new Color(0.7f, 0.5f, 1f, 0.85f),
			GlowStr = 1.4f,
			Res = new string[3] { "Sap:1", "MushroomJotunPuffs:2", "MushroomMagecap:1" }
		},
		new Def
		{
			Id = "GaldrPotion1",
			Source = "MeadHealthMinor",
			Station = "piece_cauldron",
			Icon = "potion1",
			Kind = Kind.Potion,
			Mana = 40f,
			Tint = new Color(0.35f, 0.55f, 1f),
			TintAmt = 0.85f,
			Glow = ManaGlow,
			GlowStr = 1.2f,
			Res = new string[3] { "Dandelion:4", "Raspberry:2", "Honey:1" }
		},
		new Def
		{
			Id = "GaldrPotion2",
			Source = "MeadHealthMedium",
			Station = "piece_cauldron",
			Icon = "potion2",
			Kind = Kind.Potion,
			Mana = 80f,
			Tint = new Color(0.35f, 0.55f, 1f),
			TintAmt = 0.85f,
			Glow = ManaGlow,
			GlowStr = 1.3f,
			Res = new string[3] { "Thistle:2", "Bloodbag:1", "Turnip:1" }
		},
		new Def
		{
			Id = "GaldrPotion3",
			Source = "MeadHealthMajor",
			Station = "piece_cauldron",
			Icon = "potion3",
			Kind = Kind.Potion,
			Mana = 150f,
			Tint = new Color(0.55f, 0.45f, 1f),
			TintAmt = 0.85f,
			Glow = new Color(0.6f, 0.5f, 1f, 0.9f),
			GlowStr = 1.5f,
			Res = new string[2] { "Sap:1", "MushroomMagecap:2" }
		},
		new Def
		{
			Id = "GaldrNotes1",
			Source = "LeatherScraps",
			Icon = "notes1",
			Kind = Kind.Notes,
			Knowledge = 20f,
			Tint = new Color(0.8f, 0.7f, 0.55f),
			TintAmt = 0.3f,
			Glow = new Color(1f, 0.7f, 0.3f, 0.5f),
			GlowStr = 0.8f
		},
		new Def
		{
			Id = "GaldrNotes2",
			Source = "LeatherScraps",
			Icon = "notes2",
			Kind = Kind.Notes,
			Knowledge = 50f,
			Tint = new Color(0.45f, 0.75f, 0.45f),
			TintAmt = 0.45f,
			Glow = new Color(0.5f, 1f, 0.5f, 0.5f),
			GlowStr = 0.9f
		},
		new Def
		{
			Id = "GaldrNotes3",
			Source = "LeatherScraps",
			Icon = "notes3",
			Kind = Kind.Notes,
			Knowledge = 110f,
			Tint = new Color(0.9f, 0.35f, 0.3f),
			TintAmt = 0.45f,
			Glow = new Color(1f, 0.4f, 0.3f, 0.5f),
			GlowStr = 1f
		},
		new Def
		{
			Id = "GaldrCharm",
			Source = "BeltStrength",
			Station = "piece_workbench",
			Icon = "charm",
			Kind = Kind.Charm,
			MaxQuality = 4,
			StationLevel = 2,
			IconSrc = new string[2] { "SilverNecklace", "Wishbone" },
			Tint = new Color(1f, 0.8f, 0.45f),
			TintAmt = 0.3f,
			Glow = new Color(0.55f, 0.75f, 1f, 0.8f),
			GlowStr = 1.4f,
			Res = new string[5] { "LeatherScraps:5:0", "Resin:5:0", "GreydwarfEye:4:0", "Bronze:0:3", "TrophyGreydwarf:0:1" }
		},
		new Def
		{
			Id = "GaldrMead1",
			Source = "MeadTasty",
			Station = "piece_cauldron",
			Icon = "scroll1",
			Kind = Kind.Scroll,
			Knowledge = 25f,
			Tint = new Color(1f, 0.75f, 0.3f),
			TintAmt = 0.45f,
			Glow = new Color(1f, 0.7f, 0.3f, 0.7f),
			GlowStr = 1.3f,
			Res = new string[3] { "Honey:2", "GreydwarfEye:3", "TrophyGreydwarf:1" }
		},
		new Def
		{
			Id = "GaldrMead2",
			Source = "MeadTasty",
			Station = "piece_cauldron",
			Icon = "scroll2",
			Kind = Kind.Scroll,
			Knowledge = 70f,
			Tint = new Color(0.75f, 0.5f, 1f),
			TintAmt = 0.55f,
			Glow = new Color(0.7f, 0.45f, 1f, 0.8f),
			GlowStr = 1.4f,
			Res = new string[4] { "Honey:2", "TrophyDraugr:1", "TrophySkeleton:1", "Bloodbag:2" }
		},
		new Def
		{
			Id = "GaldrMead3",
			Source = "MeadTasty",
			Station = "piece_cauldron",
			Icon = "scroll3",
			Kind = Kind.Scroll,
			Knowledge = 180f,
			Tint = new Color(0.6f, 0.9f, 1f),
			TintAmt = 0.55f,
			Glow = new Color(0.6f, 0.9f, 1f, 0.9f),
			GlowStr = 1.6f,
			Res = new string[3] { "Honey:2", "TrophyGoblin:2", "TrophyFenring:1" }
		},
		Focus("GaldrFocus_Fire1", Element.Fire, 1, "piece_workbench", 3, "SurtlingCore:2", "Coal:10", "Bronze:2"),
		Focus("GaldrFocus_Fire2", Element.Fire, 2, "forge", 3, "SurtlingCore:6", "Obsidian:6", "BlackMetal:3"),
		Focus("GaldrFocus_Lightning1", Element.Lightning, 1, "piece_workbench", 2, "HardAntler:1", "Bronze:2", "LeatherScraps:4"),
		Focus("GaldrFocus_Lightning2", Element.Lightning, 2, "forge", 3, "DragonTear:2", "Silver:4", "Crystal:3"),
		Focus("GaldrFocus_Ice1", Element.Ice, 1, "forge", 2, "WolfFang:4", "FreezeGland:3", "Silver:2"),
		Focus("GaldrFocus_Ice2", Element.Ice, 2, "forge", 3, "DragonTear:3", "FreezeGland:6", "Silver:6"),
		Focus("GaldrFocus_Dark1", Element.Dark, 1, "piece_workbench", 3, "TrophyDraugr:1", "Ooze:5", "Iron:2"),
		Focus("GaldrFocus_Dark2", Element.Dark, 2, "forge", 3, "Wishbone:1", "Ooze:10", "BlackMetal:3"),
		Focus("GaldrFocus_Life1", Element.Life, 1, "piece_workbench", 2, "AncientSeed:3", "FineWood:5", "Resin:10"),
		Focus("GaldrFocus_Life2", Element.Life, 2, "piece_workbench", 4, "YggdrasilWood:6", "Sap:3", "Resin:20"),
		Trick("meteor"),
		Trick("chain"),
		Trick("drain"),
		Trick("shards"),
		Trick("heal"),
		Trick("blast"),
		Trick("freeze"),
		new Def
		{
			Id = "GaldrBook",
			Source = "LeatherScraps",
			Icon = "book",
			Kind = Kind.Book
		}
	};

	private static readonly Dictionary<string, Def> ByItemName = new Dictionary<string, Def>();

	private static GameObject _holder;

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

	private const string TrinketSource = "*trinket";

	private static string _trinket;

	public const float PotionCooldown = 60f;

	private static float _potionReadyAt;

	private static Def Focus(string id, Element el, int level, string station, int stationLevel, params string[] res)
	{
		return new Def
		{
			Id = id,
			El = el,
			Level = level,
			Kind = Kind.Focus,
			Source = "*trinket",
			Station = station,
			StationLevel = stationLevel,
			Res = res
		};
	}

	private static Def Trick(string id)
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_006e: Unknown result type (might be due to invalid IL or missing references)
		return new Def
		{
			Id = "GaldrTrick_" + id,
			Source = "LeatherScraps",
			Kind = Kind.Trick,
			Trick = id,
			Tint = new Color(0.55f, 0.75f, 1f),
			TintAmt = 0.45f,
			Glow = new Color(0.5f, 0.8f, 1f, 0.6f),
			GlowStr = 1f
		};
	}

	private static void FindTrinket(IEnumerable<GameObject> items)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Invalid comparison between Unknown and I4
		if (_trinket != null || items == null)
		{
			return;
		}
		string text = null;
		foreach (GameObject item in items)
		{
			ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent<ItemDrop>() : null);
			if (!((Object)(object)val == (Object)null) && val.m_itemData != null && val.m_itemData.m_shared != null && (int)val.m_itemData.m_shared.m_itemType == 24 && !((Object)item).name.StartsWith("Galdr"))
			{
				if (text == null)
				{
					text = ((Object)item).name;
				}
				if (((Object)item).name.IndexOf("Bronze", StringComparison.OrdinalIgnoreCase) >= 0)
				{
					text = ((Object)item).name;
					break;
				}
			}
		}
		_trinket = text;
		if (_trinket != null)
		{
			Plugin.Log.LogInfo((object)("Foci: base item — " + _trinket));
		}
	}

	private static void Localize(Def d)
	{
		if (d.Kind == Kind.Book)
		{
			d.Name = GuideBook.ItemName;
			d.Desc = Lang.T("item.book.desc");
		}
		else if (d.Kind == Kind.Trick)
		{
			Tricks.Def def = Tricks.ById(d.Trick);
			d.Name = Lang.F("item.trick.name", (def != null) ? ((string)def.Name) : d.Trick);
			d.Desc = Lang.T("item.trick.desc");
		}
		else
		{
			d.Name = Lang.T("item." + d.Id + ".name");
			d.Desc = Lang.T("item." + d.Id + ".desc");
		}
	}

	public static void Relocalize()
	{
		foreach (Def item in All)
		{
			if ((Object)(object)item.Prefab == (Object)null)
			{
				continue;
			}
			ItemDrop component = item.Prefab.GetComponent<ItemDrop>();
			if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null)
			{
				Localize(item);
				SharedData shared = component.m_itemData.m_shared;
				shared.m_name = item.Name;
				shared.m_description = Describe(item);
				if ((Object)(object)shared.m_equipStatusEffect != (Object)null && item.Kind == Kind.Focus)
				{
					shared.m_equipStatusEffect.m_name = item.Name;
				}
			}
		}
	}

	private static string Describe(Def d)
	{
		string text = (string.IsNullOrEmpty(d.Desc) ? "" : (d.Desc + "\n\n"));
		switch (d.Kind)
		{
		case Kind.Charm:
			return text + Lang.T("manafoods.galdr_power_5_per_level");
		case Kind.Scroll:
			return text + Lang.F("manafoods.desc_knowledge", d.Knowledge);
		case Kind.Book:
			return text + Lang.T("manafoods.use_to_read");
		case Kind.Notes:
			return text + Lang.F("manafoods.desc_knowledge", d.Knowledge);
		case Kind.Focus:
			return text + Foci.Describe(d.Id);
		case Kind.Trick:
		{
			Tricks.Def def = Tricks.ById(d.Trick);
			return text + ((def != null) ? string.Concat("<color=orange>", def.Desc, "</color>") : "");
		}
		case Kind.Potion:
			return text + Lang.F("manafoods.desc_potion", d.Mana, 60f);
		default:
			return text + Lang.F("manafoods.desc_food", d.Mana, d.Regen.ToString("0.##"));
		}
	}

	private static void Build(Func<string, GameObject> lookup)
	{
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Expected O, but got Unknown
		//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Expected O, but got Unknown
		//IL_0487: Unknown result type (might be due to invalid IL or missing references)
		//IL_0493: Unknown result type (might be due to invalid IL or missing references)
		//IL_04b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_04bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_040e: Unknown result type (might be due to invalid IL or missing references)
		//IL_041a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0438: Unknown result type (might be due to invalid IL or missing references)
		//IL_0442: Unknown result type (might be due to invalid IL or missing references)
		//IL_0221: Unknown result type (might be due to invalid IL or missing references)
		//IL_023f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0282: Unknown result type (might be due to invalid IL or missing references)
		//IL_028e: Unknown result type (might be due to invalid IL or missing references)
		foreach (Def item in All)
		{
			if ((Object)(object)item.Prefab != (Object)null)
			{
				continue;
			}
			string text = ((item.Source == "*trinket") ? _trinket : item.Source);
			if (text == null)
			{
				continue;
			}
			GameObject val = lookup(text);
			if ((Object)(object)val == (Object)null)
			{
				continue;
			}
			if ((Object)(object)_holder == (Object)null)
			{
				_holder = new GameObject("GaldrItems");
				_holder.SetActive(false);
				Object.DontDestroyOnLoad((Object)(object)_holder);
			}
			GameObject val2 = Object.Instantiate<GameObject>(val, _holder.transform);
			((Object)val2).name = item.Id;
			Localize(item);
			ItemDrop component = val2.GetComponent<ItemDrop>();
			if ((Object)(object)component == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				continue;
			}
			SharedData val3 = (SharedData)Clone.Invoke(component.m_itemData.m_shared, null);
			val3.m_name = item.Name;
			val3.m_description = Describe(item);
			Sprite val4 = null;
			if (item.IconSrc != null)
			{
				string[] iconSrc = item.IconSrc;
				foreach (string arg in iconSrc)
				{
					GameObject val5 = lookup(arg);
					ItemDrop val6 = (((Object)(object)val5 != (Object)null) ? val5.GetComponent<ItemDrop>() : null);
					if ((Object)(object)val6 != (Object)null && val6.m_itemData.m_shared.m_icons != null && val6.m_itemData.m_shared.m_icons.Length != 0)
					{
						val4 = val6.m_itemData.m_shared.m_icons[0];
						break;
					}
				}
			}
			if ((Object)(object)val4 == (Object)null && val3.m_icons != null && val3.m_icons.Length != 0)
			{
				val4 = val3.m_icons[0];
			}
			if (item.Kind == Kind.Focus)
			{
				Sprite val7 = GameArt.Element(item.El);
				val4 = null;
				if ((Object)(object)val7 != (Object)null)
				{
					val3.m_icons = (Sprite[])(object)new Sprite[1] { (item.Level >= 2) ? GameArt.Recolor(val7, item.Id, new Color(1f, 0.85f, 0.5f), 0.15f, new Color(1f, 0.8f, 0.4f, 0.8f), 1.5f) : val7 };
				}
			}
			if ((Object)(object)val4 != (Object)null && item.Kind != Kind.Book && item.Kind != Kind.Notes && item.Kind != Kind.Trick)
			{
				Sprite val8 = GameArt.Recolor(val4, item.Id, item.Tint, item.TintAmt, item.Glow, item.GlowStr);
				if ((Object)(object)val8 != (Object)null)
				{
					val3.m_icons = (Sprite[])(object)new Sprite[1] { val8 };
				}
			}
			switch (item.Kind)
			{
			case Kind.Food:
				val3.m_food = item.Health;
				val3.m_foodStamina = item.Stamina;
				val3.m_foodEitr = 0f;
				val3.m_foodRegen = item.HpRegen;
				val3.m_foodBurnTime = item.Duration;
				val3.m_maxStackSize = 10;
				break;
			case Kind.Charm:
				val3.m_equipStatusEffect = null;
				val3.m_movementModifier = 0f;
				val3.m_maxQuality = item.MaxQuality;
				break;
			case Kind.Book:
				GuideBook.SetupPrefab(val2, component, val3);
				break;
			case Kind.Focus:
				val3.m_maxAdrenaline = 0f;
				val3.m_fullAdrenalineSE = null;
				val3.m_blockAdrenaline = 0f;
				val3.m_perfectBlockAdrenaline = 0f;
				val3.m_equipStatusEffect = Foci.EquipEffect(item.Id, item.Name);
				val3.m_movementModifier = 0f;
				val3.m_maxQuality = 1;
				val3.m_maxStackSize = 1;
				break;
			case Kind.Trick:
			{
				GuideBook.SetupPrefab(val2, component, val3);
				val3.m_maxStackSize = 5;
				val3.m_weight = 0.3f;
				val3.m_icons = (Sprite[])(object)new Sprite[1] { GameArt.Recolor(BookArt.Icon(), item.Id, item.Tint, item.TintAmt, item.Glow, item.GlowStr) };
				Transform transform2 = val2.transform;
				transform2.localScale *= 0.6f;
				break;
			}
			case Kind.Notes:
			{
				GuideBook.SetupPrefab(val2, component, val3);
				val3.m_maxStackSize = 10;
				val3.m_weight = 0.5f;
				val3.m_icons = (Sprite[])(object)new Sprite[1] { GameArt.Recolor(BookArt.Icon(), item.Id, item.Tint, item.TintAmt, item.Glow, item.GlowStr) };
				Transform transform = val2.transform;
				transform.localScale *= 0.75f;
				break;
			}
			case Kind.Potion:
				val3.m_consumeStatusEffect = null;
				val3.m_food = 0f;
				val3.m_foodStamina = 0f;
				val3.m_foodEitr = 0f;
				val3.m_maxStackSize = 10;
				break;
			case Kind.Scroll:
				val3.m_consumeStatusEffect = null;
				val3.m_food = 0f;
				val3.m_foodStamina = 0f;
				val3.m_foodEitr = 0f;
				val3.m_maxStackSize = 20;
				break;
			}
			component.m_itemData = component.m_itemData.Clone();
			component.m_itemData.m_shared = val3;
			component.m_itemData.m_dropPrefab = val2;
			item.Prefab = val2;
			ByItemName[item.Name] = item;
		}
	}

	public static void RegisterObjectDB(ObjectDB db)
	{
		if ((Object)(object)db == (Object)null || db.m_items == null || db.m_items.Count == 0)
		{
			return;
		}
		try
		{
			FindTrinket(db.m_items);
			Build((string n) => db.GetItemPrefab(n));
			bool flag = false;
			foreach (Def d in All)
			{
				if ((Object)(object)d.Prefab == (Object)null)
				{
					continue;
				}
				if (!db.m_items.Contains(d.Prefab))
				{
					db.m_items.Add(d.Prefab);
					flag = true;
				}
				if (!db.m_recipes.Exists((Recipe r) => (Object)(object)r != (Object)null && ((Object)r).name == "Recipe_" + d.Id))
				{
					Recipe val = MakeRecipe(db, d);
					if ((Object)(object)val != (Object)null)
					{
						db.m_recipes.Add(val);
						flag = true;
					}
				}
			}
			if (GaldrConfig.RemoveVanillaMagic.Value > 0.5f)
			{
				Staves.RegisterObjectDB(db);
				DisableVanillaMagic(db);
			}
			Blessings.RegisterObjectDB(db);
			foreach (Def item in All)
			{
				StatusEffect val2 = (((Object)(object)item.Prefab != (Object)null) ? item.Prefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_equipStatusEffect : null);
				if (item.Kind == Kind.Focus && (Object)(object)val2 != (Object)null && !db.m_StatusEffects.Contains(val2))
				{
					db.m_StatusEffects.Add(val2);
				}
			}
			if ((Object)(object)ZNetScene.instance != (Object)null)
			{
				RegisterScene(ZNetScene.instance);
			}
			ArmorSets.Describe(db);
			if (flag)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null);
				if (methodInfo != null)
				{
					methodInfo.Invoke(db, null);
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Galdr items (ObjectDB): " + ex));
		}
	}

	private static void DisableVanillaMagic(ObjectDB db)
	{
		int num = 0;
		foreach (Recipe recipe in db.m_recipes)
		{
			if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && recipe.m_item.m_itemData != null && recipe.m_item.m_itemData.m_shared != null && Staves.IsVanillaMagic(recipe.m_item.m_itemData.m_shared) && recipe.m_enabled)
			{
				recipe.m_enabled = false;
				num++;
			}
		}
		if (num > 0)
		{
			Plugin.Log.LogInfo((object)("Game magic: recipes disabled: " + num));
		}
	}

	public static void RegisterScene(ZNetScene zs)
	{
		if ((Object)(object)zs == (Object)null)
		{
			return;
		}
		try
		{
			FindTrinket(zs.m_prefabs);
			Build((string n) => zs.GetPrefab(n));
			FieldInfo fieldInfo = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs");
			Dictionary<int, GameObject> dictionary = ((fieldInfo != null) ? (fieldInfo.GetValue(zs) as Dictionary<int, GameObject>) : null);
			foreach (Def item in All)
			{
				if (!((Object)(object)item.Prefab == (Object)null))
				{
					if (!zs.m_prefabs.Contains(item.Prefab))
					{
						zs.m_prefabs.Add(item.Prefab);
					}
					if (dictionary != null)
					{
						dictionary[StringExtensionMethods.GetStableHashCode(item.Id)] = item.Prefab;
					}
				}
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Galdr items (scene): " + ex));
		}
	}

	private static CraftingStation StationByName(ObjectDB db, string prefabName)
	{
		foreach (Recipe recipe in db.m_recipes)
		{
			if ((Object)(object)recipe != (Object)null && (Object)(object)recipe.m_craftingStation != (Object)null && ((Object)recipe.m_craftingStation).name == prefabName)
			{
				return recipe.m_craftingStation;
			}
		}
		GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(prefabName) : null);
		if (!((Object)(object)val != (Object)null))
		{
			return null;
		}
		return val.GetComponent<CraftingStation>();
	}

	private static Recipe MakeRecipe(ObjectDB db, Def d)
	{
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: 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_00fe: Expected O, but got Unknown
		if (d.Res == null || d.Res.Length == 0)
		{
			return null;
		}
		CraftingStation val = StationByName(db, d.Station);
		List<Requirement> list = new List<Requirement>();
		string[] res = d.Res;
		for (int i = 0; i < res.Length; i++)
		{
			string[] array = res[i].Split(new char[1] { ':' });
			GameObject itemPrefab = db.GetItemPrefab(array[0]);
			ItemDrop val2 = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null);
			if ((Object)(object)val2 == (Object)null)
			{
				Plugin.Log.LogWarning((object)("Galdr items: missing ingredient " + array[0] + " for «" + d.Name + "»"));
				return null;
			}
			list.Add(new Requirement
			{
				m_resItem = val2,
				m_amount = int.Parse(array[1]),
				m_amountPerLevel = ((array.Length > 2) ? int.Parse(array[2]) : 0),
				m_recover = (d.Kind == Kind.Charm)
			});
		}
		Recipe obj = ScriptableObject.CreateInstance<Recipe>();
		((Object)obj).name = "Recipe_" + d.Id;
		obj.m_item = d.Prefab.GetComponent<ItemDrop>();
		obj.m_amount = 1;
		obj.m_enabled = true;
		obj.m_craftingStation = val;
		obj.m_repairStation = val;
		obj.m_minStationLevel = d.StationLevel;
		obj.m_resources = list.ToArray();
		return obj;
	}

	public static Def Of(ItemData item)
	{
		if (item == null || item.m_shared == null)
		{
			return null;
		}
		if ((Object)(object)item.m_dropPrefab != (Object)null)
		{
			string name = ((Object)item.m_dropPrefab).name;
			foreach (Def item2 in All)
			{
				if ((Object)(object)item2.Prefab != (Object)null && item2.Id == name)
				{
					return item2;
				}
			}
		}
		if (!ByItemName.TryGetValue(item.m_shared.m_name, out var value))
		{
			return null;
		}
		return value;
	}

	public static void Sum(Player p, out float mana, out float regen)
	{
		mana = 0f;
		regen = 0f;
		if ((Object)(object)p == (Object)null)
		{
			return;
		}
		List<Food> foods = p.GetFoods();
		if (foods == null)
		{
			return;
		}
		foreach (Food item in foods)
		{
			if (item != null)
			{
				Def def = Of(item.m_item);
				if (def != null && def.Kind == Kind.Food)
				{
					mana += def.Mana;
					regen += def.Regen;
				}
			}
		}
	}

	public static bool TryDrink(Player p, Inventory inv, ItemData item, out bool drunk)
	{
		drunk = false;
		Def def = Of(item);
		if (def == null || def.Kind != Kind.Potion)
		{
			return false;
		}
		MagicController instance = MagicController.Instance;
		if ((Object)(object)p != (Object)(object)Player.m_localPlayer || (Object)(object)instance == (Object)null || instance.Data == null)
		{
			return true;
		}
		if (Time.time < _potionReadyAt)
		{
			((Character)p).Message((MessageType)2, Lang.F("manafoods.too_soon_n", Mathf.CeilToInt(_potionReadyAt - Time.time)), 0, (Sprite)null, false);
			return true;
		}
		_potionReadyAt = Time.time + 60f;
		instance.Mana = Mathf.Min(instance.MaxMana(), instance.Mana + def.Mana);
		if (inv != null)
		{
			inv.RemoveOneItem(item);
		}
		drunk = true;
		return true;
	}

	private static void OnChestFilled(Container __instance)
	{
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e2: 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_00ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f1: Invalid comparison between Unknown and I4
		//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Invalid comparison between Unknown and I4
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Invalid comparison between Unknown and I4
		//IL_0102: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Invalid comparison between Unknown and I4
		//IL_010b: Unknown result type (might be due to invalid IL or missing references)
		//IL_010f: Invalid comparison between Unknown and I4
		//IL_0111: Unknown result type (might be due to invalid IL or missing references)
		//IL_0115: Invalid comparison between Unknown and I4
		try
		{
			if ((Object)(object)__instance == (Object)null || (Object)(object)((Component)__instance).GetComponent<TombStone>() != (Object)null)
			{
				return;
			}
			Piece component = ((Component)__instance).GetComponent<Piece>();
			if ((Object)(object)component != (Object)null && component.GetCreator() != 0L)
			{
				return;
			}
			if (((Component)__instance).transform.position.y > 3000f && Random.value < GaldrConfig.TrickChance.Value)
			{
				List<Def> list = All.FindAll((Def x) => x.Kind == Kind.Trick && (Object)(object)x.Prefab != (Object)null);
				if (list.Count > 0)
				{
					__instance.GetInventory().AddItem(list[Random.Range(0, list.Count)].Prefab, 1);
				}
			}
			if (Random.value > GaldrConfig.NotesChance.Value)
			{
				return;
			}
			int num = 1;
			if (WorldGenerator.instance != null)
			{
				Biome biome = WorldGenerator.instance.GetBiome(((Component)__instance).transform.position);
				if ((int)biome == 2 || (int)biome == 4)
				{
					num = 2;
				}
				else if ((int)biome == 16 || (int)biome == 512 || (int)biome == 32 || (int)biome == 64)
				{
					num = 3;
				}
			}
			Def def = null;
			foreach (Def item in All)
			{
				if (item.Kind == Kind.Notes && item.Id == "GaldrNotes" + num)
				{
					def = item;
				}
			}
			if (def != null && !((Object)(object)def.Prefab == (Object)null))
			{
				__instance.GetInventory().AddItem(def.Prefab, 1);
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogWarning((object)("Notes in chest: " + ex.Message));
		}
	}

	public static int CharmLevel(Player p)
	{
		if ((Object)(object)p == (Object)null)
		{
			return 0;
		}
		Inventory inventory = ((Humanoid)p).GetInventory();
		if (inventory == null)
		{
			return 0;
		}
		foreach (ItemData equippedItem in inventory.GetEquippedItems())
		{
			Def def = Of(equippedItem);
			if (def != null && def.Kind == Kind.Charm)
			{
				return Mathf.Max(1, equippedItem.m_quality);
			}
		}
		return 0;
	}

	public static bool TryRead(Player p, Inventory inv, ItemData item)
	{
		Def def = Of(item);
		if (def == null || (def.Kind != Kind.Scroll && def.Kind != Kind.Notes && def.Kind != Kind.Trick))
		{
			return false;
		}
		if ((Object)(object)p != (Object)(object)Player.m_localPlayer || (Object)(object)MagicController.Instance == (Object)null || MagicController.Instance.Data == null)
		{
			return true;
		}
		if (def.Kind == Kind.Trick)
		{
			Tricks.Def def2 = Tricks.ById(def.Trick);
			if (def2 == null)
			{
				return true;
			}
			if (!Tricks.Learn(def2.Id))
			{
				((Character)p).Message((MessageType)2, Lang.T("manafoods.you_already_know_this_technique"), 0, (Sprite)null, false);
				return true;
			}
			((Character)p).Message((MessageType)2, Lang.F("manafoods.technique_learned_n", def2.Name), 0, (Sprite)null, false);
			if (inv != null)
			{
				inv.RemoveOneItem(item);
			}
			return true;
		}
		Rewards.Add(def.Knowledge, def.Name.ToLowerInvariant());
		if (inv != null)
		{
			inv.RemoveOneItem(item);
		}
		return true;
	}
}
[Serializable]
public class SpellState
{
	public string id;

	public bool unlocked;

	public int[] ranks = new int[3];

	public string name = "";

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

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

	public Dictionary<string, List<string>> formsBy = new Dictionary<string, List<string>>();

	public Dictionary<string, string> nameBy = new Dictionary<string, string>();

	private static readonly List<string> Empty = new List<string>();

	public int TotalRanks => ranks[0] + ranks[1] + ranks[2];

	public string Word
	{
		get
		{
			if (nameBy != null && VoiceLang.Current != null && nameBy.TryGetValue(VoiceLang.Current, out var value) && !string.IsNullOrEmpty(value))
			{
				return value;
			}
			List<string> list = Forms;
			if (list != null && list.Count > 0)
			{
				return list[0];
			}
			return name ?? "";
		}
	}

	public bool AnyWord
	{
		get
		{
			if (!HasWord)
			{
				return HasOtherWord;
			}
			return true;
		}
	}

	public List<string> Forms => FormsFor(VoiceLang.Current);

	public bool HasWord
	{
		get
		{
			List<string> list = Forms;
			if (list != null)
			{
				return list.Count > 0;
			}
			return false;
		}
	}

	public bool HasOtherWord
	{
		get
		{
			string current = VoiceLang.Current;
			if (current != "ru" && forms.Count > 0)
			{
				return true;
			}
			if (current != "en" && formsEn.Count > 0)
			{
				return true;
			}
			foreach (KeyValuePair<string, List<string>> item in formsBy)
			{
				if (item.Key != current && item.Value != null && item.Value.Count > 0)
				{
					return true;
				}
			}
			return false;
		}
	}

	public List<string> FormsFor(string code)
	{
		switch (code)
		{
		case "ru":
			return forms;
		case "en":
			return formsEn;
		default:
		{
			if (formsBy.TryGetValue(code, out var value))
			{
				return value;
			}
			break;
		}
		case null:
			break;
		}
		return Empty;
	}

	public void SetFormsFor(string code, List<string> f)
	{
		switch (code)
		{
		case "ru":
			forms = f;
			break;
		case "en":
			formsEn = f;
			break;
		default:
			formsBy[code] = f;
			break;
		case null:
			break;
		}
	}

	public void SetWord(string code, string word)
	{
		if (nameBy == null)
		{
			nameBy = new Dictionary<string, string>();
		}
		if (code != null)
		{
			nameBy[code] = word ?? "";
		}
		name = word ?? "";
	}

	public void SetForms(List<string> f)
	{
		SetFormsFor(VoiceLang.Current, f);
	}
}
[Serializable]
public class PlayerMagicData
{
	public int version = 1;

	public long savedAt;

	public int native = -1;

	public float knowledge;

	public float healProgress;

	public List<SpellState> spells = new List<SpellState>();

	public int[] core = new int[3];

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

	public List<int> bosses = new List<int>();

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

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

	public int bookPages;

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

	public bool loadoutSet;

	public string passive = "";

	public int preset;

	public List<string>[] presets = new List<string>[3]
	{
		new List<string>(),
		new List<string>(),
		new List<string>()
	};

	public bool[] presetSet = new bool[3];

	public Dictionary<string, int> casts = new Dictionary<string, int>();

	public int kills;

	public int shouts;

	public int whispers;

	public int misfires;

	public int quick;

	public float manaSpent;

	public float bestHit;

	public string bestHitSpell = "";

	public long firstCast;

	public SpellState Get(string id, bool create)
	{
		for (int i = 0; i < spells.Count; i++)
		{
			if (spells[i].id == id)
			{
				return spells[i];
			}
		}
		if (!create)
		{
			return null;
		}
		SpellState spellState = new SpellState
		{
			id = id
		};
		spells.Add(spellState);
		return spellState;
	}

	public void Sanitize()
	{
		if (spells == null)
		{
			spells = new List<SpellState>();
		}
		if (core == null || core.Length != 3)
		{
			core = new int[3];
		}
		if (stones == null)
		{
			stones = new List<string>();
		}
		if (bosses == null)
		{
			bosses = new List<int>();
		}
		if (trophies == null)
		{
			trophies = new List<string>();
		}
		if (tricks == null)
		{
			tricks = new List<string>();
		}
		if (loadout == null)
		{
			loadout = new List<string>();
		}
		loadout.RemoveAll((string id) => SpellDefs.Get(id) == null || SpellDefs.Get(id).Tier == 0);
		if (presets == null || presets.Length != 3)
		{
			presets = new List<string>[3]
			{
				new List<string>(),
				new List<string>(),
				new List<string>()
			};
		}
		for (int num = 0; num < 3; num++)
		{
			if (presets[num] == null)
			{
				presets[num] = new List<string>();
			}
			presets[num].RemoveAll((string id) => SpellDefs.Get(id) == null || SpellDefs.Get(id).Tier == 0);
		}
		if (presetSet == null || presetSet.Length != 3)
		{
			presetSet = new bool[3];
		}
		preset = Mathf.Clamp(preset, 0, 2);
		if (casts == null)
		{
			casts = new Dictionary<string, int>();
		}
		if (bestHitSpell == null)
		{
			bestHitSpell = "";
		}
		if (passive == null)
		{
			passive = "";
		}
		foreach (SpellState spell in spells)
		{
			if (spell.ranks == null || spell.ranks.Length != 3)
			{
				spell.ranks = new int[3];
			}
			if (spell.forms == null)
			{
				spell.forms = new List<string>();
			}
			if (spell.formsEn == null)
			{
				spell.formsEn = new List<string>();
			}
			if (spell.formsBy == null)
			{
				spell.formsBy = new Dictionary<string, List<string>>();
			}
			if (spell.name == null)
			{
				spell.name = "";
			}
			if (spell.nameBy == null)
			{
				spell.nameBy = new Dictionary<string, string>();
			}
		}
	}

	public string Summary()
	{
		int num = 0;
		foreach (SpellState spell in spells)
		{
			if (spell.unlocked)
			{
				num++;
			}
		}
		string text = ((native >= 0) ? SpellDefs.ElementName((Element)native) : "not chosen");
		int num2 = 0;
		foreach (SpellState spell2 in spells)
		{
			if (spell2.HasWord)
			{
				num2++;
			}
		}
		return "element " + text + ", Knowledge " + Mathf.FloorToInt(knowledge) + ", spells unlocked " + num + ", words " + num2;
	}
}
public static class Persistence
{
	private const string Key = "Galdr_Data";

	private static readonly FieldInfo CustomDataField = AccessTools.Field(typeof(Player), "m_customData");

	private static string _currentPath;

	private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;

	private static bool _loggedSave;

	private static Dictionary<string, string> Dict(Player p)
	{
		if (CustomDataField == null || (Object)(object)p == (Object)null)
		{
			return null;
		}
		return CustomDataField.GetValue(p) as Dictionary<string, string>;
	}

	private static string FilePath(string playerName)
	{
		string text = Path.Combine(Paths.ConfigPath, "Galdr");
		Directory.CreateDirectory(text);
		string text2 = (string.IsNullOrEmpty(playerName) ? "unnamed" : playerName);
		char[] invalidFileNameChars = Path.GetInvalidFileNameChars();
		foreach (char oldChar in invalidFileNameChars)
		{
			text2 = text2.Replace(oldChar, '_');
		}
		return Path.Combine(text, text2 + ".txt");
	}

	private static PlayerMagicData Parse(string text, string source)
	{
		if (string.IsNullOrEmpty(text))
		{
			return null;
		}
		try
		{
			PlayerMagicData playerMagicData = (text.TrimStart(Array.Empty<char>()).StartsWith("{") ? JsonUtility.FromJson<PlayerMagicData>(text) : Deserialize(text));
			playerMagicData?.Sanitize();
			return playerMagicData;
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Corrupted magic data (" + source + "): " + ex.Message));
			return null;
		}
	}

	private static string B64(string s)
	{
		return Convert.ToBase64String(Encoding.UTF8.GetBytes(s ?? ""));
	}

	private static string UnB64(string s)
	{
		if (string.IsNullOrEmpty(s))
		{
			return "";
		}
		try
		{
			return Encoding.UTF8.GetString(Convert.FromBase64String(s));
		}
		catch
		{
			return "";
		}
	}

	private static string Join(IEnumerable<int> xs)
	{
		StringBuilder stringBuilder = new StringBuilder();
		foreach (int x in xs)
		{
			if (stringBuilder.Length > 0)
			{
				stringBuilder.Append(',');
			}
			stringBuilder.Append(x.ToString(Inv));
		}
		return stringBuilder.ToString();
	}

	private static List<int> Ints(string s)
	{
		List<int> list = new List<int>();
		if (string.IsNullOrEmpty(s))
		{
			return list;
		}
		string[] array = s.Split(new char[1] { ',' });
		for (int i = 0; i < array.Length; i++)
		{
			if (int.TryParse(array[i], NumberStyles.Integer, Inv, out var result))
			{
				list.Add(result);
			}
		}
		return list;
	}

	public static string Serialize(PlayerMagicData d)
	{
		StringBuilder stringBuilder = new StringBuilder();
		stringBuilder.Append("galdr=2\n");
		stringBuilder.Append("savedAt=").Append(d.savedAt.ToString(Inv)).Append('\n');
		stringBuilder.Append("native=").Append(d.native.ToString(Inv)).Append('\n');
		stringBuilder.Append("knowledge=").Append(d.knowledge.ToString("R", Inv)).Append('\n');
		stringBuilder.Append("heal=").Append(d.healProgress.ToString("R", Inv)).Append('\n');
		stringBuilder.Append("core=").Append(Join(d.core)).Append('\n');
		stringBuilder.Append("bosses=").Append(Join(d.bosses)).Append('\n');
		List<string> list = new List<string>();
		foreach (string stone in d.stones)
		{
			list.Add(B64(stone));
		}
		stringBuilder.Append("trophies=").Append(string.Join(",", d.trophies.ToArray())).Append('\n');
		stringBuilder.Append("pages=").Append(d.bookPages.ToString(Inv)).Append('\n');
		stringBuilder.Append("tricks=").Append(string.Join(",", d.tricks.ToArray())).Append('\n');
		stringBuilder.Append("stones=").Append(string.Join(",", list.ToArray())).Append('\n');
		if (d.loadoutSet)
		{
			stringBuilder.Append("loadout=").Append(string.Join(",", d.loadout.ToArray())).Append('\n');
		}
		stringBuilder.Append("preset=").Append(d.preset.ToString(Inv)).Append('\n');
		if (!string.IsNullOrEmpty(d.passive))
		{
			stringBuilder.Append("passive=").Append(d.passive).Append('\n');
		}
		for (int i = 0; i < 3; i++)
		{
			if (d.presetSet[i] && i != d.preset)
			{
				stringBuilder.Append("preset").Append(i.ToString(Inv)).Append('=')
					.Append(string.Join(",", d.presets[i].ToArray()))
					.Append('\n');
			}
		}
		if (d.casts.Count > 0)
		{
			List<string> list2 = new List<string>();
			foreach (KeyValuePair<string, int> cast in d.casts)
			{
				list2.Add(cast.Key + ":" + cast.Value.ToString(Inv));
			}
			stringBuilder.Append("casts=").Append(string.Join(",", list2.ToArray())).Append('\n');
		}
		stringBuilder.Append("chron=").Append(Join(new int[5] { d.kills, d.shouts, d.whispers, d.misfires, d.quick })).Append('|')
			.Append(d.manaSpent.ToString("R", Inv))
			.Append('|')
			.Append(d.firstCast.ToString(Inv))
			.Append('|')
			.Append(d.bestHit.ToString("R", Inv))
			.Append('|')
			.Append(d.bestHitSpell ?? "")
			.Append('\n');
		foreach (SpellState spell in d.spells)
		{
			List<string> list3 = new List<string>();
			foreach (string form in spell.forms)
			{
				list3.Add(B64(form));
			}
			List<string> list4 = new List<string>();
			if (spell.formsEn != null)
			{
				foreach (string item in spell.formsEn)
				{
					list4.Add(B64(item));
				}
			}
			stringBuilder.Append("spell=").Append(spell.id).Append('|')
				.Append(spell.unlocked ? '1' : '0')
				.Append('|')
				.Append(Join(spell.ranks))
				.Append('|')
				.Append(B64(spell.name))
				.Append('|')
				.Append(string.Join(",", list3.ToArray()))
				.Append('|')
				.Append(string.Join(",", list4.ToArray()))
				.Append('\n');
			if (spell.formsBy != null)
			{
				foreach (KeyValuePair<string, List<string>> item2 in spell.formsBy)
				{
					if (item2.Value == null || item2.Value.Count == 0)
					{
						continue;
					}
					List<string> list5 = new List<string>();
					foreach (string item3 in item2.Value)
					{
						list5.Add(B64(item3));
					}
					stringBuilder.Append("words=").Append(spell.id).Append('|')
						.Append(item2.Key)
						.Append('|')
						.Append(string.Join(",", list5.ToArray()))
						.Append('\n');
				}
			}
			if (spell.nameBy == null)
			{
				continue;
			}
			foreach (KeyValuePair<string, string> item4 in spell.nameBy)
			{
				if (!string.IsNullOrEmpty(item4.Value))
				{
					stringBuilder.Append("word=").Append(spell.id).Append('|')
						.Append(item4.Key)
						.Append('|')
						.Append(B64(item4.Value))
						.Append('\n');
				}
			}
		}
		return stringBuilder.ToString();
	}

	public static PlayerMagicData Deserialize(string text)
	{
		PlayerMagicData playerMagicData = new PlayerMagicData();
		string[] array = text.Split(new char[1] { '\n' });
		for (int i = 0; i < array.Length; i++)
		{
			string text2 = array[i].Trim('\r', ' ');
			int num = text2.IndexOf('=');
			if (num <= 0)
			{
				continue;
			}
			string text3 = text2.Substring(0, num);
			string text4 = text2.Substring(num + 1);
			switch (text3)
			{
			case "savedAt":
				long.TryParse(text4, NumberStyles.Integer, Inv, out playerMagicData.savedAt);
				break;
			case "native":
				int.TryParse(text4, NumberStyles.Integer, Inv, out playerMagicData.native);
				break;
			case "knowledge":
				float.TryParse(text4, NumberStyles.Float, Inv, out playerMagicData.knowledge);
				break;
			case "heal":
				float.TryParse(text4, NumberStyles.Float, Inv, out playerMagicData.healProgress);
				break;
			case "core":
			{
				List<int> list2 = Ints(text4);
				for (int l = 0; l < 3 && l < list2.Count; l++)
				{
					playerMagicData.core[l] = list2[l];
				}
				break;
			}
			case "bosses":
				playerMagicData.bosses = Ints(text4);
				break;
			case "trophies":
				playerMagicData.trophies = new List<string>(text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries));
				break;
			case "pages":
				int.TryParse(text4, NumberStyles.Integer, Inv, out playerMagicData.bookPages);
				break;
			case "tricks":
				playerMagicData.tricks = new List<string>(text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries));
				break;
			case "stones":
				playerMagicData.stones = new List<string>();
				if (text4.Length > 0)
				{
					string[] array3 = text4.Split(new char[1] { ',' });
					foreach (string s in array3)
					{
						playerMagicData.stones.Add(UnB64(s));
					}
				}
				break;
			case "loadout":
				playerMagicData.loadout = new List<string>(text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries));
				playerMagicData.loadoutSet = true;
				break;
			case "passive":
				playerMagicData.passive = text4.Trim();
				break;
			case "preset":
				int.TryParse(text4, NumberStyles.Integer, Inv, out playerMagicData.preset);
				playerMagicData.preset = Mathf.Clamp(playerMagicData.preset, 0, 2);
				break;
			case "preset0":
			case "preset1":
			case "preset2":
			{
				int num3 = text3[6] - 48;
				playerMagicData.presets[num3] = new List<string>(text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries));
				playerMagicData.presetSet[num3] = true;
				break;
			}
			case "casts":
			{
				string[] array3 = text4.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
				foreach (string text7 in array3)
				{
					int num2 = text7.LastIndexOf(':');
					if (num2 > 0 && int.TryParse(text7.Substring(num2 + 1), NumberStyles.Integer, Inv, out var result))
					{
						playerMagicData.casts[text7.Substring(0, num2)] = result;
					}
				}
				break;
			}
			case "chron":
			{
				string[] array4 = text4.Split(new char[1] { '|' });
				List<int> list3 = Ints(array4[0]);
				if (list3.Count > 0)
				{
					playerMagicData.kills = list3[0];
				}
				if (list3.Count > 1)
				{
					playerMagicData.shouts = list3[1];
				}
				if (list3.Count > 2)
				{
					playerMagicData.whispers = list3[2];
				}
				if (list3.Count > 3)
				{
					playerMagicData.misfires = list3[3];
				}
				if (list3.Count > 4)
				{
					playerMagicData.quick = list3[4];
				}
				if (array4.Length > 1)
				{
					float.TryParse(array4[1], NumberStyles.Float, Inv, out playerMagicData.manaSpent);
				}
				if (array4.Length > 2)
				{
					long.TryParse(array4[2], NumberStyles.Integer, Inv, out playerMagicData.firstCast);
				}
				if (array4.Length > 3)
				{
					float.TryParse(array4[3], NumberStyles.Float, Inv, out playerMagicData.bestHit);
				}
				if (array4.Length > 4)
				{
					playerMagicData.bestHitSpell = array4[4];
				}
				break;
			}
			case "word":
			{
				string[] array6 = text4.Split(new char[1] { '|' });
				if (array6.Length >= 3 && SpellDefs.Get(array6[0]) != null)
				{
					SpellState spellState3 = playerMagicData.Get(array6[0], create: true);
					if (spellState3.nameBy == null)
					{
						spellState3.nameBy = new Dictionary<string, string>();
					}
					spellState3.nameBy[array6[1]] = UnB64(array6[2]);
				}
				break;
			}
			case "words":
			{
				string[] array5 = text4.Split(new char[1] { '|' });
				if (array5.Length < 3 || SpellDefs.Get(array5[0]) == null)
				{
					break;
				}
				SpellState spellState2 = playerMagicData.Get(array5[0], create: true);
				List<string> list4 = new List<string>();
				if (array5[2].Length > 0)
				{
					string[] array3 = array5[2].Split(new char[1] { ',' });
					for (int k = 0; k < array3.Length; k++)
					{
						string text8 = UnB64(array3[k]);
						if (text8.Length > 0)
						{
							list4.Add(text8);
						}
					}
				}
				spellState2.SetFormsFor(array5[1], list4);
				break;
			}
			case "spell":
			{
				string[] array2 = text4.Split(new char[1] { '|' });
				if (array2.Length < 5 || SpellDefs.Get(array2[0]) == null)
				{
					break;
				}
				SpellState spellState = playerMagicData.Get(array2[0], create: true);
				spellState.unlocked = array2[1] == "1";
				List<int> list = Ints(array2[2]);
				for (int j = 0; j < 3 && j < list.Count; j++)
				{
					spellState.ranks[j] = list[j];
				}
				spellState.name = UnB64(array2[3]);
				spellState.forms = new List<string>();
				string[] array3;
				if (array2[4].Length > 0)
				{
					array3 = array2[4].Split(new char[1] { ',' });
					for (int k = 0; k < array3.Length; k++)
					{
						string text5 = UnB64(array3[k]);
						if (text5.Length > 0)
						{
							spellState.forms.Add(text5);
						}
					}
				}
				spellState.formsEn = new List<string>();
				if (array2.Length <= 5 || array2[5].Length <= 0)
				{
					break;
				}
				array3 = array2[5].Split(new char[1] { ',' });
				for (int k = 0; k < array3.Length; k++)
				{
					string text6 = UnB64(array3[k]);
					if (text6.Length > 0)
					{
						spellState.formsEn.Add(text6);
					}
				}
				break;
			}
			}
		}
		return playerMagicData;
	}

	public static PlayerMagicData Load(Player p, string playerName)
	{
		PlayerMagicData playerMagicData = null;
		PlayerMagicData playerMagicData2 = null;
		string text = null;
		try
		{
			text = (_currentPath = FilePath(playerName));
			if (File.Exists(text))
			{
				playerMagicData = Parse(File.ReadAllText(text), "file");
			}
		}
		catch (Exception ex)
		{
			Plugin.Log.LogError((object)("Couldn't read the progress file: " + ex.Message));
		}
		try
		{
			Dictionary<string, string> dictionary = Dict(p);
			if (dictionary != null && dictionary.TryGetValue("Galdr_Data", out var value))
			{
				playerMagicData2 = Parse(value, "character");
			}
		}
		catch (Exception ex2)
		{
			Plugin.Log.LogError((object)("Couldn't read progress from the character: " + ex2.Message));
		}
		PlayerMagicData playerMagicData3 = playerMagicData;
		string text2 = "file";
		if (playerMagicData2 != null && (playerMagicData3 == null || playerMagicData2.s