Decompiled source of Malmr v1.0.0

plugins/Malmr/Malmr.dll

Decompiled 15 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Ezomic.Core;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using Vandi;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("Thijssen Software")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright (c) 2026 Robbin Thijssen")]
[assembly: AssemblyDescription("Tap Alt with a pickaxe and your blows fill a bar until the whole ore deposit breaks at once. Opens metal by metal, on your Pickaxes skill and the biome's boss beaten.")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+5045b41e8e419bcf5d2ca071dbdac61076a63977")]
[assembly: AssemblyProduct("Malmr")]
[assembly: AssemblyTitle("Malmr")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace Malmr
{
	internal static class Bosses
	{
		internal const int Unreadable = -1;

		private static bool _vandiWarned;

		private static bool _gameWarned;

		private static ZNetScene _scene;

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

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

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

		internal static bool ThroughVandi => MalmrPlugin.VandiPresent;

		internal static int KillsNeeded
		{
			get
			{
				if (!ThroughVandi)
				{
					return 1;
				}
				return MalmrConfig.BossKills.Value;
			}
		}

		internal static string CountName
		{
			get
			{
				if (!ThroughVandi)
				{
					return "game";
				}
				return "vandi";
			}
		}

		internal static int LocalKills(string bossKey)
		{
			if (string.IsNullOrEmpty(bossKey))
			{
				return 0;
			}
			if (!ThroughVandi)
			{
				return GameKills(bossKey);
			}
			return VandiKills(bossKey);
		}

		internal static bool? Counted(string bossKey)
		{
			if (string.IsNullOrEmpty(bossKey))
			{
				return false;
			}
			if (!ThroughVandi)
			{
				Scan();
				return Tokens.ContainsKey(bossKey);
			}
			try
			{
				return VandiBridge.CountsKillsOf(bossKey);
			}
			catch (Exception error)
			{
				WarnVandi(error);
				return null;
			}
		}

		private static int VandiKills(string bossKey)
		{
			try
			{
				return VandiBridge.LocalBossKills(bossKey);
			}
			catch (Exception error)
			{
				WarnVandi(error);
				return -1;
			}
		}

		private static int GameKills(string bossKey)
		{
			try
			{
				Scan();
				if (!Tokens.TryGetValue(bossKey, out var value))
				{
					return 0;
				}
				Game instance = Game.instance;
				if ((Object)(object)instance == (Object)null)
				{
					return 0;
				}
				PlayerProfile playerProfile = instance.GetPlayerProfile();
				if (playerProfile == null || playerProfile.m_playerStats == null || playerProfile.m_playerStats.Length == 0)
				{
					return 0;
				}
				PlayerStats val = playerProfile.m_playerStats[0];
				if (val == null || val.m_enemyStats == null || val.m_enemyStats.Length == 0 || val.m_enemyStats[0] == null)
				{
					return 0;
				}
				float num = 0f;
				foreach (string item in value)
				{
					if (val.m_enemyStats[0].TryGetValue(item, out var value2))
					{
						num += value2;
					}
				}
				return Mathf.Max(0, Mathf.FloorToInt(num + 0.001f));
			}
			catch (Exception ex)
			{
				if (!_gameWarned)
				{
					_gameWarned = true;
					MalmrPlugin.Log.LogWarning((object)("Could not read this character's boss kills from the game, so every metal that waits for a boss stays shut. Probably a game update moved the kill tally. Said once per session: " + ex.Message));
				}
				return -1;
			}
		}

		private static void WarnVandi(Exception error)
		{
			if (!_vandiWarned)
			{
				_vandiWarned = true;
				MalmrPlugin.Log.LogWarning((object)("Could not ask Vandi for a boss count, so every metal that waits for a boss stays shut. Malmr and Vandi are probably from different builds. Said once per session: " + error.Message));
			}
		}

		internal static string Phrase(string bossKey)
		{
			string text = DisplayName(bossKey);
			if (!ThroughVandi)
			{
				return text + " beaten by you";
			}
			int num = KillsNeeded - 1;
			if (num <= 0)
			{
				return text + " beaten through Vandi";
			}
			return text + " beaten at " + ((num == 1) ? "one star" : (num + " stars")) + " through Vandi";
		}

		internal static string DisplayName(string bossKey)
		{
			if (string.IsNullOrEmpty(bossKey))
			{
				return "";
			}
			Scan();
			if (!Names.TryGetValue(bossKey, out var value))
			{
				return bossKey;
			}
			return value;
		}

		private static string TokenText(string bossKey)
		{
			if (!Tokens.TryGetValue(bossKey, out var value))
			{
				return "";
			}
			return string.Join("+", value.ToArray());
		}

		private static void Scan()
		{
			if ((Object)(object)_scene == (Object)(object)ZNetScene.instance)
			{
				return;
			}
			_scene = ZNetScene.instance;
			Names.Clear();
			Tokens.Clear();
			Marked.Clear();
			if ((Object)(object)_scene == (Object)null)
			{
				return;
			}
			Dictionary<string, List<Character>> dictionary = new Dictionary<string, List<Character>>(StringComparer.OrdinalIgnoreCase);
			Dictionary<string, List<Character>> dictionary2 = new Dictionary<string, List<Character>>(StringComparer.OrdinalIgnoreCase);
			Dictionary<Character, string> dictionary3 = new Dictionary<Character, string>();
			Character val = default(Character);
			foreach (GameObject prefab in _scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null || !prefab.TryGetComponent<Character>(ref val))
				{
					continue;
				}
				string text = (string.IsNullOrEmpty(val.m_defeatSetGlobalKey) ? null : val.m_defeatSetGlobalKey.ToLowerInvariant());
				if (val.m_boss)
				{
					Marked.Add(((Object)prefab).name + "=" + (text ?? "(no key)"));
				}
				if (text != null)
				{
					Dictionary<string, List<Character>> dictionary4 = (val.m_boss ? dictionary : dictionary2);
					if (!dictionary4.TryGetValue(text, out var value))
					{
						value = (dictionary4[text] = new List<Character>());
					}
					value.Add(val);
					dictionary3[val] = ((Object)prefab).name;
				}
			}
			HashSet<string> hashSet = new HashSet<string>(dictionary.Keys, StringComparer.OrdinalIgnoreCase);
			hashSet.UnionWith(dictionary2.Keys);
			foreach (string item in hashSet)
			{
				if (!dictionary.TryGetValue(item, out var value2))
				{
					value2 = dictionary2[item];
				}
				Names[item] = Localised(value2[0].m_name, dictionary3[value2[0]]);
				List<string> list2 = new List<string>();
				foreach (Character item2 in value2)
				{
					if (!string.IsNullOrEmpty(item2.m_name) && !list2.Contains(item2.m_name))
					{
						list2.Add(item2.m_name);
					}
				}
				if (list2.Count > 0)
				{
					Tokens[item] = list2;
				}
			}
			Marked.Sort(StringComparer.OrdinalIgnoreCase);
		}

		private static string Localised(string token, string fallback)
		{
			if (string.IsNullOrEmpty(token) || Localization.instance == null)
			{
				return fallback;
			}
			string text = Localization.instance.Localize(token);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return fallback;
		}

		internal static List<string> Holes()
		{
			List<string> list = new List<string>();
			if (MalmrConfig.BossKills.Value <= 0)
			{
				return list;
			}
			Scan();
			foreach (KeyValuePair<string, List<string>> item in MetalsByBoss())
			{
				string text = string.Join(", ", item.Value.ToArray());
				if (ThroughVandi && Counted(item.Key) == false)
				{
					list.Add(item.Key + ": Vandi does not count kills of it, because it is not in Vandi's BossBiomes. " + text + " can never open until it is, or until Bosses gives " + ((item.Value.Count == 1) ? "it" : "them") + " a boss Vandi counts.");
				}
				if ((Object)(object)_scene != (Object)null && !Names.ContainsKey(item.Key))
				{
					list.Add(item.Key + ": no creature in this world sets it when it dies, so " + text + " can never open. Check the spelling against the list of bosses on world load.");
				}
			}
			return list;
		}

		internal static void Describe(StringBuilder text)
		{
			int value = MalmrConfig.BossKills.Value;
			if (value <= 0)
			{
				text.Append("\n   Bosses: BossKills is ").Append(value).Append(", so every metal opens on the Pickaxes level alone.");
				return;
			}
			Dictionary<string, List<string>> dictionary = MetalsByBoss();
			if (dictionary.Count == 0)
			{
				text.Append("\n   Bosses: no metal in Unlocks waits for one.");
			}
			else
			{
				text.Append(ThroughVandi ? ("\n   Bosses, each killed " + KillsNeeded + " time(s) through Vandi: ") : "\n   Bosses, each killed once by the character, as the game's own kill tally counts it (Vandi is not installed): ");
				bool flag = true;
				foreach (KeyValuePair<string, List<string>> item in dictionary)
				{
					if (!flag)
					{
						text.Append("; ");
					}
					flag = false;
					text.Append(string.Join(", ", item.Value.ToArray())).Append(" on ").Append(item.Key)
						.Append(" (")
						.Append(DisplayName(item.Key));
					if (!ThroughVandi)
					{
						string text2 = TokenText(item.Key);
						text.Append(", counted as ").Append((text2.Length > 0) ? text2 : "nothing");
					}
					text.Append(')');
				}
			}
			foreach (string item2 in Holes())
			{
				text.Append("\n   ").Append(item2);
			}
			text.Append("\n   Bosses this world has, with the key each sets when it dies: ").Append((Marked.Count == 0) ? "none found" : string.Join(", ", Marked.ToArray()));
		}

		private static Dictionary<string, List<string>> MetalsByBoss()
		{
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
			foreach (string key in MalmrConfig.UnlockTable().Keys)
			{
				string text = MalmrConfig.BossFor(key);
				if (text != null)
				{
					if (!dictionary.TryGetValue(text, out var value))
					{
						value = (dictionary[text] = new List<string>());
					}
					value.Add(key);
				}
			}
			return dictionary;
		}
	}
	internal static class Collapse
	{
		private sealed class Job
		{
			public Component Rock;

			public ZNetView View;

			public HitData Template;

			public string Prefab;

			public readonly List<int> Order = new List<int>();

			public int Next;

			public int Broken;

			public int Refused;

			public int Frames;
		}

		private const int PerFrame = 6;

		private const double BudgetMs = 4.0;

		private const float Margin = 0.01f;

		private static readonly List<Job> Jobs = new List<Job>();

		private static ZNetScene _scene;

		private static bool _warned;

		internal static bool Busy(ZNetView nview)
		{
			foreach (Job job in Jobs)
			{
				if ((Object)(object)job.View == (Object)(object)nview)
				{
					return true;
				}
			}
			return false;
		}

		internal static void Start(Component rock, ZNetView nview, HitData template)
		{
			if ((Object)(object)rock == (Object)null || (Object)(object)nview == (Object)null)
			{
				return;
			}
			if ((Object)(object)ZNetScene.instance != (Object)(object)_scene)
			{
				_scene = ZNetScene.instance;
				Jobs.Clear();
			}
			if (Busy(nview))
			{
				return;
			}
			Job job = new Job
			{
				Rock = rock,
				View = nview,
				Template = template,
				Prefab = Deposits.PrefabName(rock)
			};
			Collider[] areas = Deposits.Areas(rock, includeDead: true);
			Ledger.Reading reading = Ledger.Read(rock, nview);
			if (reading == null)
			{
				return;
			}
			for (int i = 0; i < reading.Health.Length && i < areas.Length; i++)
			{
				if (reading.Health[i] > 0f && (Object)(object)areas[i] != (Object)null)
				{
					job.Order.Add(i);
				}
			}
			job.Order.Sort(delegate(int a, int b)
			{
				//IL_0008: Unknown result type (might be due to invalid IL or missing references)
				//IL_000d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0010: Unknown result type (might be due to invalid IL or missing references)
				//IL_0015: Unknown result type (might be due to invalid IL or missing references)
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_002a: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				Bounds bounds = areas[b].bounds;
				Vector3 center = ((Bounds)(ref bounds)).center;
				ref float y = ref center.y;
				bounds = areas[a].bounds;
				return y.CompareTo(((Bounds)(ref bounds)).center.y);
			});
			Jobs.Add(job);
			try
			{
				Step(job);
			}
			catch (Exception error)
			{
				job.Next = job.Order.Count;
				Warn(error);
			}
			if (Done(job))
			{
				Finish(job);
			}
		}

		internal static void Tick()
		{
			if (Jobs.Count == 0)
			{
				return;
			}
			if ((Object)(object)ZNetScene.instance != (Object)(object)_scene)
			{
				_scene = ZNetScene.instance;
				Jobs.Clear();
				return;
			}
			for (int num = Jobs.Count - 1; num >= 0; num--)
			{
				Job job = Jobs[num];
				try
				{
					Step(job);
				}
				catch (Exception error)
				{
					job.Next = job.Order.Count;
					Warn(error);
				}
				if (Done(job))
				{
					Finish(job);
				}
			}
		}

		private static void Step(Job job)
		{
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			job.Frames++;
			int num = 6;
			long timestamp = Stopwatch.GetTimestamp();
			while (num > 0 && job.Next < job.Order.Count && (num >= 6 || !(Elapsed(timestamp) >= 4.0)))
			{
				if ((Object)(object)job.Rock == (Object)null || !job.View.IsValid() || !job.View.IsOwner())
				{
					job.Next = job.Order.Count;
					break;
				}
				int num2 = job.Order[job.Next++];
				float[] array = Ledger.Health(job.Rock, job.View.GetZDO());
				if (num2 < array.Length && !(array[num2] <= 0f))
				{
					Collider[] array2 = Deposits.Areas(job.Rock, includeDead: true);
					Collider val = ((num2 < array2.Length) ? array2[num2] : null);
					HitData val2 = job.Template.Clone();
					val2.m_damage = default(DamageTypes);
					val2.m_damage.m_damage = array[num2] + 0.01f;
					val2.m_hitCollider = null;
					val2.m_radius = 0f;
					val2.m_pushForce = 0f;
					if ((Object)(object)val != (Object)null)
					{
						Bounds bounds = val.bounds;
						val2.m_point = ((Bounds)(ref bounds)).center;
					}
					job.View.InvokeRPC((job.Rock is MineRock5) ? "RPC_Damage" : "Hit", new object[2] { val2, num2 });
					num--;
					if (!job.View.IsValid())
					{
						job.Broken++;
						job.Next = job.Order.Count;
						break;
					}
					float[] array3 = Ledger.Health(job.Rock, job.View.GetZDO());
					if (num2 < array3.Length && array3[num2] > 0f)
					{
						job.Refused++;
					}
					else
					{
						job.Broken++;
					}
				}
			}
		}

		private static double Elapsed(long started)
		{
			return (double)(Stopwatch.GetTimestamp() - started) * 1000.0 / (double)Stopwatch.Frequency;
		}

		private static bool Done(Job job)
		{
			return job.Next >= job.Order.Count;
		}

		private static void Finish(Job job)
		{
			Jobs.Remove(job);
			bool flag = (Object)(object)job.Rock != (Object)null && job.View.IsValid();
			Ledger.Reading reading = (flag ? Ledger.Read(job.Rock, job.View) : null);
			if (reading != null && reading.Standing == 0)
			{
				Ledger.SetProgress(job.View, 0f, reading.Total);
			}
			if ((job.Refused > 0 || (reading != null && reading.Standing > 0 && job.View.IsOwner())) && !_warned)
			{
				_warned = true;
				MalmrPlugin.Log.LogWarning((object)(job.Prefab + " did not break whole: " + job.Refused + " chunk(s) survived a lethal blow and " + (reading?.Standing ?? 0) + " are still standing. Its bar stays full, so the next vein blow tries again. Most likely a game update renamed the deposit's damage message. Said once per session."));
			}
			if (MalmrConfig.Verbose.Value)
			{
				MalmrPlugin.Log.LogInfo((object)(job.Prefab + " broke whole: " + job.Broken + " of " + job.Order.Count + " chunk(s) over " + job.Frames + " frame(s)" + (flag ? (", the deposit kept (" + (reading?.Standing ?? 0) + " standing)") : "") + "."));
			}
		}

		private static void Warn(Exception error)
		{
			if (!_warned)
			{
				_warned = true;
				MalmrPlugin.Log.LogWarning((object)("A deposit could not finish breaking. Its bar stays full, so the next vein blow tries again. Said once per session: " + error));
			}
		}
	}
	internal static class Deposits
	{
		internal sealed class Kind
		{
			public string Entry;

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

			public string Metal;

			public string Ore = "";

			public string Why = "";
		}

		internal sealed class Row
		{
			public string Prefab;

			public int Chunks;

			public Kind Kind;
		}

		private sealed class Station
		{
			public string Name;

			public string Fuel = "";

			public readonly List<Conversion> Conversions = new List<Conversion>();
		}

		private sealed class Conversion
		{
			public Station Station;

			public string From;

			public string To;
		}

		private static ZNetScene _scene;

		private static int _revision = -1;

		private static readonly List<Station> Stations = new List<Station>();

		private static readonly Dictionary<string, List<Conversion>> TakenBy = new Dictionary<string, List<Conversion>>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<Conversion> None = new List<Conversion>();

		private static readonly HashSet<Station> Ore = new HashSet<Station>();

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

		private static ZNetScene _surveyed;

		internal static readonly List<Row> Survey = new List<Row>();

		internal static string SmeltingLine { get; private set; }

		internal static Kind Of(Component rock)
		{
			if ((Object)(object)rock == (Object)null)
			{
				return null;
			}
			Refresh();
			string text = PrefabName(rock);
			if (ByPrefab.TryGetValue(text, out var value))
			{
				return value;
			}
			value = Classify(text, DropsOf(rock));
			ByPrefab[text] = value;
			return value;
		}

		internal static string PrefabName(Component rock)
		{
			if ((Object)(object)rock == (Object)null)
			{
				return "";
			}
			ZNetView val = default(ZNetView);
			if ((Object)(object)ZNetScene.instance != (Object)null && rock.TryGetComponent<ZNetView>(ref val) && val.IsValid())
			{
				GameObject prefab = ZNetScene.instance.GetPrefab(val.GetZDO().GetPrefab());
				if ((Object)(object)prefab != (Object)null)
				{
					return ((Object)prefab).name;
				}
			}
			return Utils.GetPrefabName(rock.gameObject);
		}

		internal static string DisplayName(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return "";
			}
			if ((Object)(object)ObjectDB.instance == (Object)null || Localization.instance == null)
			{
				return name;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name + "New");
			if ((Object)(object)itemPrefab == (Object)null)
			{
				itemPrefab = ObjectDB.instance.GetItemPrefab(name);
			}
			if ((Object)(object)itemPrefab == (Object)null)
			{
				return name;
			}
			ItemDrop val = default(ItemDrop);
			if (!itemPrefab.TryGetComponent<ItemDrop>(ref val) || val.m_itemData == null || val.m_itemData.m_shared == null)
			{
				return name;
			}
			string text = Localization.instance.Localize(val.m_itemData.m_shared.m_name);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return name;
		}

		internal static string Noun(string metal)
		{
			string text = MalmrConfig.NameFor(metal);
			if (!string.IsNullOrEmpty(text))
			{
				return text;
			}
			return DisplayName(metal) + " vein";
		}

		internal static string Nouns(string metal)
		{
			return Noun(metal) + "s";
		}

		internal static bool HandOnly(Component rock, Kind kind)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			if ((Object)(object)rock == (Object)null || kind == null || kind.Entry == null)
			{
				return false;
			}
			if ((int)BiomeOf(rock) != 512)
			{
				return false;
			}
			foreach (string entry in kind.Entries)
			{
				if (!MalmrConfig.MistlandsAllows(entry))
				{
					return true;
				}
			}
			return false;
		}

		internal static Biome BiomeOf(Component rock)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)rock == (Object)null))
			{
				return Heightmap.FindBiome(rock.transform.position);
			}
			return (Biome)0;
		}

		internal static Collider[] Areas(Component rock, bool includeDead)
		{
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val != (Object)null)
			{
				return ((Component)val).GetComponentsInChildren<Collider>(includeDead);
			}
			MineRock val2 = (MineRock)(object)((rock is MineRock) ? rock : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return (Collider[])(object)new Collider[0];
			}
			if (!((Object)(object)val2.m_areaRoot != (Object)null))
			{
				return ((Component)val2).GetComponentsInChildren<Collider>(includeDead);
			}
			return val2.m_areaRoot.GetComponentsInChildren<Collider>(includeDead);
		}

		internal static float Distance(Component rock, Vector3 from)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			float num = float.MaxValue;
			Collider[] array = Areas(rock, includeDead: false);
			foreach (Collider val in array)
			{
				if (!((Object)(object)val == (Object)null) && val.enabled)
				{
					Bounds bounds = val.bounds;
					float num2 = ((Bounds)(ref bounds)).SqrDistance(from);
					if (num2 < num)
					{
						num = num2;
					}
				}
			}
			if (num != float.MaxValue)
			{
				return Mathf.Sqrt(num);
			}
			return Vector3.Distance(from, rock.transform.position);
		}

		internal static int MinToolTier(Component rock)
		{
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val != (Object)null)
			{
				return val.m_minToolTier;
			}
			MineRock val2 = (MineRock)(object)((rock is MineRock) ? rock : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return 0;
			}
			return val2.m_minToolTier;
		}

		internal static void SurveyTick()
		{
			if ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)_surveyed == (Object)(object)ZNetScene.instance || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || ObjectDB.instance.m_items.Count == 0)
			{
				return;
			}
			_surveyed = ZNetScene.instance;
			try
			{
				Write();
			}
			catch (Exception ex)
			{
				MalmrPlugin.Log.LogWarning((object)("Could not write the deposit table: " + ex.Message));
			}
		}

		internal static void Rebuild()
		{
			Refresh();
			Survey.Clear();
			if ((Object)(object)ZNetScene.instance == (Object)null)
			{
				return;
			}
			foreach (GameObject prefab in ZNetScene.instance.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Component val = Rock(prefab);
				if (!((Object)(object)val == (Object)null))
				{
					string name = ((Object)prefab).name;
					if (!ByPrefab.TryGetValue(name, out var value))
					{
						value = Classify(name, DropsOf(val));
						ByPrefab[name] = value;
					}
					Survey.Add(new Row
					{
						Prefab = name,
						Chunks = Areas(val, includeDead: true).Length,
						Kind = value
					});
				}
			}
			Survey.Sort((Row a, Row b) => string.Compare(a.Prefab, b.Prefab, StringComparison.OrdinalIgnoreCase));
		}

		private static void Write()
		{
			Rebuild();
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append("Deposits in this world, and what each one counts as for vein mining:");
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			List<string> list = new List<string>();
			foreach (Row item in Survey)
			{
				if (item.Kind == null || item.Kind.Entry == null)
				{
					list.Add((item.Kind != null && item.Kind.Why.Length > 0) ? (item.Prefab + " (" + item.Kind.Why + ")") : item.Prefab);
					continue;
				}
				foreach (string entry in item.Kind.Entries)
				{
					hashSet.Add(entry);
				}
				stringBuilder.Append("\n   ").Append(item.Prefab.PadRight(26)).Append(' ')
					.Append(item.Kind.Metal)
					.Append(", ")
					.Append(Opens(item.Kind.Entry));
				foreach (string entry2 in item.Kind.Entries)
				{
					if (entry2 != item.Kind.Entry)
					{
						stringBuilder.Append(", with ").Append(entry2).Append(' ')
							.Append(Opens(entry2));
					}
				}
				stringBuilder.Append(", ").Append(item.Chunks).Append(" chunk(s) - ")
					.Append(item.Kind.Why);
			}
			if (list.Count > 0)
			{
				stringBuilder.Append("\n   Not veins (no entry in Unlocks answers for what they drop): ").Append(string.Join(", ", list.ToArray()));
			}
			foreach (string key in MalmrConfig.UnlockTable().Keys)
			{
				if (!(key == "*") && !hashSet.Contains(key))
				{
					stringBuilder.Append("\n   ").Append(key).Append(": no deposit here resolves to it. If one should, either no station this world has makes it from what the deposit drops, or the deposit drops something besides it (see the not-veins list) - name it in Deposits as Prefab:")
						.Append(key)
						.Append('.');
				}
			}
			stringBuilder.Append("\n   ").Append(SmeltingLine);
			stringBuilder.Append("\n   In the Mistlands only these vein mine, everything else there by hand: ").Append(MalmrConfig.MistlandsText());
			Bosses.Describe(stringBuilder);
			MalmrPlugin.Log.LogInfo((object)stringBuilder.ToString());
		}

		private static string Opens(string entry)
		{
			int num = MalmrConfig.LevelFor(entry);
			if (num < 0)
			{
				return "never";
			}
			string text = MalmrConfig.BossFor(entry);
			return "from Pickaxes " + num + ((text != null) ? (" and " + text + " x" + MalmrConfig.BossKills.Value) : "");
		}

		private static void Refresh()
		{
			MalmrConfig.UnlockTable();
			MalmrConfig.DepositOverride("");
			bool flag = false;
			if ((Object)(object)ZNetScene.instance != (Object)(object)_scene)
			{
				_scene = ZNetScene.instance;
				ReadSmelters();
				ByPrefab.Clear();
				flag = true;
			}
			if (MalmrConfig.Revision != _revision)
			{
				_revision = MalmrConfig.Revision;
				ByPrefab.Clear();
				flag = true;
			}
			if (flag)
			{
				OreStations();
			}
		}

		private static void ReadSmelters()
		{
			Stations.Clear();
			TakenBy.Clear();
			if ((Object)(object)_scene == (Object)null)
			{
				return;
			}
			Smelter val = default(Smelter);
			foreach (GameObject prefab in _scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null || !prefab.TryGetComponent<Smelter>(ref val) || val.m_conversion == null)
				{
					continue;
				}
				Station station = new Station
				{
					Name = ((Object)prefab).name
				};
				if (val.m_maxFuel > 0 && (Object)(object)val.m_fuelItem != (Object)null)
				{
					station.Fuel = Utils.GetPrefabName(((Component)val.m_fuelItem).gameObject);
				}
				foreach (ItemConversion item in val.m_conversion)
				{
					if (item != null && !((Object)(object)item.m_from == (Object)null) && !((Object)(object)item.m_to == (Object)null))
					{
						Conversion conversion = new Conversion
						{
							Station = station,
							From = Utils.GetPrefabName(((Component)item.m_from).gameObject),
							To = Utils.GetPrefabName(((Component)item.m_to).gameObject)
						};
						station.Conversions.Add(conversion);
						if (!TakenBy.TryGetValue(conversion.From, out var value))
						{
							value = (TakenBy[conversion.From] = new List<Conversion>());
						}
						value.Add(conversion);
					}
				}
				if (station.Conversions.Count > 0)
				{
					Stations.Add(station);
				}
			}
		}

		private static List<Conversion> Taking(string item)
		{
			if (!TakenBy.TryGetValue(item, out var value))
			{
				return None;
			}
			return value;
		}

		private static void OreStations()
		{
			Ore.Clear();
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (Station station2 in Stations)
			{
				foreach (Conversion conversion in station2.Conversions)
				{
					if (MalmrConfig.MatchEntry(conversion.To) != null)
					{
						Ore.Add(station2);
						if (station2.Fuel.Length > 0)
						{
							hashSet.Add(station2.Fuel);
						}
						break;
					}
				}
			}
			foreach (Station station3 in Stations)
			{
				if (station3.Fuel.Length > 0 && hashSet.Contains(station3.Fuel))
				{
					Ore.Add(station3);
				}
			}
			StringBuilder stringBuilder = new StringBuilder("Smelting stations here turn: ");
			if (Stations.Count == 0)
			{
				stringBuilder.Append("nothing (no Smelter found)");
			}
			for (int i = 0; i < Stations.Count; i++)
			{
				Station station = Stations[i];
				if (i > 0)
				{
					stringBuilder.Append("; ");
				}
				stringBuilder.Append(station.Name).Append(Ore.Contains(station) ? "" : " (not a furnace, so never *)").Append(": ");
				for (int j = 0; j < station.Conversions.Count; j++)
				{
					if (j > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(station.Conversions[j].From).Append('>').Append(station.Conversions[j].To);
				}
			}
			SmeltingLine = stringBuilder.ToString();
		}

		private static Kind Classify(string prefab, List<DropData> drops)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			string text = MalmrConfig.DepositOverride(prefab);
			if (text != null)
			{
				string text2 = MalmrConfig.MatchEntry(text);
				Kind kind = new Kind
				{
					Entry = text2,
					Metal = (text2 ?? text),
					Why = ((text2 != null) ? "named in Deposits" : ("named in Deposits as " + text + ", which is not in Unlocks"))
				};
				if (text2 != null)
				{
					kind.Entries.Add(text2);
				}
				return kind;
			}
			if (drops.Count == 0)
			{
				return new Kind
				{
					Why = "drops nothing"
				};
			}
			List<string> list = new List<string>();
			foreach (DropData drop in drops)
			{
				string prefabName = Utils.GetPrefabName(drop.m_item);
				if (!list.Contains(prefabName))
				{
					list.Add(prefabName);
				}
			}
			string text3 = "drops " + string.Join(", ", list.ToArray());
			return AsMetal(list) ?? ByName(list, text3) ?? AsAnyOre(list) ?? new Kind
			{
				Why = text3
			};
		}

		private static Kind AsMetal(List<string> items)
		{
			foreach (string item in items)
			{
				foreach (Conversion item2 in Taking(item))
				{
					string text = MalmrConfig.MatchEntry(item2.To);
					if (text != null)
					{
						Kind kind = new Kind();
						kind.Entry = text;
						kind.Metal = text;
						kind.Ore = item;
						kind.Why = "drops " + item + ", which smelts into " + item2.To + " at " + item2.Station.Name;
						kind.Entries.Add(text);
						return kind;
					}
				}
			}
			return null;
		}

		private static Kind ByName(List<string> items, string dropped)
		{
			Kind kind = new Kind();
			foreach (string item in items)
			{
				string text = MalmrConfig.MatchEntry(item);
				if (text == null)
				{
					return null;
				}
				if (!kind.Entries.Contains(text))
				{
					kind.Entries.Add(text);
				}
				if (kind.Entry == null || Strictness(text) > Strictness(kind.Entry))
				{
					kind.Entry = text;
					kind.Ore = item;
				}
			}
			kind.Metal = kind.Entry;
			kind.Why = ((items.Count == 1) ? ("drops only " + kind.Ore + ", named in Unlocks") : ((kind.Entries.Count == 1) ? (dropped + ", all named in Unlocks as " + kind.Entry) : (dropped + ", all named in Unlocks, so every one of " + string.Join(", ", kind.Entries.ToArray()) + " has to be open; called " + kind.Entry + ", the strictest")));
			return kind;
		}

		private static Kind AsAnyOre(List<string> items)
		{
			if (!MalmrConfig.UnlockTable().ContainsKey("*"))
			{
				return null;
			}
			foreach (string item in items)
			{
				foreach (Conversion item2 in Taking(item))
				{
					if (Ore.Contains(item2.Station))
					{
						Kind kind = new Kind();
						kind.Entry = "*";
						kind.Metal = item2.To;
						kind.Ore = item;
						kind.Why = "drops " + item + ", which smelts into " + item2.To + " at " + item2.Station.Name + " - not named, so the * level applies";
						kind.Entries.Add("*");
						return kind;
					}
				}
			}
			return null;
		}

		private static int Strictness(string entry)
		{
			int num = MalmrConfig.LevelFor(entry);
			if (num >= 0)
			{
				return num;
			}
			return int.MaxValue;
		}

		private static List<DropData> DropsOf(Component rock)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00da: 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_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			DropTable val = null;
			MineRock5 val2 = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val2 != (Object)null)
			{
				val = val2.m_dropItems;
			}
			MineRock val3 = (MineRock)(object)((rock is MineRock) ? rock : null);
			if ((Object)(object)val3 != (Object)null)
			{
				val = val3.m_dropItems;
			}
			List<DropData> list = new List<DropData>();
			if (val == null || val.m_drops == null)
			{
				return list;
			}
			foreach (DropData drop in val.m_drops)
			{
				if ((Object)(object)drop.m_item != (Object)null)
				{
					list.Add(drop);
				}
			}
			for (int i = 1; i < list.Count; i++)
			{
				DropData val4 = list[i];
				int num = i - 1;
				while (num >= 0 && list[num].m_weight < val4.m_weight)
				{
					list[num + 1] = list[num];
					num--;
				}
				list[num + 1] = val4;
			}
			return list;
		}

		private static Component Rock(GameObject prefab)
		{
			MineRock5 result = default(MineRock5);
			if (prefab.TryGetComponent<MineRock5>(ref result))
			{
				return (Component)(object)result;
			}
			MineRock result2 = default(MineRock);
			if (prefab.TryGetComponent<MineRock>(ref result2))
			{
				return (Component)(object)result2;
			}
			return null;
		}
	}
	internal static class DevConsole
	{
		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		internal static class Hook
		{
			[CompilerGenerated]
			private static class <>O
			{
				public static ConsoleEvent <0>__OnCommand;
			}

			private static void Postfix()
			{
				//IL_003d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0028: Unknown result type (might be due to invalid IL or missing references)
				//IL_002d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0033: Expected O, but got Unknown
				if (!_registered)
				{
					_registered = true;
					object obj = <>O.<0>__OnCommand;
					if (obj == null)
					{
						ConsoleEvent val = OnCommand;
						<>O.<0>__OnCommand = val;
						obj = (object)val;
					}
					new ConsoleCommand("malmr", "[vein on|off | progress [prefab] [metres]] - what your Pickaxes level and boss kills open, and what every deposit counts as. vein switches vein mining like its key; progress reads the bar of the nearest deposit within 10 metres, or the nearest of that name, or within that many metres", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
				}
			}
		}

		private const float ProgressReach = 10f;

		private const float ProgressReachMax = 50f;

		private static bool _registered;

		private static void OnCommand(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			if ((Object)(object)context == (Object)null)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null)
			{
				context.AddString("malmr: no world loaded");
				return;
			}
			string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "");
			if (text == "vein")
			{
				VeinVerb(context, args);
			}
			else if (text == "progress")
			{
				ProgressVerb(context, localPlayer, args);
			}
			else if (text.Length > 0)
			{
				context.AddString("malmr: unknown '" + args[1] + "'. Try malmr, malmr vein on|off or malmr progress [prefab] [metres].");
			}
			else
			{
				Report(context, localPlayer);
			}
		}

		private static void VeinVerb(Terminal term, ConsoleEventArgs args)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			string text = ((args.Length > 2) ? args[2].ToLowerInvariant() : "");
			if (text == "on")
			{
				VeinMode.Set(on: true);
			}
			else if (text == "off")
			{
				VeinMode.Set(on: false);
			}
			else if (text.Length > 0)
			{
				term.AddString("malmr vein: on or off, not '" + args[2] + "'");
				return;
			}
			term.AddString("malmr vein=" + (VeinMode.On ? "on" : "off") + (MalmrConfig.Enabled.Value ? "" : " (the mod is switched off in its config)") + "   (the key is " + Opened.KeyName(MalmrConfig.VeinToggleKey.Value) + ", tapped with a pickaxe out)");
		}

		private static void ProgressVerb(Terminal term, Player player, ConsoleEventArgs args)
		{
			string only = null;
			float reachMetres = 10f;
			int num = 2;
			if (args.Length > num && !float.TryParse(args[num], NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
			{
				only = args[num];
				num++;
			}
			if (args.Length > num)
			{
				if (!float.TryParse(args[num], NumberStyles.Float, CultureInfo.InvariantCulture, out result) || result <= 0f)
				{
					term.AddString("malmr progress: '" + args[num] + "' is not a number of metres");
					return;
				}
				reachMetres = Mathf.Min(result, 50f);
			}
			Progress(term, player, only, reachMetres);
		}

		private static void Progress(Terminal term, Player player, string only, float reachMetres)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a6: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = ((Component)player).transform.position;
			Component nearest = null;
			float best = reachMetres;
			MineRock5[] array = Object.FindObjectsByType<MineRock5>((FindObjectsSortMode)0);
			for (int i = 0; i < array.Length; i++)
			{
				Consider((Component)(object)array[i], position, only, ref nearest, ref best);
			}
			MineRock[] array2 = Object.FindObjectsByType<MineRock>((FindObjectsSortMode)0);
			for (int i = 0; i < array2.Length; i++)
			{
				Consider((Component)(object)array2[i], position, only, ref nearest, ref best);
			}
			string text = " reach=" + reachMetres.ToString("0.#", CultureInfo.InvariantCulture);
			if ((Object)(object)nearest == (Object)null)
			{
				term.AddString("malmr progress=none" + text + "   (no " + (string.IsNullOrEmpty(only) ? "deposit" : only) + " within that many metres)");
				return;
			}
			ZNetView val = default(ZNetView);
			nearest.TryGetComponent<ZNetView>(ref val);
			Ledger.Reading reading = Ledger.Read(nearest, val);
			if (reading == null)
			{
				term.AddString("malmr progress=none" + text + "   (the nearest deposit has no live ZDO)");
				return;
			}
			Deposits.Kind kind = Deposits.Of(nearest);
			bool flag = kind != null && kind.Entry != null;
			bool flag2 = flag && Deposits.HandOnly(nearest, kind);
			bool flag3 = flag && !flag2 && Gate.For(kind, Vein.EarnedLevel(player)).Open;
			term.AddString("malmr progress prefab=" + Deposits.PrefabName(nearest) + " metal=" + (flag ? kind.Metal : "none") + " open=" + (flag3 ? "yes" : "no") + " biome=" + ((object)Deposits.BiomeOf(nearest)/*cast due to .constrained prefix*/).ToString() + " handonly=" + (flag2 ? "yes" : "no") + " percent=" + reading.Percent + " damage=" + reading.Progress.ToString("0.0", CultureInfo.InvariantCulture) + " total=" + reading.Total.ToString("0.0", CultureInfo.InvariantCulture) + " stale=" + (reading.Stale ? "yes" : "no") + " chunks=" + reading.Standing + "/" + reading.Chunks + " owner=" + (val.IsOwner() ? "self" : "other") + " distance=" + best.ToString("0.0", CultureInfo.InvariantCulture) + text);
		}

		private static void Consider(Component rock, Vector3 here, string only, ref Component nearest, ref float best)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			ZNetView val = default(ZNetView);
			if (!((Object)(object)rock == (Object)null) && rock.TryGetComponent<ZNetView>(ref val) && val.IsValid() && (string.IsNullOrEmpty(only) || string.Equals(Deposits.PrefabName(rock), only, StringComparison.OrdinalIgnoreCase)))
			{
				float num = Deposits.Distance(rock, here);
				if (!(num > best))
				{
					best = num;
					nearest = rock;
				}
			}
		}

		private static void Report(Terminal term, Player player)
		{
			float num = Vein.EarnedLevel(player);
			float skillLevel = ((Character)player).GetSkillLevel((SkillType)12);
			term.AddString("malmr enabled=" + (MalmrConfig.Enabled.Value ? "yes" : "no") + " pickaxes=" + (int)num + " vein=" + (VeinMode.On ? "on" : "off") + " bosskills=" + MalmrConfig.BossKills.Value + " bosscount=" + Bosses.CountName + "   (" + ((MalmrConfig.BossKills.Value <= 0) ? "BossKills is 0, so no metal waits for a boss" : (Bosses.ThroughVandi ? ("a boss counts through Vandi: " + Bosses.KillsNeeded + " kills of a boss you summoned, " + ((Bosses.KillsNeeded == 2) ? "the one-star kill" : "as BossKills says")) : "Vandi is not installed, so a boss counts once this character has killed it, as the game counts kills")) + ")");
			if ((int)skillLevel != (int)num)
			{
				term.AddString("Your gear and effects put Pickaxes at " + (int)skillLevel + ". That makes each blow harder but does not open veins early.");
			}
			term.AddString("mistlands=" + MalmrConfig.MistlandsText() + "   (only these vein mine in the Mistlands; anything else there is mined by hand)");
			foreach (KeyValuePair<string, int> item in MalmrConfig.UnlockTable())
			{
				Gate gate = Gate.For(item.Key, num);
				bool flag = MalmrConfig.Enabled.Value && gate.Open;
				string text = ((gate.Boss == null) ? " boss=none" : (" boss=" + gate.Boss + " kills=" + ((gate.Kills < 0) ? "?" : gate.Kills.ToString()) + "/" + gate.KillsNeeded));
				string text2 = ((item.Key == "*") ? "any other ore a furnace takes, " : (Deposits.Nouns(item.Key) + ", "));
				term.AddString(item.Key + " unlock=" + item.Value + " open=" + (flag ? "yes" : "no") + text + " missing=" + gate.Missing + "   (" + text2 + gate.Why() + ")");
			}
			foreach (string item2 in Bosses.Holes())
			{
				term.AddString(item2);
			}
			Deposits.Rebuild();
			List<string> list = new List<string>();
			foreach (Deposits.Row item3 in Deposits.Survey)
			{
				if (item3.Kind == null || item3.Kind.Entry == null)
				{
					list.Add(item3.Prefab);
					continue;
				}
				term.AddString(item3.Prefab + "=" + item3.Kind.Metal + " chunks=" + item3.Chunks + "   (" + item3.Kind.Why + ")");
			}
			if (list.Count > 0)
			{
				term.AddString("not veins: " + string.Join(", ", list.ToArray()));
			}
			if (!string.IsNullOrEmpty(Deposits.SmeltingLine))
			{
				term.AddString(Deposits.SmeltingLine);
			}
		}
	}
	internal static class Focus
	{
		private const float Linger = 5f;

		private const float Reach = 8f;

		private const float Recheck = 0.25f;

		private static Component _struck;

		private static Vector3 _struckPoint;

		private static float _struckAt;

		private static Component _gated;

		private static float _nextGate;

		private static bool _open;

		private static string _noun = "";

		private static ZDOID _readUid = ZDOID.None;

		private static uint _readRevision;

		private static Ledger.Reading _reading;

		private static bool _marker;

		private static bool _bar;

		private static string _label = "";

		private static float _fraction;

		internal static void Struck(Component rock, Vector3 point)
		{
			//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)
			_struck = rock;
			_struckPoint = point;
			_struckAt = Time.time;
		}

		internal static void Tick()
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			_marker = false;
			_bar = false;
			if (!MalmrConfig.Enabled.Value || !VeinMode.On)
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead() || !VeinMode.PickaxeOut(localPlayer))
			{
				return;
			}
			_marker = true;
			Component val = Hovered(localPlayer);
			if ((Object)(object)val == (Object)null && (Object)(object)_struck != (Object)null && Time.time - _struckAt < 5f && Vector3.Distance(((Component)localPlayer).transform.position, _struckPoint) < 8f)
			{
				val = _struck;
			}
			ZNetView val2 = default(ZNetView);
			if ((Object)(object)val == (Object)null || !val.TryGetComponent<ZNetView>(ref val2) || !val2.IsValid())
			{
				return;
			}
			if ((Object)(object)val != (Object)(object)_gated || Time.time >= _nextGate)
			{
				_gated = val;
				_nextGate = Time.time + 0.25f;
				Deposits.Kind kind = Deposits.Of(val);
				_open = kind != null && kind.Entry != null && !Deposits.HandOnly(val, kind) && Gate.For(kind, Vein.EarnedLevel(localPlayer)).Open;
				_noun = ((kind != null) ? Deposits.Noun(kind.Metal) : "");
			}
			if (_open)
			{
				Ledger.Reading reading = Read(val, val2);
				if (reading != null && reading.Standing != 0)
				{
					_bar = true;
					_fraction = reading.Fraction;
					_label = _noun + " " + reading.Percent + "%";
				}
			}
		}

		internal static void Draw()
		{
			if (_marker)
			{
				VeinBar.Draw(_bar, _label, _fraction);
			}
		}

		private static Ledger.Reading Read(Component rock, ZNetView nview)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			ZDO zDO = nview.GetZDO();
			if (_reading != null && zDO.m_uid == _readUid && zDO.DataRevision == _readRevision)
			{
				return _reading;
			}
			_reading = Ledger.Read(rock, nview);
			_readUid = zDO.m_uid;
			_readRevision = zDO.DataRevision;
			return _reading;
		}

		private static Component Hovered(Player player)
		{
			GameObject hoverObject = ((Humanoid)player).GetHoverObject();
			if ((Object)(object)hoverObject == (Object)null)
			{
				return null;
			}
			MineRock5 componentInParent = hoverObject.GetComponentInParent<MineRock5>();
			if ((Object)(object)componentInParent != (Object)null)
			{
				return (Component)(object)componentInParent;
			}
			MineRock componentInParent2 = hoverObject.GetComponentInParent<MineRock>();
			if ((Object)(object)componentInParent2 != (Object)null)
			{
				return (Component)(object)componentInParent2;
			}
			return null;
		}
	}
	internal sealed class Gate
	{
		public string Entry;

		public int Unlock;

		public float Level;

		public string Boss;

		public int KillsNeeded;

		public int Kills;

		public bool Off => Unlock < 0;

		public bool LevelMet
		{
			get
			{
				if (Unlock >= 0)
				{
					return Level >= (float)Unlock;
				}
				return false;
			}
		}

		public bool BossMet
		{
			get
			{
				if (Boss != null)
				{
					if (Kills >= 0)
					{
						return Kills >= KillsNeeded;
					}
					return false;
				}
				return true;
			}
		}

		public bool Open
		{
			get
			{
				if (!Off && LevelMet)
				{
					return BossMet;
				}
				return false;
			}
		}

		public string Missing
		{
			get
			{
				if (Open)
				{
					return "none";
				}
				if (Off)
				{
					return "off";
				}
				if (!LevelMet && !BossMet)
				{
					return "both";
				}
				if (!LevelMet)
				{
					return "level";
				}
				return "boss";
			}
		}

		internal static Gate For(string entry, float level)
		{
			Gate gate = new Gate
			{
				Entry = entry,
				Unlock = MalmrConfig.LevelFor(entry),
				Level = level,
				Boss = MalmrConfig.BossFor(entry)
			};
			if (gate.Boss != null)
			{
				gate.KillsNeeded = Bosses.KillsNeeded;
				gate.Kills = Bosses.LocalKills(gate.Boss);
			}
			return gate;
		}

		internal static Gate For(Deposits.Kind kind, float level)
		{
			Gate gate = For(kind.Entry, level);
			if (!gate.Open)
			{
				return gate;
			}
			foreach (string entry in kind.Entries)
			{
				if (!(entry == kind.Entry))
				{
					Gate gate2 = For(entry, level);
					if (!gate2.Open)
					{
						return gate2;
					}
				}
			}
			return gate;
		}

		public string Why()
		{
			if (Off)
			{
				return "switched off in Unlocks";
			}
			if (Open)
			{
				return "open";
			}
			List<string> list = new List<string>();
			if (!LevelMet)
			{
				list.Add("Pickaxes " + Unlock + ", you are at " + (int)Level);
			}
			if (!BossMet)
			{
				string text = ((Kills < 0) ? "a count that could not be read" : Kills.ToString());
				list.Add(Bosses.ThroughVandi ? (Bosses.DisplayName(Boss) + " killed " + KillsNeeded + " time(s) through Vandi, you have " + text) : (Bosses.DisplayName(Boss) + " killed once by this character, as the game counts it, you have " + text));
			}
			return "needs " + string.Join(" and ", list.ToArray());
		}

		public string Needs()
		{
			List<string> list = new List<string>();
			if (!LevelMet)
			{
				list.Add("Pickaxes " + Unlock);
			}
			if (!BossMet)
			{
				list.Add(Bosses.Phrase(Boss));
			}
			return string.Join(" and ", list.ToArray());
		}

		public string Earned()
		{
			string text = "Pickaxes " + Unlock;
			if (Boss != null)
			{
				return text + " and " + Bosses.Phrase(Boss);
			}
			return text;
		}
	}
	internal static class Ledger
	{
		internal sealed class Reading
		{
			public float Progress;

			public bool Stale;

			public float Total;

			public int Standing;

			public int Chunks;

			public float[] Health;

			public float Fraction
			{
				get
				{
					if (!(Total <= 0f))
					{
						return Mathf.Clamp01(Progress / Total);
					}
					return 1f;
				}
			}

			public int Percent => Mathf.Clamp(Mathf.FloorToInt(Fraction * 100f), 0, 100);
		}

		internal const string ProgressName = "malmr_vein_progress";

		internal const string TotalName = "malmr_vein_total";

		private const float Regrown = 0.01f;

		private static int _progressKey;

		private static int _totalKey;

		private static bool _keyed;

		internal static int ProgressKey
		{
			get
			{
				Key();
				return _progressKey;
			}
		}

		internal static int TotalKey
		{
			get
			{
				Key();
				return _totalKey;
			}
		}

		private static void Key()
		{
			if (!_keyed)
			{
				_progressKey = StringExtensionMethods.GetStableHashCode("malmr_vein_progress");
				_totalKey = StringExtensionMethods.GetStableHashCode("malmr_vein_total");
				_keyed = true;
			}
		}

		internal static Reading Read(Component rock, ZNetView nview)
		{
			if ((Object)(object)rock == (Object)null || (Object)(object)nview == (Object)null || !nview.IsValid())
			{
				return null;
			}
			ZDO zDO = nview.GetZDO();
			float[] array = Health(rock, zDO);
			Reading reading = new Reading
			{
				Health = array,
				Chunks = array.Length
			};
			float[] array2 = array;
			foreach (float num in array2)
			{
				if (!(num <= 0f))
				{
					reading.Total += num;
					reading.Standing++;
				}
			}
			float num2 = Mathf.Max(0f, zDO.GetFloat(ProgressKey, 0f));
			float num3 = zDO.GetFloat(TotalKey, -1f);
			if (num2 > 0f && num3 >= 0f && reading.Total > num3 + 0.01f)
			{
				reading.Stale = true;
			}
			else
			{
				reading.Progress = num2;
			}
			return reading;
		}

		internal static void SetProgress(ZNetView nview, float value, float total)
		{
			if (!((Object)(object)nview == (Object)null) && nview.IsValid() && nview.IsOwner())
			{
				ZDO zDO = nview.GetZDO();
				zDO.Set(ProgressKey, Mathf.Max(0f, value));
				zDO.Set(TotalKey, Mathf.Max(0f, total));
			}
		}

		internal static float[] Health(Component rock, ZDO zdo)
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Expected O, but got Unknown
			int num = Deposits.Areas(rock, includeDead: true).Length;
			float[] array = new float[num];
			float num2 = FullHealth(rock);
			for (int i = 0; i < num; i++)
			{
				array[i] = num2;
			}
			if (zdo == null)
			{
				return array;
			}
			if ((Object)(object)((rock is MineRock5) ? rock : null) != (Object)null)
			{
				string text = zdo.GetString(ZDOVars.s_health, "");
				if (text.Length == 0)
				{
					return array;
				}
				try
				{
					ZPackage val = new ZPackage(Convert.FromBase64String(text));
					int num3 = val.ReadInt();
					for (int j = 0; j < num3; j++)
					{
						float num4 = val.ReadSingle();
						if (j < num)
						{
							array[j] = num4;
						}
					}
				}
				catch (Exception)
				{
				}
				return array;
			}
			if (rock is MineRock)
			{
				for (int k = 0; k < num; k++)
				{
					array[k] = zdo.GetFloat("Health" + k, num2);
				}
			}
			return array;
		}

		internal static float FullHealth(Component rock)
		{
			MineRock val = (MineRock)(object)((rock is MineRock) ? rock : null);
			if ((Object)(object)val != (Object)null)
			{
				return val.GetHealth();
			}
			MineRock5 val2 = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val2 == (Object)null)
			{
				return 0f;
			}
			float num = (((Object)(object)Game.instance != (Object)null) ? Game.instance.m_worldLevelMineHPMultiplier : 0f);
			return val2.m_health + (float)Game.m_worldLevel * val2.m_health * num;
		}

		internal static DamageModifiers Modifiers(Component rock)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val != (Object)null)
			{
				return val.m_damageModifiers;
			}
			MineRock val2 = (MineRock)(object)((rock is MineRock) ? rock : null);
			if (!((Object)(object)val2 != (Object)null))
			{
				return default(DamageModifiers);
			}
			return val2.m_damageModifiers;
		}
	}
	internal static class MalmrConfig
	{
		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<string> Unlocks;

		internal static ConfigEntry<string> Deposits;

		internal static ConfigEntry<string> Mistlands;

		internal static ConfigEntry<string> Names;

		internal static ConfigEntry<string> Bosses;

		internal static ConfigEntry<int> BossKills;

		internal static ConfigEntry<KeyCode> VeinToggleKey;

		internal static ConfigEntry<bool> AnnounceUnlocks;

		internal static ConfigEntry<bool> Verbose;

		internal const string AnyMetal = "*";

		private static string _unlocksRaw;

		private static Dictionary<string, int> _unlocks;

		private static string _depositsRaw;

		private static Dictionary<string, string> _deposits;

		private static string _bossesRaw;

		private static Dictionary<string, string> _bosses;

		private static string _mistlandsRaw;

		private static List<string> _mistlands;

		private static string _namesRaw;

		private static Dictionary<string, string> _names;

		internal static int Revision { get; private set; }

		internal static void Bind(ConfigFile cfg)
		{
			Enabled = cfg.Bind<bool>("Malmr", "Enabled", true, "Off leaves the plugin loaded and changing nothing. Every swing is vanilla again, and a deposit's stored progress stays where it was until the mod is back on.");
			Unlocks = cfg.Bind<string>("Unlocks", "Unlocks", "Stone:20, Copper:30, Iron:40, Silver:50, Eitr:60, Flametal:70, Gold:80", "The Pickaxes level at which each metal's deposits, and stone, can be vein mined. Comma separated Name:Level pairs. Only what is named here vein mines; everything else is mined by hand. The name is the smelted metal, the one that comes OUT of the smelter, so Iron covers muddy scrap piles and anything else whose drop smelts into iron. A trailing \"New\" is ignored when names are compared, so Flametal covers both the old meteorite flametal and the Ashlands one. Names are prefab names, not what the game shows: Gold is the Deep North metal the game calls Bloodgold, and Eitr is the Mistlands' giant brains, because what they drop becomes refined eitr in the eitr refinery (Names sets what the screen calls them). Tin and obsidian are not here, and naming them vein mines nothing: in Valheim 1.0 a tin rock and an obsidian rock are each a single rock, not a deposit of chunks, and Malmr does not touch them, so they are mined by hand whatever this line says. Named here they still get an unlock message and a console line for veins that never come, and the log says no deposit resolves to them, so leave them out; naming a rock in Deposits, which that log line suggests, cannot help for them either. A name can also be the dropped item itself, for what nothing smelts. Stone is one: it covers the plain rocks and boulders, the ones whose every drop is stone. A deposit that drops ore is never a stone vein, even though ore deposits drop stone as well: it is its ore's vein, or nobody's if that ore is not named here. A rock that drops anything else beside its stone is not a stone vein either, unless that is named here too, and then BOTH have to be open to you, each with its own level and its own boss, and both on the Mistlands line for a rock that stands there, because a full bar hands out everything the rock drops. So a name for a dropped item covers a rock only while every other drop on that rock is named here as well, at a level that is not -1: one drop left out or switched off keeps the whole rock mined by hand. A * entry, for example *:80, would give every other ore a level, meaning a drop that goes into a furnace: a station that makes one of the entries named here, or burns the same fuel as one that does. That is how an ore added by another mod could join without a new build; naming that ore here is the better way, because it gets its own level. A level of -1 switches that entry off. The level compared is the one you have earned, the big number on the skills page. A bonus from gear, food or an effect makes each blow harder but does not open a metal early.");
			Deposits = cfg.Bind<string>("Unlocks", "Deposits", "", "Overrides, as Prefab:Name pairs, comma separated, e.g. \"rock4_copper_frac:none, goldvein_frac:Gold\". The prefab is the deposit's own name as the log lists it on world load, and the Name is looked up in Unlocks like any other. Empty is the normal state: every deposit is classed by what it drops, and the log says what each one came out as. Use a Name that is not in Unlocks (for example none) to rule a deposit out. Only a deposit in that list can be overridden. A rock that is not in it, like tin or obsidian in 1.0, is one Malmr does not touch, and naming it here does nothing.");
			Mistlands = cfg.Bind<string>("Unlocks", "Mistlands", "Eitr, Stone", "Which Unlocks entries still vein mine when the deposit stands in the Mistlands, comma separated. Everything else found in the Mistlands, like copper or iron, is mined the normal way there even when that metal is open to you, and the top left of the screen says so once per deposit. The default is Eitr, the giant brains, which are the Mistlands' own vein, and Stone: this line is here to keep the copper and iron scattered through the Mistlands mined by hand, and stone is stone everywhere. A rock that answers to two entries, like one dropping two things named in Unlocks, needs both here. * lets every open entry vein mine there too. Empty means nothing vein mines in the Mistlands. The biome is the one on the map at the deposit's spot; everywhere else this line does nothing.");
			Names = cfg.Bind<string>("Unlocks", "Names", "Eitr:Giant brain", "What the screen calls one deposit of an Unlocks entry, as Name:Text pairs, comma separated. It shows on the vein bar (\"Giant brain 45%\") and, with an s added, in the messages (\"Giant brains open to you now\"). An entry not listed is called after its metal as the game names it, plus vein: \"Copper vein\".");
			Bosses = cfg.Bind<string>("Unlocks", "Bosses", "Stone:defeated_eikthyr, Copper:defeated_gdking, Iron:defeated_bonemass, Silver:defeated_dragon, Obsidian:defeated_dragon, Eitr:defeated_queen, Flametal:defeated_fader, Gold:defeated_fader", "The boss each Unlocks entry also waits for, as Name:bosskey pairs, comma separated. An entry opens when BOTH hold: your Pickaxes level has reached its Unlocks level, and you have beaten its boss (see BossKills for how that is counted, with Vandi and without it). The boss is the one whose biome the ore comes from: Eikthyr for stone, the Elder for copper, Bonemass for iron, Moder for silver, the Queen for the giant brains (Eitr), Fader for flametal. Gold, the Deep North's bloodgold, is on Fader too until the Deep North's own boss has been read from a world: the log lists every boss in the world with its key when a world loads. The key is the boss's defeat key, the one the game sets when it dies. Names match Unlocks the same way, so Flametal covers both flametals. An entry not in this list needs no boss, only the level. A pair whose name is not in Unlocks does nothing. The default's Obsidian pair is one. Add Obsidian to Unlocks and the pair is read, so its entry waits for Moder, but in 1.0 obsidian rocks are single rocks that Malmr does not touch, so Moder's kill opens nothing for it but a message. With Vandi installed, only a boss in Vandi's BossBiomes can ever be met, and the log says so when a world loads.");
			BossKills = cfg.Bind<int>("Unlocks", "BossKills", 2, "How many times you must have killed an entry's boss. With Vandi installed it is Vandi's count: kills of a boss you summoned yourself at its altar. 2 is the one-star kill, because Vandi brings a boss back one star harder for every repeat kill, so your first kill is the boss as the game ships it and your second is the boss at one star. Vandi counts kills, not stars, so with its HarderBosses off the second kill is a plain boss and still counts. Without Vandi there are no stars to ask for, and this number is not used: one kill of the boss by this character, as the game itself counts kills, opens it. The game only started keeping that count per creature with its Call to Arms update, so a boss this character beat before then is not in it and has to be beaten again. On both, 0 switches the boss half off and every entry opens on the Pickaxes level alone.");
			VeinToggleKey = cfg.Bind<KeyCode>("Controls", "VeinToggleKey", (KeyCode)308, "Tap this with a pickaxe out to switch vein mining on, and tap it again to switch it off. It stays on across swings, tool changes and deposits until you tap it again; a small gold Vein above the crosshair says it is on while a pickaxe is out. While it is on, your pickaxe blows on a deposit of an open metal fill a bar instead of breaking chunks, and when the bar is full the whole deposit breaks at once. A tap is a short press and release of this key on its own, with the game in front the whole time: holding it, pressing any other key during it (E on a boar, say), or Alt+Tab, out of the game or back into it, does not switch anything. With any other tool in hand the key does nothing here. None switches the key off.");
			AnnounceUnlocks = cfg.Bind<bool>("Display", "AnnounceUnlocks", true, "Say so in the middle of the screen when a metal's veins open for you, whichever of the two came last: the Pickaxes level-up or the boss kill. Once per opening. A metal already open when you log in is not announced again. Without it the only way to find out is to switch vein mining on and swing, or to type malmr in the console.");
			Verbose = cfg.Bind<bool>("Display", "Verbose", false, "Write a line to BepInEx/LogOutput.log for every vein mining blow this machine handles as the deposit's owner (what it added and where the deposit stands), for every deposit that breaks whole, and for every blow this machine sends. The table of every deposit and what it resolved to is written once per world whatever this says.");
		}

		internal static Dictionary<string, int> UnlockTable()
		{
			string text = Unlocks.Value ?? "";
			if (_unlocks != null && text == _unlocksRaw)
			{
				return _unlocks;
			}
			_unlocksRaw = text;
			_unlocks = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, string> item in Pairs(text))
			{
				if (!int.TryParse(item.Value, out var result))
				{
					MalmrPlugin.Log.LogWarning((object)("Unlocks: '" + item.Key + ":" + item.Value + "' has no whole-number level, so " + item.Key + " is left out."));
				}
				else
				{
					_unlocks[item.Key] = result;
				}
			}
			Changed();
			return _unlocks;
		}

		internal static string MatchEntry(string name)
		{
			if (string.IsNullOrEmpty(name))
			{
				return null;
			}
			Dictionary<string, int> dictionary = UnlockTable();
			if (dictionary.ContainsKey(name))
			{
				return Canonical(dictionary, name);
			}
			string b = WithoutNew(name);
			foreach (string key in dictionary.Keys)
			{
				if (!(key == "*") && string.Equals(WithoutNew(key), b, StringComparison.OrdinalIgnoreCase))
				{
					return key;
				}
			}
			return null;
		}

		internal static int LevelFor(string entry)
		{
			if (entry == null)
			{
				return -1;
			}
			if (!UnlockTable().TryGetValue(entry, out var value))
			{
				return -1;
			}
			return value;
		}

		internal static string DepositOverride(string prefab)
		{
			string text = Deposits.Value ?? "";
			if (_deposits == null || text != _depositsRaw)
			{
				_depositsRaw = text;
				_deposits = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, string> item in Pairs(text))
				{
					_deposits[item.Key] = item.Value;
				}
				Changed();
			}
			if (!_deposits.TryGetValue(prefab ?? "", out var value))
			{
				return null;
			}
			return value;
		}

		internal static string BossFor(string entry)
		{
			if (string.IsNullOrEmpty(entry) || BossKills.Value <= 0)
			{
				return null;
			}
			Dictionary<string, string> dictionary = BossTable();
			if (dictionary.TryGetValue(entry, out var value))
			{
				return value;
			}
			if (entry == "*")
			{
				return null;
			}
			string b = WithoutNew(entry);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				if (!(item.Key == "*") && string.Equals(WithoutNew(item.Key), b, StringComparison.OrdinalIgnoreCase))
				{
					return item.Value;
				}
			}
			return null;
		}

		internal static Dictionary<string, string> BossTable()
		{
			string text = Bosses.Value ?? "";
			if (_bosses != null && text == _bossesRaw)
			{
				return _bosses;
			}
			_bossesRaw = text;
			_bosses = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
			foreach (KeyValuePair<string, string> item in Pairs(text))
			{
				_bosses[item.Key] = item.Value.ToLowerInvariant();
			}
			Changed();
			return _bosses;
		}

		private static void Changed()
		{
			Revision++;
		}

		internal static bool MistlandsAllows(string entry)
		{
			string text = Mistlands.Value ?? "";
			if (_mistlands == null || text != _mistlandsRaw)
			{
				_mistlandsRaw = text;
				_mistlands = new List<string>();
				string[] array = text.Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text2 = array[i].Trim();
					if (text2.Length > 0)
					{
						_mistlands.Add(text2);
					}
				}
			}
			if (string.IsNullOrEmpty(entry))
			{
				return false;
			}
			foreach (string mistland in _mistlands)
			{
				if (mistland == "*")
				{
					return true;
				}
				if (string.Equals(mistland, entry, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
				if (string.Equals(WithoutNew(mistland), WithoutNew(entry), StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		internal static string MistlandsText()
		{
			string text = (Mistlands.Value ?? "").Trim();
			if (text.Length != 0)
			{
				return text.Replace(" ", "");
			}
			return "(none)";
		}

		internal static string NameFor(string entry)
		{
			string text = Names.Value ?? "";
			if (_names == null || text != _namesRaw)
			{
				_namesRaw = text;
				_names = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
				foreach (KeyValuePair<string, string> item in Pairs(text))
				{
					_names[item.Key] = item.Value;
				}
			}
			if (string.IsNullOrEmpty(entry))
			{
				return null;
			}
			if (_names.TryGetValue(entry, out var value))
			{
				return value;
			}
			string b = WithoutNew(entry);
			foreach (KeyValuePair<string, string> name in _names)
			{
				if (string.Equals(WithoutNew(name.Key), b, StringComparison.OrdinalIgnoreCase))
				{
					return name.Value;
				}
			}
			return null;
		}

		private static IEnumerable<KeyValuePair<string, string>> Pairs(string raw)
		{
			string[] array = raw.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0)
				{
					int num = text.LastIndexOf(':');
					if (num <= 0 || num == text.Length - 1)
					{
						MalmrPlugin.Log.LogWarning((object)("Skipped '" + text + "' - it needs to be Name:Value."));
					}
					else
					{
						yield return new KeyValuePair<string, string>(text.Substring(0, num).Trim(), text.Substring(num + 1).Trim());
					}
				}
			}
		}

		private static string WithoutNew(string name)
		{
			if (!name.EndsWith("New", StringComparison.OrdinalIgnoreCase) || name.Length <= 3)
			{
				return name;
			}
			return name.Substring(0, name.Length - 3);
		}

		private static string Canonical(Dictionary<string, int> table, string name)
		{
			foreach (string key in table.Keys)
			{
				if (string.Equals(key, name, StringComparison.OrdinalIgnoreCase))
				{
					return key;
				}
			}
			return name;
		}
	}
	internal static class DamagePatches
	{
		[HarmonyPrefix]
		[HarmonyPatch(typeof(MineRock5), "Damage")]
		private static bool Vein5(MineRock5 __instance, HitData hit)
		{
			return Vein.Intercept((Component)(object)__instance, hit);
		}

		[HarmonyPrefix]
		[HarmonyPatch(typeof(MineRock), "Damage")]
		private static bool VeinSmall(MineRock __instance, HitData hit)
		{
			return Vein.Intercept((Component)(object)__instance, hit);
		}
	}
	internal static class ListenPatches
	{
		private static bool _warned;

		[HarmonyPostfix]
		[HarmonyPatch(typeof(MineRock5), "Awake")]
		private static void Vein5(MineRock5 __instance)
		{
			Guard("MineRock5.Awake", (Component)(object)__instance);
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(MineRock), "Start")]
		private static void VeinSmall(MineRock __instance)
		{
			Guard("MineRock.Start", (Component)(object)__instance);
		}

		private static void Guard(string where, Component rock)
		{
			try
			{
				Owner.Listen(rock);
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					MalmrPlugin.Log.LogWarning((object)("Could not register vein mining on a deposit in " + where + ", so this machine cannot fill the bar of a deposit it owns. Said once per session: " + ex));
				}
			}
		}
	}
	internal static class Announce
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(Player), "OnSkillLevelup")]
		private static void LevelUp(Player __instance, SkillType skill)
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Invalid comparison between Unknown and I4
			if (!((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && (int)skill == 12)
			{
				Opened.Check();
			}
		}
	}
	[BepInPlugin("ezomic.valheim.malmr", "Malmr", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class MalmrPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ezomic.valheim.malmr";

		public const string PluginName = "Malmr";

		public const string PluginVersion = "1.0.0";

		public const string PluginAuthor = "Robbin Thijssen";

		private const string CoreGuid = "ezomic.valheim.core";

		private const string VandiGuid = "ezomic.valheim.vandi";

		internal static ManualLogSource Log;

		internal static bool CorePresent;

		internal static bool VandiPresent;

		private Harmony _harmony;

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

		private void Awake()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			MalmrConfig.Bind(((BaseUnityPlugin)this).Config);
			TryRegisterWithCore();
			VandiPresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.vandi");
			Log.LogInfo((object)(VandiPresent ? "Vandi is installed, so a metal's boss counts through Vandi: kills of a boss you summoned yourself, BossKills of them, 2 by default, which is the one-star kill." : "Vandi is not installed, so a metal's boss counts once this character has killed it, as the game's own kill tally has it. There are no stars without Vandi, so BossKills is not used beyond 0 switching the boss half off."));
			_harmony = new Harmony("ezomic.valheim.malmr");
			Apply("deposit hit", typeof(DamagePatches));
			Apply("deposit message", typeof(ListenPatches));
			Apply("unlock message", typeof(Announce));
			Apply("console command", typeof(DevConsole.Hook));
			Log.LogInfo((object)"Malmr 1.0.0 by Robbin Thijssen - ready.");
		}

		private void Update()
		{
			Step("deposit survey", Deposits.SurveyTick);
			Step("unlock message", Opened.Tick);
			Step("vein mode key", VeinMode.Tick);
			Step("deposit break", Collapse.Tick);
			Step("vein bar", Focus.Tick);
		}

		private void OnGUI()
		{
			Step("vein bar drawing", Focus.Draw);
		}

		private void OnApplicationFocus(bool focused)
		{
			VeinMode.FocusChanged();
		}

		private static void Step(string what, Action step)
		{
			try
			{
				step();
			}
			catch (Exception ex)
			{
				if (Failed.Add(what))
				{
					Log.LogWarning((object)("The " + what + " failed and will keep failing quietly for the rest of this session. Said once: " + ex));
				}
			}
		}

		private void Apply(string what, Type patches)
		{
			try
			{
				_harmony.PatchAll(patches);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Could not apply the " + what + " patches, so that part of Malmr is off for this session. The rest is unaffected. " + ex.Message));
			}
		}

		private void TryRegisterWithCore()
		{
			CorePresent = Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core");
			if (!CorePresent)
			{
				Log.LogInfo((object)"Core not installed - running standalone, without the version gate. In multiplayer every player and the server need Malmr, or vein mining blows on a deposit owned by a machine without it are lost.");
			}
			else
			{
				RegisterWithCore();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void RegisterWithCore()
		{
			Suite.Register("ezomic.valheim.malmr", "Malmr", "1.0.0", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null);
			Suite.Sync((ConfigEntryBase[])(object)new ConfigEntryBase[7]
			{
				(ConfigEntryBase)MalmrConfig.Enabled,
				(ConfigEntryBase)MalmrConfig.Unlocks,
				(ConfigEntryBase)MalmrConfig.Deposits,
				(ConfigEntryBase)MalmrConfig.Mistlands,
				(ConfigEntryBase)MalmrConfig.Names,
				(ConfigEntryBase)MalmrConfig.Bosses,
				(ConfigEntryBase)MalmrConfig.BossKills
			});
			Suite.Local((ConfigEntryBase[])(object)new ConfigEntryBase[3]
			{
				(ConfigEntryBase)MalmrConfig.VeinToggleKey,
				(ConfigEntryBase)MalmrConfig.AnnounceUnlocks,
				(ConfigEntryBase)MalmrConfig.Verbose
			});
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class Opened
	{
		private const float Interval = 1f;

		private static float _next;

		private static long _player;

		private static ZNetScene _scene;

		private static int _revision = -1;

		private static int _bossKills = -1;

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

		private static bool _failed;

		internal static void Tick()
		{
			if (!(Time.time < _next))
			{
				_next = Time.time + 1f;
				Check();
			}
		}

		internal static void Check()
		{
			try
			{
				Look();
			}
			catch (Exception ex)
			{
				if (!_failed)
				{
					_failed = true;
					MalmrPlugin.Log.LogWarning((object)("Could not check which metals are open, so the unlock message may not come. Swinging is unaffected. Said once per session: " + ex));
				}
			}
		}

		private static void Look()
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ZoneSystem.instance == (Object)null)
			{
				return;
			}
			long playerID = localPlayer.GetPlayerID();
			if (playerID == 0L)
			{
				return;
			}
			float level = Vein.EarnedLevel(localPlayer);
			List<Gate> list = new List<Gate>();
			foreach (string key in MalmrConfig.UnlockTable().Keys)
			{
				if (!(key == "*"))
				{
					list.Add(Gate.For(key, level));
				}
			}
			bool flag = playerID != _player || (Object)(object)ZNetScene.instance != (Object)(object)_scene || MalmrConfig.Revision != _revision || MalmrConfig.BossKills.Value != _bossKills;
			List<Gate> list2 = new List<Gate>();
			foreach (Gate item in list)
			{
				if (!flag && Was.TryGetValue(item.Entry, out var value) && !value && item.Open)
				{
					list2.Add(item);
				}
			}
			Was.Clear();
			foreach (Gate item2 in list)
			{
				Was[item2.Entry] = item2.Open;
			}
			_player = playerID;
			_scene = ZNetScene.instance;
			_revision = MalmrConfig.Revision;
			_bossKills = MalmrConfig.BossKills.Value;
			if (list2.Count == 0)
			{
				return;
			}
			foreach (Gate item3 in list2)
			{
				MalmrPlugin.Log.LogInfo((object)(item3.Entry + " veins open: Pickaxes " + (int)item3.Level + " of " + item3.Unlock + ((item3.Boss != null) ? (", " + item3.Boss + " killed " + item3.Kills + " of " + item3.KillsNeeded + " time(s) " + (Bosses.ThroughVandi ? "through Vandi" : "as the game counts it")) : ", no boss needed") + "."));
			}
			if (MalmrConfig.Enabled.Value && MalmrConfig.AnnounceUnlocks.Value)
			{
				((Character)localPlayer).Message((MessageType)2, Message(list2), 0, (Sprite)null, false);
			}
		}

		private static string Message(List<Gate> opened)
		{
			//IL_013d: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = new List<string>();
			Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
			foreach (Gate item in opened)
			{
				string text = item.Earned();
				if (!dictionary.TryGetValue(text, out var value))
				{
					value = (dictionary[text] = new List<string>());
					list.Add(text);
				}
				value.Add(Deposits.Nouns(item.Entry));
			}
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string item2 in list)
			{
				List<string> list3 = dictionary[item2];
				string value2 = ((list3.Count == 1) ? list3[0] : (string.Join(", ", list3.GetRange(0, list3.Count - 1).ToArray()) + " and " + list3[list3.Count - 1]));
				stringBuilder.Append(value2).Append(" open to you now: ").Append(item2)
					.Append(".\n");
			}
			stringBuilder.Append("Tap ").Append(KeyName(MalmrConfig.VeinToggleKey.Value)).Append(" with a pickaxe out to mine a whole deposit");
			return stringBuilder.ToString();
		}

		internal unsafe static string KeyName(KeyCode key)
		{
			string text = ((object)(*(KeyCode*)(&key))/*cast due to .constrained prefix*/).ToString();
			StringBuilder stringBuilder = new StringBuilder(text.Length + 4);
			for (int i = 0; i < text.Length; i++)
			{
				if (i > 0 && char.IsUpper(text[i]) && !char.IsUpper(text[i - 1]))
				{
					stringBuilder.Append(' ');
				}
				stringBuilder.Append(text[i]);
			}
			return stringBuilder.ToString();
		}
	}
	internal static class Owner
	{
		internal const string Rpc = "Malmr_Vein1";

		private static bool _duplicateSaid;

		private static bool _failed;

		internal static void Listen(Component rock)
		{
			ZNetView nview = default(ZNetView);
			if ((Object)(object)rock == (Object)null || !rock.TryGetComponent<ZNetView>(ref nview) || nview.GetZDO() == null)
			{
				return;
			}
			try
			{
				nview.Register<HitData, int>("Malmr_Vein1", (Action<long, HitData, int>)delegate(long sender, HitData hit, int area)
				{
					OnHit(rock, nview, sender, hit, area);
				});
			}
			catch (ArgumentException)
			{
				if (!_duplicateSaid)
				{
					_duplicateSaid = true;
					MalmrPlugin.Log.LogWarning((object)("Malmr_Vein1 was already registered on " + Deposits.PrefabName(rock) + "; kept the first. Said once per session."));
				}
			}
		}

		private static void OnHit(Component rock, ZNetView nview, long sender, HitData hit, int area)
		{
			try
			{
				Apply(rock, nview, sender, hit, area);
			}
			catch (Exception ex)
			{
				if (!_failed)
				{
					_failed = true;
					MalmrPlugin.Log.LogWarning((object)("A vein mining blow could not be counted and was lost. Said once per session: " + ex));
				}
			}
		}

		private static void Apply(Component rock, ZNetView nview, long sender, HitData hit, int area)
		{
			if ((Object)(object)rock == (Object)null || hit == null || (Object)(object)nview == (Object)null || !nview.IsValid() || !nview.IsOwner())
			{
				return;
			}
			if (!MalmrConfig.Enabled.Value)
			{
				nview.InvokeRPC((rock is MineRock5) ? "RPC_Damage" : "Hit", new object[2] { hit, area });
				return;
			}
			HitData template;
			bool num = Count(rock, nview, sender, hit, area, out template);
			Ward(rock, hit);
			if (num)
			{
				Collapse.Start(rock, nview, template);
			}
		}

		private static bool Count(Component rock, ZNetView nview, long sender, HitData hit, int area, out HitData template)
		{
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: 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_011e: Unknown result type (might be due to invalid IL or missing references)
			template = null;
			if (Collapse.Busy(nview))
			{
				return false;
			}
			Collider[] array = Deposits.Areas(rock, includeDead: true);
			if (area < 0 || area >= array.Length)
			{
				MalmrPlugin.Log.LogWarning((object)("A vein mining blow named chunk " + area + " of " + Deposits.PrefabName(rock) + ", which has " + array.Length + "."));
				return false;
			}
			Ledger.Reading reading = Ledger.Read(rock, nview);
			if (reading == null)
			{
				return false;
			}
			if (reading.Health[area] <= 0f)
			{
				return false;
			}
			template = hit.Clone();
			DamageModifier val = default(DamageModifier);
			hit.ApplyResistance(Ledger.Modifiers(rock), ref val);
			float totalDamage = hit.GetTotalDamage();
			Vector3 val2 = EffectPoint(rock, array[area], hit);
			if (!hit.CheckToolTier(Deposits.MinToolTier(rock), false))
			{
				if ((Object)(object)DamageText.instance != (Object)null)
				{
					DamageText.instance.ShowText((TextType)5, val2, 0f, false);
				}
				return false;
			}
			if ((Object)(object)DamageText.instance != (Object)null)
			{
				DamageText.instance.ShowText(val, val2, totalDamage, false);
			}
			if (totalDamage <= 0f)
			{
				return false;
			}
			Effects(rock, hit, val2);
			float num = reading.Progress + totalDamage;
			bool flag = num >= reading.Total;
			Ledger.SetProgress(nview, num, reading.Total);
			if (MalmrConfig.Verbose.Value)
			{
				MalmrPlugin.Log.LogInfo((object)(Deposits.PrefabName(rock) + ": +" + totalDamage.ToString("0.0") + " from peer " + sender + ", " + num.ToString("0.0") + " of " + reading.Total.ToString("0.0") + " across " + reading.Standing + " chunk(s)" + (reading.Stale ? ", the old bar dropped because the deposit grew back" : "") + (flag ? " - full, breaking it" : "")));
			}
			return flag;
		}

		private static void Ward(Component rock, HitData hit)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if (!((Object)(object)val == (Object)null) && val.m_triggerPrivateArea)
			{
				Character attacker = hit.GetAttacker();
				if (!((Object)(object)attacker == (Object)null))
				{
					PrivateArea.OnObjectDamaged(((Component)val).transform.position, attacker, false);
				}
			}
		}

		private static Vector3 EffectPoint(Component rock, Collider chunk, HitData hit)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			if ((Object)(object)val != (Object)null && val.m_hitEffectAreaCenter && (Object)(object)chunk != (Object)null)
			{
				Bounds bounds = chunk.bounds;
				return ((Bounds)(ref bounds)).center;
			}
			return hit.m_point;
		}

		private static void Effects(Component rock, HitData hit, Vector3 point)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Invalid comparison between Unknown and I4
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			Character attacker = hit.GetAttacker();
			ZDOID attacker2 = hit.m_attacker;
			MineRock5 val = (MineRock5)(object)((rock is MineRock5) ? rock : null);
			MineRock val2 = (MineRock)(object)((rock is MineRock) ? rock : null);
			if ((Object)(object)val != (Object)null)
			{
				val.m_hitEffect.Create(point, Quaternion.identity, (Transform)null, 1f, -1, attacker2);
			}
			if ((Object)(object)val2 != (Object)null)
			{
				val2.m_hitEffect.Create(point, Quaternion.identity, (Transform)null, 1f, -1, attacker2);
			}
			if ((int)hit.m_hitType != 20)
			{
				Player closestPlayer = Player.GetClosestPlayer(point, 10f);
				if ((Object)(object)closestPlayer != (Object)null)
				{
					((Character)closestPlayer).AddNoise(100f);
				}
			}
			if ((Object)(object)val2 != (Object)null && val2.m_onHit != null)
			{
				val2.m_onHit();
			}
			Player val3 = (Player)(object)((attacker is Player) ? attacker : null);
			if (!((Object)(object)val3 == (Object)null) && !((Object)(object)val3 != (Object)(object)Player.m_localPlayer) && !((Object)(object)Game.instance == (Object)null))
			{
				bool flag = ((Humanoid)val3).GetInventory().CheatedDamagingItemEquipped();
				if ((Object)(object)val2 != (Object)null)
				{
					flag &= !PlayerProfile.s_bypassCheatChecks;
				}
				Game.instance.IncrementPlayerStat((PlayerStatType)37, 1f, flag);
			}
		}
	}
	internal static class VandiBridge
	{
		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static int LocalBossKills(string bossKey)
		{
			return VandiApi.LocalBossKills(bossKey);
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		internal static bool CountsKillsOf(string bossKey)
		{
			return VandiApi.CountsKillsOf(bossKey);
		}
	}
	internal static class Vein
	{
		private static bool _warned;

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

		private static ZNetScene _toldIn;

		internal static bool Intercept(Component rock, HitData hit)
		{
			try
			{
				return !Divert(rock, hit);
			}
			catch (Exception ex)
			{
				if (!_warned)
				{
					_warned = true;
					MalmrPlugin.Log.LogWarning((object)("A vein mining blow could not be sent, and it went to the deposit the vanilla way. Said once per session: " + ex));
				}
				return true;
			}
		}

		private static bool Divert(Component rock, HitData hit)
		{
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Invalid comparison between Unknown and I4
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			if (!MalmrConfig.Enabled.Value || !VeinMode.On)
			{
				return false;
			}
			if ((Object)(object)rock == (Object)null || hit == null)
			{
				return false;
			}
			if ((Object)(object)hit.m_hitCollider == (Object)null || hit.m_radius > 0f)
			{
				return false;
			}
			if ((int)hit.m_skill != 12)
			{
				return false;
			}
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || hit.m_attacker != ((Character)localPlayer).GetZDOID())
			{
				return false;
			}
			ZNetView val = default(ZNetView);
			if (!rock.TryGetComponent<ZNetView>(ref val))
			{
				return false;
			}
			if (!val.IsValid())
			{
				return true;
			}
			Deposits.Kind kind = Deposits.Of(rock);
			if (kind == null || kind.Entry == null)
			{
				return false;
			}
			if (Deposits.HandOnly(rock, kind))
			{
				Tell(val, kind, Deposits.Nouns(kind.Metal) + " in the Mistlands are mined by hand", "is mined by hand in the Mistlands (the Mistlands line)");
				return false;
			}
			Gate gate = Gate.For(kind, EarnedLevel(localPlayer));
			if (!gate.Open)
			{
				string text = Deposits.Nouns((gate.Entry == kind.Entry) ? kind.Metal : gate.Entry);
				Tell(val, kind, gate.Off ? (text + " cannot be vein mined here") : (text + " need " + gate.Needs()), "is shut for vein mining: " + ((gate.Entry == kind.Entry) ? "" : (gate.Entry + ": ")) + gate.Why());
				return false;
			}
			int num = Array.IndexOf(Deposits.Areas(rock, includeDead: true), hit.m_hitCollider);
			if (num < 0)
			{
				return false;
			}
			Focus.Struck(rock, hit.m_point);
			string text2 = null;
			if (MalmrConfig.Verbose.Value)
			{
				text2 = "Sent a vein mining blow on " + Deposits.PrefabName(rock) + " (" + kind.Metal + ") chunk " + num + " to " + (val.IsOwner() ? "this machine" : ("peer " + val.GetZDO().GetOwner())) + ".";
			}
			val.InvokeRPC("Malmr_Vein1", new object[2] { hit, num });
			if (text2 != null)
			{
				MalmrPlugin.Log.LogInfo((object)text2);
			}
			return true;
		}

		private static void Tell(ZNetView nview, Deposits.Kind kind, string onScreen, string forLog)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_toldIn != (Object)(object)ZNetScene.instance)
			{
				_toldIn = ZNetScene.instance;
				Told.Clear();
			}
			if (!Told.Add(nview.GetZDO().m_uid))
			{
				return;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				((Character)localPlayer).Message((MessageType)1, onScreen, 0, (Sprite)null, false);
				if (MalmrConfig.Verbose.Value)
				{
					MalmrPlugin.Log.LogInfo((object)(kind.Metal + " " + forLog));
				}
			}
		}

		internal static float EarnedLevel(Player player)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Invalid comparison between Unknown and I4
			if ((Object)(object)player == (Object)null)
			{
				return 0f;
			}
			Skills skills = ((Character)player).GetSkills();
			if ((Object)(object)skills == (Object)null)
			{
				return 0f;
			}
			foreach (Skill skill in skills.GetSkillList())
			{
				if (skill != null && skill.m_info != null && (int)skill.m_info.m_skill == 12)
				{
					return Mathf.Floor(skill.m_level);
				}
			}
			return 0f;
		}
	}
	internal static class VeinBar
	{
		private static readonly Color Gold = Color32.op_Implicit(new Color32((byte)217, (byte)164, (byte)65, byte.MaxValue));

		private static readonly Color Border = Color32.op_Implicit(new Color32((byte)107, (byte)90, (byte)58, byte.MaxValue));

		private static readonly Color Back = Color.black;

		private static readonly Color White = Color.white;

		private const float BarWidth = 120f;

		private const float BarHeight = 8f;

		private const float TextSize = 12f;

		private const float Gap = 12f;

		private const float TextGap = 3f;

		private static Texture2D _gold;

		private static Texture2D _border;

		private static Texture2D _back;

		private static GUIStyle _marker;

		private static GUIStyle _label;

		private static float _builtFor = -1f;

		internal static void Draw(bool bar, string label, float fraction)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Invalid comparison between Unknown and I4
			//IL_00a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0167: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			if (Event.current == null || (int)Event.current.type != 7 || Hud.IsUserHidden() || InventoryGui.IsVisible() || Menu.IsVisible() || Minimap.IsOpen() || Console.IsVisible())
			{
				return;
			}
			float num = (float)Screen.height / 1080f;
			Build(num);
			float num2 = (float)Screen.width * 0.5f;
			float num3 = (float)Screen.height * 0.5f;
			float num4 = Mathf.Ceil((float)_marker.fontSize * 1.4f);
			float num5 = 400f * Mathf.Max(1f, num);
			Text(new Rect(num2 - num5 * 0.5f, num3 - 12f * num - num4, num5, num4), "Vein", _marker);
			if (bar)
			{
				float num6 = Mathf.Round(120f * num);
				float num7 = Mathf.Max(3f, Mathf.Round(8f * num));
				float num8 = Mathf.Max(1f, Mathf.Round(num));
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(Mathf.Round(num2 - num6 * 0.5f), Mathf.Round(num3 + 12f * num), num6, num7);
				Rect val2 = default(Rect);
				((Rect)(ref val2))..ctor(((Rect)(ref val)).x + num8, ((Rect)(ref val)).y + num8, ((Rect)(ref val)).width - num8 * 2f, ((Rect)(ref val)).height - num8 * 2f);
				GUI.DrawTexture(val, (Texture)(object)_border);
				GUI.DrawTexture(val2, (Texture)(object)_back);
				float num9 = Mathf.Round(((Rect)(ref val2)).width * Mathf.Clamp01(fraction));
				if (num9 > 0f)
				{
					GUI.DrawTexture(new Rect(((Rect)(ref val2)).x, ((Rect)(ref val2)).y, num9, ((Rect)(ref val2)).height), (Texture)(object)_gold);
				}
				Text(new Rect(num2 - num5 * 0.5f, ((Rect)(ref val)).yMax + 3f * num, num5, num4), label, _label);
			}
		}

		private static void Text(Rect area, string text, GUIStyle style)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			GUI.Label(area, text, style);
		}

		private static void Build(float scale)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ce: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Expected O, but got Unknown
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Expected O, but got Unknown
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Expected O, but got Unknown
			//IL_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0120: Unknown result type (might be due to invalid IL or missing references)
			//IL_0126: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			bool flag = (Object)(object)_gold != (Object)null && (Object)(object)_border != (Object)null && (Object)(object)_back != (Object)null;
			if (!(_marker != null && flag) || !Mathf.Approximately(_builtFor, scale))
			{
				_builtFor = scale;
				if ((Object)(object)_gold == (Object)null)
				{
					_gold = Solid(Gold);
				}
				if ((Object)(object)_border == (Object)null)
				{
					_border = Solid(Border);
				}
				if ((Object)(object)_back == (Object)null)
				{
					_back = Solid(Back);
				}
				int fontSize = Mathf.Max(12, Mathf.RoundToInt(12f * scale));
				GUIStyle val = new GUIStyle(GUI.skin.label)
				{
					fontSize = fontSize,
					fontStyle = (FontStyle)0,
					alignment = (TextAnchor)4,
					wordWrap = false,
					clipping = (TextClipping)0,
					padding = new RectOffset(0, 0, 0, 0),
					margin = new RectOffset(0, 0, 0, 0)
				};
				val.normal.textColor = Gold;
				_marker = val;
				GUIStyle val2 = new GUIStyle(_marker);
				val2.normal.textColor = White;
				_label = val2;
			}
		}

		private static Texture2D Solid(Color colour)
		{
			//IL_0002: 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_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			Texture2D val = new Texture2D(1, 1);
			val.SetPixel(0, 0, colour);
			val.Apply();
			((Object)val).hideFlags = (HideFlags)61;
			return val;
		}
	}
	internal static class VeinMode
	{
		private const float TapSeconds = 0.5f;

		private const float FocusSettleSeconds = 0.15f;

		private const int FocusSettleFrames = 3;

		private static bool _armed;

		private static float _downAt;

		private static int _downFocusChanges;

		private static bool _pending;

		private static float _upA