plugins/Yoke/Yoke.dll

Decompiled 2 days ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
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 Ezomic.Shared;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using Utangard;

[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("Bigger stacks, without unbalancing the game.")]
[assembly: AssemblyFileVersion("1.2.2.0")]
[assembly: AssemblyInformationalVersion("1.2.2+629f40e89d1116f6ffa2a950d1817762ea09819f")]
[assembly: AssemblyProduct("Yoke")]
[assembly: AssemblyTitle("Yoke")]
[assembly: AssemblyVersion("1.2.2.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 Ezomic.Shared
{
	public static class BiomeIndex
	{
		private struct Placement
		{
			public int Index;

			public int Confidence;
		}

		private static ManualLogSource _log;

		public static Func<string, string> BiomeForKey;

		public static Func<string> Overrides;

		public static string OverrideSetting = "BiomeOverrides";

		public const string None = "none";

		public const string Everything = "all";

		private static readonly KeyValuePair<string, Biome>[] Ordered = new KeyValuePair<string, Biome>[9]
		{
			new KeyValuePair<string, Biome>("meadows", (Biome)1),
			new KeyValuePair<string, Biome>("blackforest", (Biome)8),
			new KeyValuePair<string, Biome>("ocean", (Biome)256),
			new KeyValuePair<string, Biome>("swamp", (Biome)2),
			new KeyValuePair<string, Biome>("mountain", (Biome)4),
			new KeyValuePair<string, Biome>("plains", (Biome)16),
			new KeyValuePair<string, Biome>("mistlands", (Biome)512),
			new KeyValuePair<string, Biome>("ashlands", (Biome)32),
			new KeyValuePair<string, Biome>("deepnorth", (Biome)64)
		};

		public static readonly string[] All = BuildNames();

		private const int Guessed = 0;

		private const int Derived = 1;

		private const int Pinned = 2;

		private static Dictionary<string, Placement> _biomeOf;

		private static bool _complete;

		private const int MaxClosurePasses = 12;

		private const int MaxFallbackRounds = 4;

		public static ManualLogSource Log
		{
			get
			{
				return _log ?? (_log = Logger.CreateLogSource("BiomeIndex"));
			}
			set
			{
				_log = value;
			}
		}

		public static bool Complete => _complete;

		public static int Count
		{
			get
			{
				if (_biomeOf != null)
				{
					return _biomeOf.Count;
				}
				return 0;
			}
		}

		private static string BossBiome(string globalKey)
		{
			return BiomeForKey?.Invoke(globalKey);
		}

		private static string[] BuildNames()
		{
			string[] array = new string[Ordered.Length + 1];
			for (int i = 0; i < Ordered.Length; i++)
			{
				array[i] = Ordered[i].Key;
			}
			array[Ordered.Length] = "none";
			return array;
		}

		public static void Invalidate()
		{
			_biomeOf = null;
			_complete = false;
		}

		public static string BiomeOf(string prefabName)
		{
			if (_biomeOf != null && _biomeOf.TryGetValue(prefabName, out var value))
			{
				return Ordered[value.Index].Key;
			}
			return "none";
		}

		public static void Prepare()
		{
			Build();
		}

		private static void Build()
		{
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			if (_complete && _biomeOf != null)
			{
				return;
			}
			ZoneSystem instance = ZoneSystem.instance;
			SpawnSystem val = Object.FindObjectOfType<SpawnSystem>();
			ZNetScene instance2 = ZNetScene.instance;
			Dictionary<string, Placement> dictionary = new Dictionary<string, Placement>();
			if ((Object)(object)instance != (Object)null && instance.m_vegetation != null)
			{
				foreach (ZoneVegetation item in instance.m_vegetation)
				{
					if (item != null && item.m_enable)
					{
						Harvest(dictionary, item.m_prefab, Earliest(item.m_biome));
					}
				}
			}
			if ((Object)(object)val != (Object)null)
			{
				HarvestSpawnLists(dictionary, val);
			}
			if ((Object)(object)instance2 != (Object)null && instance2.m_prefabs != null)
			{
				Bosses(dictionary, instance2);
			}
			ApplyOverrides(dictionary);
			List<string> guesses = new List<string>();
			for (int i = 0; i < 4; i++)
			{
				int deferred = 0;
				for (int j = 0; j < 12; j++)
				{
					bool flag = Craft(dictionary, permissive: false, out deferred, null);
					if ((Object)(object)instance2 != (Object)null && instance2.m_prefabs != null)
					{
						flag |= Convert(dictionary, instance2);
					}
					if (!flag)
					{
						break;
					}
				}
				if (deferred == 0 || !Craft(dictionary, permissive: true, out var _, guesses))
				{
					break;
				}
			}
			ApplyOverrides(dictionary);
			_biomeOf = dictionary;
			_complete = (Object)(object)instance != (Object)null && (Object)(object)val != (Object)null && (Object)(object)instance2 != (Object)null;
			if (_complete)
			{
				Log.LogInfo((object)("Biome index built: " + dictionary.Count + " item(s) placed."));
				ReportGuesses(dictionary, guesses);
				return;
			}
			List<string> list = new List<string>();
			if ((Object)(object)instance == (Object)null)
			{
				list.Add("ZoneSystem");
			}
			if ((Object)(object)val == (Object)null)
			{
				list.Add("SpawnSystem");
			}
			if ((Object)(object)instance2 == (Object)null)
			{
				list.Add("ZNetScene");
			}
			Log.LogInfo((object)("Biome index partial: " + dictionary.Count + " item(s) placed, still waiting on " + string.Join(" and ", list.ToArray()) + "."));
		}

		private static void ReportGuesses(Dictionary<string, Placement> found, List<string> guesses)
		{
			if (guesses == null || guesses.Count == 0)
			{
				return;
			}
			List<string> list = new List<string>(guesses.Count);
			foreach (string guess in guesses)
			{
				if (found.TryGetValue(guess, out var value) && value.Confidence == 0)
				{
					list.Add(guess);
				}
			}
			if (list.Count != 0)
			{
				list.Sort(StringComparer.Ordinal);
				Log.LogWarning((object)("Biome index: " + list.Count + " item(s) classified from an INCOMPLETE ingredient list, because the closure could not place everything their recipe asks for. Their biome is a best guess and " + OverrideSetting + " is the cure: " + string.Join(", ", list.ToArray()) + "."));
			}
		}

		private static void HarvestSpawnLists(Dictionary<string, Placement> found, SpawnSystem spawn)
		{
			if (spawn.m_spawnLists == null)
			{
				return;
			}
			HashSet<string> hashSet = new HashSet<string>();
			foreach (SpawnSystemList spawnList in spawn.m_spawnLists)
			{
				if ((Object)(object)spawnList == (Object)null || spawnList.m_spawners == null)
				{
					continue;
				}
				foreach (SpawnData spawner in spawnList.m_spawners)
				{
					if (Runs(spawner) && !Conditional(spawner))
					{
						hashSet.Add(((Object)spawner.m_prefab).name);
					}
				}
			}
			foreach (SpawnSystemList spawnList2 in spawn.m_spawnLists)
			{
				if ((Object)(object)spawnList2 == (Object)null || spawnList2.m_spawners == null)
				{
					continue;
				}
				foreach (SpawnData spawner2 in spawnList2.m_spawners)
				{
					if (Runs(spawner2))
					{
						Harvest(found, spawner2.m_prefab, SpawnIndex(spawner2, hashSet));
					}
				}
			}
		}

		private static bool Runs(SpawnData row)
		{
			if (row != null && row.m_enabled && !row.m_devDisabled)
			{
				return (Object)(object)row.m_prefab != (Object)null;
			}
			return false;
		}

		private static bool Conditional(SpawnData row)
		{
			if (string.IsNullOrEmpty(row.m_requiredGlobalKey) && string.IsNullOrEmpty(row.m_requiredPersistentEvent))
			{
				if (row.m_requiredEnvironments != null)
				{
					return row.m_requiredEnvironments.Count > 0;
				}
				return false;
			}
			return true;
		}

		private static int SpawnIndex(SpawnData row, HashSet<string> unconditional)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			int num = Earliest(row.m_biome);
			if (num < 0)
			{
				return -1;
			}
			if (!string.IsNullOrEmpty(row.m_requiredPersistentEvent))
			{
				return -1;
			}
			if (!Conditional(row))
			{
				return num;
			}
			if (unconditional.Contains(((Object)row.m_prefab).name))
			{
				return -1;
			}
			string requiredGlobalKey = row.m_requiredGlobalKey;
			if (string.IsNullOrEmpty(requiredGlobalKey))
			{
				return num;
			}
			string text = BossBiome(requiredGlobalKey);
			if (text == null)
			{
				return num;
			}
			int num2 = IndexOf(text);
			if (num2 < 0)
			{
				return num;
			}
			if (num <= num2)
			{
				return num2;
			}
			return num;
		}

		private static void Harvest(Dictionary<string, Placement> found, GameObject prefab, int index)
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)prefab == (Object)null || index < 0)
			{
				return;
			}
			Pickable[] componentsInChildren = prefab.GetComponentsInChildren<Pickable>(true);
			foreach (Pickable val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null)
				{
					Record(found, val.m_itemPrefab, index, 1);
				}
			}
			PickableItem[] componentsInChildren2 = prefab.GetComponentsInChildren<PickableItem>(true);
			foreach (PickableItem val2 in componentsInChildren2)
			{
				if ((Object)(object)val2 == (Object)null)
				{
					continue;
				}
				if ((Object)(object)val2.m_itemPrefab != (Object)null)
				{
					Record(found, ((Component)val2.m_itemPrefab).gameObject, index, 1);
				}
				if (val2.m_randomItemPrefabs == null)
				{
					continue;
				}
				RandomItem[] randomItemPrefabs = val2.m_randomItemPrefabs;
				foreach (RandomItem val3 in randomItemPrefabs)
				{
					if ((Object)(object)val3.m_itemPrefab != (Object)null)
					{
						Record(found, ((Component)val3.m_itemPrefab).gameObject, index, 1);
					}
				}
			}
			MineRock5[] componentsInChildren3 = prefab.GetComponentsInChildren<MineRock5>(true);
			foreach (MineRock5 val4 in componentsInChildren3)
			{
				if ((Object)(object)val4 != (Object)null)
				{
					Record(found, val4.m_dropItems, index, 1);
				}
			}
			MineRock[] componentsInChildren4 = prefab.GetComponentsInChildren<MineRock>(true);
			foreach (MineRock val5 in componentsInChildren4)
			{
				if ((Object)(object)val5 != (Object)null)
				{
					Record(found, val5.m_dropItems, index, 1);
				}
			}
			DropOnDestroyed[] componentsInChildren5 = prefab.GetComponentsInChildren<DropOnDestroyed>(true);
			foreach (DropOnDestroyed val6 in componentsInChildren5)
			{
				if ((Object)(object)val6 != (Object)null)
				{
					Record(found, val6.m_dropWhenDestroyed, index, 1);
				}
			}
			LootSpawner[] componentsInChildren6 = prefab.GetComponentsInChildren<LootSpawner>(true);
			foreach (LootSpawner val7 in componentsInChildren6)
			{
				if ((Object)(object)val7 != (Object)null)
				{
					Record(found, val7.m_items, index, 1);
				}
			}
			Container[] componentsInChildren7 = prefab.GetComponentsInChildren<Container>(true);
			foreach (Container val8 in componentsInChildren7)
			{
				if ((Object)(object)val8 != (Object)null)
				{
					Record(found, val8.m_defaultItems, index, 1);
				}
			}
			CharacterDrop[] componentsInChildren8 = prefab.GetComponentsInChildren<CharacterDrop>(true);
			foreach (CharacterDrop drop in componentsInChildren8)
			{
				RecordDrops(found, drop, index);
			}
			SpawnArea[] componentsInChildren9 = prefab.GetComponentsInChildren<SpawnArea>(true);
			foreach (SpawnArea val9 in componentsInChildren9)
			{
				if ((Object)(object)val9 == (Object)null || val9.m_prefabs == null)
				{
					continue;
				}
				foreach (SpawnData prefab2 in val9.m_prefabs)
				{
					if (prefab2 != null && !((Object)(object)prefab2.m_prefab == (Object)null))
					{
						componentsInChildren8 = prefab2.m_prefab.GetComponentsInChildren<CharacterDrop>(true);
						foreach (CharacterDrop drop2 in componentsInChildren8)
						{
							RecordDrops(found, drop2, index);
						}
					}
				}
			}
			Plant[] componentsInChildren10 = prefab.GetComponentsInChildren<Plant>(true);
			foreach (Plant val10 in componentsInChildren10)
			{
				if ((Object)(object)val10 == (Object)null || val10.m_grownPrefabs == null)
				{
					continue;
				}
				GameObject[] grownPrefabs = val10.m_grownPrefabs;
				foreach (GameObject val11 in grownPrefabs)
				{
					if ((Object)(object)val11 == (Object)null)
					{
						continue;
					}
					componentsInChildren = val11.GetComponentsInChildren<Pickable>(true);
					foreach (Pickable val12 in componentsInChildren)
					{
						if ((Object)(object)val12 != (Object)null)
						{
							Record(found, val12.m_itemPrefab, index, 1);
						}
					}
					componentsInChildren8 = val11.GetComponentsInChildren<CharacterDrop>(true);
					foreach (CharacterDrop drop3 in componentsInChildren8)
					{
						RecordDrops(found, drop3, index);
					}
				}
			}
			TreeBase[] componentsInChildren11 = prefab.GetComponentsInChildren<TreeBase>(true);
			foreach (TreeBase val13 in componentsInChildren11)
			{
				if ((Object)(object)val13 == (Object)null)
				{
					continue;
				}
				Record(found, val13.m_dropWhenDestroyed, index, 1);
				if ((Object)(object)val13.m_stubPrefab != (Object)null)
				{
					componentsInChildren5 = val13.m_stubPrefab.GetComponentsInChildren<DropOnDestroyed>(true);
					foreach (DropOnDestroyed val14 in componentsInChildren5)
					{
						if ((Object)(object)val14 != (Object)null)
						{
							Record(found, val14.m_dropWhenDestroyed, index, 1);
						}
					}
				}
				HarvestLogChain(found, val13.m_logPrefab, index);
			}
		}

		private static void HarvestLogChain(Dictionary<string, Placement> found, GameObject log, int index)
		{
			if ((Object)(object)log == (Object)null)
			{
				return;
			}
			HashSet<GameObject> hashSet = new HashSet<GameObject>();
			Queue<GameObject> queue = new Queue<GameObject>();
			queue.Enqueue(log);
			while (queue.Count > 0)
			{
				GameObject val = queue.Dequeue();
				if ((Object)(object)val == (Object)null || !hashSet.Add(val))
				{
					continue;
				}
				TreeLog[] componentsInChildren = val.GetComponentsInChildren<TreeLog>(true);
				foreach (TreeLog val2 in componentsInChildren)
				{
					if (!((Object)(object)val2 == (Object)null))
					{
						Record(found, val2.m_dropWhenDestroyed, index, 1);
						if ((Object)(object)val2.m_subLogPrefab != (Object)null)
						{
							queue.Enqueue(val2.m_subLogPrefab);
						}
					}
				}
			}
		}

		private static void RecordDrops(Dictionary<string, Placement> found, CharacterDrop drop, int index)
		{
			if ((Object)(object)drop == (Object)null || drop.m_drops == null)
			{
				return;
			}
			foreach (Drop drop2 in drop.m_drops)
			{
				if (drop2 != null)
				{
					Record(found, drop2.m_prefab, index, 1);
				}
			}
		}

		private static bool Convert(Dictionary<string, Placement> found, ZNetScene scene)
		{
			bool flag = false;
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Smelter component = prefab.GetComponent<Smelter>();
				if ((Object)(object)component != (Object)null && component.m_conversion != null)
				{
					foreach (ItemConversion item in component.m_conversion)
					{
						flag |= Inherit(found, item.m_from, item.m_to);
					}
				}
				CookingStation component2 = prefab.GetComponent<CookingStation>();
				if ((Object)(object)component2 != (Object)null && component2.m_conversion != null)
				{
					foreach (ItemConversion item2 in component2.m_conversion)
					{
						flag |= Inherit(found, item2.m_from, item2.m_to);
					}
				}
				Fermenter component3 = prefab.GetComponent<Fermenter>();
				if (!((Object)(object)component3 != (Object)null) || component3.m_conversion == null)
				{
					continue;
				}
				foreach (ItemConversion item3 in component3.m_conversion)
				{
					flag |= Inherit(found, item3.m_from, item3.m_to);
				}
			}
			return flag;
		}

		private static void Bosses(Dictionary<string, Placement> found, ZNetScene scene)
		{
			foreach (GameObject prefab in scene.m_prefabs)
			{
				if ((Object)(object)prefab == (Object)null)
				{
					continue;
				}
				Character component = prefab.GetComponent<Character>();
				if ((Object)(object)component == (Object)null || string.IsNullOrEmpty(component.m_defeatSetGlobalKey))
				{
					continue;
				}
				string text = BossBiome(component.m_defeatSetGlobalKey);
				if (text == null)
				{
					continue;
				}
				int num = IndexOf(text);
				if (num >= 0)
				{
					CharacterDrop[] componentsInChildren = prefab.GetComponentsInChildren<CharacterDrop>(true);
					foreach (CharacterDrop drop in componentsInChildren)
					{
						RecordDrops(found, drop, num);
					}
				}
			}
		}

		private static int IndexOf(string biome)
		{
			for (int i = 0; i < Ordered.Length; i++)
			{
				if (Ordered[i].Key == biome)
				{
					return i;
				}
			}
			return -1;
		}

		private static bool Craft(Dictionary<string, Placement> found, bool permissive, out int deferred, List<string> guesses)
		{
			deferred = 0;
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_recipes == null)
			{
				return false;
			}
			bool result = false;
			foreach (Recipe recipe in instance.m_recipes)
			{
				if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null || recipe.m_resources == null || (Object)(object)((Component)recipe.m_item).gameObject == (Object)null)
				{
					continue;
				}
				int num = 0;
				int num2 = 0;
				int num3 = -1;
				int num4 = int.MaxValue;
				int num5 = 1;
				Requirement[] resources = recipe.m_resources;
				foreach (Requirement val in resources)
				{
					if (val == null || (Object)(object)val.m_resItem == (Object)null || (Object)(object)((Component)val.m_resItem).gameObject == (Object)null)
					{
						continue;
					}
					num++;
					if (found.TryGetValue(((Object)((Component)val.m_resItem).gameObject).name, out var value))
					{
						num2++;
						if (value.Index > num3)
						{
							num3 = value.Index;
						}
						if (value.Index < num4)
						{
							num4 = value.Index;
						}
						if (value.Confidence < num5)
						{
							num5 = value.Confidence;
						}
					}
				}
				if (num2 == 0)
				{
					continue;
				}
				bool requireOnlyOneIngredient = recipe.m_requireOnlyOneIngredient;
				int index = (requireOnlyOneIngredient ? num4 : num3);
				bool flag = false;
				if (!requireOnlyOneIngredient && num2 < num)
				{
					deferred++;
					if (!permissive)
					{
						continue;
					}
					num5 = 0;
					flag = true;
				}
				if (Record(found, ((Object)((Component)recipe.m_item).gameObject).name, index, num5))
				{
					result = true;
					if (flag && guesses != null && !guesses.Contains(((Object)((Component)recipe.m_item).gameObject).name))
					{
						guesses.Add(((Object)((Component)recipe.m_item).gameObject).name);
					}
				}
			}
			return result;
		}

		private static bool Inherit(Dictionary<string, Placement> found, ItemDrop from, ItemDrop to)
		{
			if ((Object)(object)from == (Object)null || (Object)(object)to == (Object)null || (Object)(object)((Component)from).gameObject == (Object)null || (Object)(object)((Component)to).gameObject == (Object)null)
			{
				return false;
			}
			if (!found.TryGetValue(((Object)((Component)from).gameObject).name, out var value))
			{
				return false;
			}
			int confidence = ((value.Confidence >= 1) ? 1 : value.Confidence);
			return Record(found, ((Object)((Component)to).gameObject).name, value.Index, confidence);
		}

		private static void ApplyOverrides(Dictionary<string, Placement> found)
		{
			string[] array = (Overrides?.Invoke() ?? "").Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0)
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { ':' });
				if (array2.Length != 2)
				{
					Log.LogWarning((object)("Ignoring biome override '" + text + "': expected prefab:biome."));
					continue;
				}
				string text2 = array2[1].Trim().ToLowerInvariant();
				int num = IndexOf(text2);
				if (num < 0)
				{
					Log.LogWarning((object)("Ignoring biome override '" + text + "': '" + text2 + "' is not a biome."));
				}
				else
				{
					found[array2[0].Trim()] = new Placement
					{
						Index = num,
						Confidence = 2
					};
				}
			}
		}

		private static void Record(Dictionary<string, Placement> found, DropTable table, int index, int confidence)
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: 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_0031: Unknown result type (might be due to invalid IL or missing references)
			if (table == null || table.m_drops == null)
			{
				return;
			}
			foreach (DropData drop in table.m_drops)
			{
				if ((Object)(object)drop.m_item != (Object)null)
				{
					Record(found, ((Object)drop.m_item).name, index, confidence);
				}
			}
		}

		private static void Record(Dictionary<string, Placement> found, GameObject prefab, int index, int confidence)
		{
			if ((Object)(object)prefab != (Object)null)
			{
				Record(found, ((Object)prefab).name, index, confidence);
			}
		}

		private static bool Record(Dictionary<string, Placement> found, string prefabName, int index, int confidence)
		{
			if (string.IsNullOrEmpty(prefabName) || index < 0 || index >= Ordered.Length)
			{
				return false;
			}
			if (found.TryGetValue(prefabName, out var value))
			{
				if (confidence < value.Confidence)
				{
					return false;
				}
				if (confidence == value.Confidence && index >= value.Index)
				{
					return false;
				}
			}
			found[prefabName] = new Placement
			{
				Index = index,
				Confidence = confidence
			};
			return true;
		}

		private static int Earliest(Biome mask)
		{
			//IL_0004: 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)
			for (int i = 0; i < Ordered.Length; i++)
			{
				if ((mask & Ordered[i].Value) != 0)
				{
					return i;
				}
			}
			return -1;
		}
	}
}
namespace Yoke
{
	internal static class DevConsole
	{
		private static bool _registered;

		[HarmonyPostfix]
		[HarmonyPatch(typeof(Terminal), "InitTerminal")]
		private static void Register()
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if (!_registered)
			{
				_registered = true;
				new ConsoleCommand("yoke", "item <prefab>: which biome Yoke files an item under, and its stack size now and in vanilla", new ConsoleEvent(OnCommand), false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			}
		}

		private static void OnCommand(ConsoleEventArgs args)
		{
			Terminal context = args.Context;
			if ((Object)(object)context == (Object)null)
			{
				return;
			}
			if (args.Length < 3 || args[1].ToLowerInvariant() != "item")
			{
				context.AddString("yoke item <prefab>, for example yoke item TrophyDeer. The prefab names are the first column of " + Path.GetFileName(YokeConfig.ItemListPath) + ".");
				return;
			}
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0)
			{
				context.AddString("yoke item: no item database yet, so load a world first");
				return;
			}
			GameObject itemPrefab = instance.GetItemPrefab(args[2]);
			if ((Object)(object)itemPrefab == (Object)null)
			{
				context.AddString("yoke item: no item called '" + args[2] + "'. Prefab names are case-sensitive.");
			}
			else
			{
				context.AddString(StackTuner.Explain(itemPrefab));
			}
		}
	}
	internal static class ItemDump
	{
		internal readonly struct Row
		{
			public readonly string Prefab;

			public readonly string Name;

			public readonly string Type;

			public readonly string Group;

			public readonly int OriginalStack;

			public readonly int Stack;

			public readonly float OriginalWeight;

			public readonly float Weight;

			public readonly string Note;

			public Row(string prefab, string name, string type, string group, int originalStack, int stack, float originalWeight, float weight, string note)
			{
				Prefab = prefab;
				Name = name;
				Type = type;
				Group = group;
				OriginalStack = originalStack;
				Stack = stack;
				OriginalWeight = originalWeight;
				Weight = weight;
				Note = note ?? "";
			}
		}

		internal const string Awaiting = "awaiting ";

		private static readonly string[] ReasonOrder = new string[5] { "equipment", "metal-held", "trophy", "excluded", "unguarded" };

		private static string _lastWritten;

		public static void Write(List<Row> rows)
		{
			if (!YokeConfig.WriteItemList.Value || rows == null || rows.Count == 0)
			{
				return;
			}
			string itemListPath = YokeConfig.ItemListPath;
			if (string.IsNullOrEmpty(itemListPath))
			{
				return;
			}
			string text = Build(rows);
			if (text == _lastWritten)
			{
				return;
			}
			try
			{
				File.WriteAllText(itemListPath, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				_lastWritten = text;
				YokePlugin.Log.LogInfo((object)("Wrote the item list to " + Path.GetFileName(itemListPath) + "."));
			}
			catch (Exception ex)
			{
				YokePlugin.Log.LogWarning((object)("Could not write the item list: " + ex.Message));
			}
		}

		private static string Build(List<Row> rows)
		{
			rows.Sort(delegate(Row a, Row b)
			{
				int num8 = Array.IndexOf(BiomeIndex.All, a.Group).CompareTo(Array.IndexOf(BiomeIndex.All, b.Group));
				return (num8 != 0) ? num8 : string.Compare(a.Prefab, b.Prefab, StringComparison.OrdinalIgnoreCase);
			});
			List<string[]> list = new List<string[]>(rows.Count);
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			Dictionary<string, int> dictionary = new Dictionary<string, int>();
			foreach (Row row in rows)
			{
				if (row.Note.StartsWith("awaiting ", StringComparison.Ordinal))
				{
					num3++;
				}
				else if (row.Note.Length > 0)
				{
					string key = Reason(row.Note);
					dictionary.TryGetValue(key, out var value);
					dictionary[key] = value + 1;
				}
				else if (row.Stack != row.OriginalStack || row.Weight != row.OriginalWeight)
				{
					num++;
				}
				else
				{
					num2++;
				}
				string[] obj = new string[7] { row.Prefab, row.Name, row.Type, row.Group, null, null, null };
				int originalStack = row.OriginalStack;
				string text = originalStack.ToString();
				originalStack = row.Stack;
				obj[4] = Range(text, originalStack.ToString());
				obj[5] = Range(Weight(row.OriginalWeight), Weight(row.Weight));
				obj[6] = row.Note;
				list.Add(obj);
			}
			string[] array = new string[7] { "Prefab", "Name", "Type", "Biome", "Stack", "Weight", "Status" };
			int[] array2 = new int[array.Length];
			for (int num4 = 0; num4 < array.Length; num4++)
			{
				array2[num4] = array[num4].Length;
			}
			foreach (string[] item in list)
			{
				for (int num5 = 0; num5 < item.Length; num5++)
				{
					if (item[num5].Length > array2[num5])
					{
						array2[num5] = item[num5].Length;
					}
				}
			}
			StringBuilder stringBuilder = new StringBuilder(rows.Count * 96);
			stringBuilder.AppendLine("Yoke 1.2.2 - every item in this session");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  Generated on every run, so edits here are overwritten. The settings are in");
			stringBuilder.AppendLine("  " + Path.GetFileName(YokeConfig.ConfigPath) + " beside this file.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  The first column is the prefab name, which is what ExcludeItems takes.");
			stringBuilder.AppendLine("  An arrow means Yoke changed that value. A single number means it did not,");
			stringBuilder.AppendLine("  and the last column says why: a rule that refused it, or the boss whose");
			stringBuilder.AppendLine("  death will raise that group.");
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  Earned so far: " + Progression.Describe());
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  Biome index: " + BiomeIndex.Count + " item(s) placed from the game's own tables" + (BiomeIndex.Complete ? "." : ", INCOMPLETE - no world loaded yet."));
			int num6 = 0;
			foreach (Row row2 in rows)
			{
				if (row2.Group == "none" && row2.OriginalStack > 1)
				{
					num6++;
				}
			}
			if (num6 > 0)
			{
				stringBuilder.AppendLine("  " + num6 + " stackable item(s) have no biome and stay at vanilla. They are the 'none' rows at the bottom; BiomeOverrides is how you place one.");
			}
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  Settings: StackMultiplier " + Number(YokeConfig.StackMultiplier.Value) + ", StackCap " + YokeConfig.StackCap.Value + ", WeightMultiplier " + Number(YokeConfig.WeightMultiplier.Value));
			stringBuilder.AppendLine("            IncludeNonTeleportable " + Flag(YokeConfig.IncludeNonTeleportable.Value) + ", IncludeTrophies " + Flag(YokeConfig.IncludeTrophies.Value));
			stringBuilder.AppendLine();
			stringBuilder.AppendLine("  " + rows.Count + " items: " + num + " raised above vanilla, " + num3 + " waiting on a boss, " + Total(dictionary) + " left alone by a rule, " + num2 + " unchanged");
			stringBuilder.AppendLine("  " + Breakdown(dictionary));
			stringBuilder.AppendLine();
			AppendRow(stringBuilder, array, array2);
			string[] array3 = new string[array.Length];
			for (int num7 = 0; num7 < array3.Length; num7++)
			{
				array3[num7] = new string('-', array2[num7]);
			}
			AppendRow(stringBuilder, array3, array2);
			foreach (string[] item2 in list)
			{
				AppendRow(stringBuilder, item2, array2);
			}
			return stringBuilder.ToString();
		}

		private static void AppendRow(StringBuilder sb, string[] cells, int[] widths)
		{
			StringBuilder stringBuilder = new StringBuilder();
			for (int i = 0; i < cells.Length; i++)
			{
				if (i == cells.Length - 1)
				{
					stringBuilder.Append(cells[i]);
				}
				else
				{
					stringBuilder.Append(cells[i].PadRight(widths[i])).Append("  ");
				}
			}
			sb.AppendLine(stringBuilder.ToString().TrimEnd(Array.Empty<char>()));
		}

		private static string Range(string from, string to)
		{
			if (!(from == to))
			{
				return from + " -> " + to;
			}
			return from;
		}

		private static string Weight(float value)
		{
			return value.ToString("0.##", CultureInfo.InvariantCulture);
		}

		private static string Flag(bool value)
		{
			if (!value)
			{
				return "false";
			}
			return "true";
		}

		private static string Number(float value)
		{
			return value.ToString(CultureInfo.InvariantCulture);
		}

		private static string Reason(string note)
		{
			string[] reasonOrder = ReasonOrder;
			foreach (string text in reasonOrder)
			{
				if (note.StartsWith(text, StringComparison.Ordinal))
				{
					return text;
				}
			}
			return note;
		}

		private static int Total(Dictionary<string, int> counts)
		{
			int num = 0;
			foreach (int value in counts.Values)
			{
				num += value;
			}
			return num;
		}

		private static string Breakdown(Dictionary<string, int> counts)
		{
			StringBuilder stringBuilder = new StringBuilder("(");
			for (int i = 0; i < ReasonOrder.Length; i++)
			{
				counts.TryGetValue(ReasonOrder[i], out var value);
				if (i > 0)
				{
					stringBuilder.Append(", ");
				}
				stringBuilder.Append(ReasonOrder[i]).Append(' ').Append(value);
			}
			return stringBuilder.Append(')').ToString();
		}
	}
	internal static class LiveItems
	{
		private readonly struct Tuned
		{
			public readonly int Stack;

			public readonly float Weight;

			public Tuned(int stack, float weight)
			{
				Stack = stack;
				Weight = weight;
			}
		}

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

		private static MethodInfo _updateTotalWeight;

		private static bool _weightRefreshWarned;

		public static void Forget()
		{
			Settled.Clear();
		}

		public static void Record(string prefabName, int stack, float weight)
		{
			Settled[prefabName] = new Tuned(stack, weight);
		}

		public static void SweepPlayer()
		{
			Player localPlayer = Player.m_localPlayer;
			if (!((Object)(object)localPlayer == (Object)null))
			{
				int num = Sweep(((Humanoid)localPlayer).GetInventory());
				if (num > 0)
				{
					YokePlugin.Log.LogInfo((object)("Updated " + num + " stack(s) already in your inventory."));
				}
			}
		}

		public static int Sweep(Inventory inventory)
		{
			if (inventory == null || Settled.Count == 0)
			{
				return 0;
			}
			List<ItemData> allItems = inventory.GetAllItems();
			if (allItems == null)
			{
				return 0;
			}
			Dictionary<SharedData, int> dictionary = Floors(allItems);
			int num = 0;
			bool flag = false;
			foreach (ItemData item in allItems)
			{
				if (item == null || item.m_shared == null)
				{
					continue;
				}
				GameObject dropPrefab = item.m_dropPrefab;
				if (!((Object)(object)dropPrefab == (Object)null) && Settled.TryGetValue(((Object)dropPrefab).name, out var value))
				{
					bool flag2 = false;
					int value2;
					int num2 = ((dictionary.TryGetValue(item.m_shared, out value2) && value2 > value.Stack) ? value2 : value.Stack);
					if (item.m_shared.m_maxStackSize != num2)
					{
						item.m_shared.m_maxStackSize = num2;
						flag2 = true;
					}
					if (!Mathf.Approximately(item.m_shared.m_weight, value.Weight))
					{
						item.m_shared.m_weight = value.Weight;
						flag = true;
						flag2 = true;
					}
					if (flag2)
					{
						num++;
					}
				}
			}
			if (flag)
			{
				RefreshWeight(inventory);
			}
			return num;
		}

		private static Dictionary<SharedData, int> Floors(List<ItemData> items)
		{
			Dictionary<SharedData, int> dictionary = new Dictionary<SharedData, int>();
			foreach (ItemData item in items)
			{
				if (item != null && item.m_shared != null && (!dictionary.TryGetValue(item.m_shared, out var value) || value < item.m_stack))
				{
					dictionary[item.m_shared] = item.m_stack;
				}
			}
			return dictionary;
		}

		private static void RefreshWeight(Inventory inventory)
		{
			try
			{
				if (_updateTotalWeight == null)
				{
					_updateTotalWeight = AccessTools.Method(typeof(Inventory), "UpdateTotalWeight", (Type[])null, (Type[])null);
				}
				if (_updateTotalWeight != null)
				{
					_updateTotalWeight.Invoke(inventory, null);
				}
			}
			catch (Exception ex)
			{
				if (!_weightRefreshWarned)
				{
					_weightRefreshWarned = true;
					YokePlugin.Log.LogWarning((object)("Could not refresh carried weight: " + ex.Message));
				}
			}
		}
	}
	internal static class LiveItemPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(InventoryGui), "Show", new Type[]
		{
			typeof(Container),
			typeof(int)
		})]
		private static void SweepOnOpen(Container container)
		{
			LiveItems.SweepPlayer();
			if (!((Object)(object)container == (Object)null))
			{
				LiveItems.Sweep(container.GetInventory());
			}
		}
	}
	[HarmonyPatch]
	internal static class LoadGuard
	{
		internal static bool Armed;

		[HarmonyPrepare]
		private static bool Prepare()
		{
			if (Target() != null)
			{
				return true;
			}
			YokePlugin.Log.LogError((object)"Yoke's LoadGuard could not find Inventory.AddItem(load path) - a game update has changed its parameter list. The guard is DISABLED, so stack sizes stay at vanilla to keep stored stacks safe. Yoke needs retargeting before it can raise stacks again.");
			return false;
		}

		private static MethodBase Target()
		{
			return AccessTools.Method(typeof(Inventory), "AddItem", new Type[14]
			{
				typeof(int),
				typeof(int),
				typeof(float),
				typeof(Vector2i),
				typeof(bool),
				typeof(int),
				typeof(int),
				typeof(long),
				typeof(string),
				typeof(Dictionary<string, string>),
				typeof(int),
				typeof(bool),
				typeof(bool),
				typeof(bool)
			}, (Type[])null);
		}

		[HarmonyTargetMethod]
		private static MethodBase Where()
		{
			return Target();
		}

		public static int KeepStored(int stored, int limit)
		{
			return stored;
		}

		[HarmonyTranspiler]
		private static IEnumerable<CodeInstruction> Unclamp(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo min = AccessTools.Method(typeof(Mathf), "Min", new Type[2]
			{
				typeof(int),
				typeof(int)
			}, (Type[])null);
			MethodInfo keep = AccessTools.Method(typeof(LoadGuard), "KeepStored", (Type[])null, (Type[])null);
			int replaced = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, min))
				{
					replaced++;
					yield return CodeInstructionExtensions.WithBlocks(CodeInstructionExtensions.WithLabels(new CodeInstruction(OpCodes.Call, (object)keep), (IEnumerable<Label>)instruction.labels), (IEnumerable<ExceptionBlock>)instruction.blocks);
				}
				else
				{
					yield return instruction;
				}
			}
			Armed = replaced == 1;
			if (!Armed)
			{
				YokePlugin.Log.LogError((object)("LoadGuard expected one Mathf.Min in Inventory.AddItem(load path) and replaced " + replaced + ". A game update likely moved the clamp - stack sizes stay at vanilla until this is looked at, so nothing stored can be clamped away."));
			}
		}
	}
	internal static class Progression
	{
		private struct Tier
		{
			public string BossKey;

			public string Group;

			public float Multiplier;
		}

		private static List<Tier> _tiers;

		private static string _parsedFrom;

		private static string _signature;

		private static bool _utangardChecked;

		private static bool _utangardPresent;

		private static string[] _order;

		private static string _orderFrom;

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

		private const string UtangardGuid = "ezomic.valheim.utangard";

		public static void Invalidate()
		{
			_tiers = null;
			_order = null;
			_signature = null;
			ForgetUngated();
		}

		public static bool Refresh()
		{
			string text = Signature();
			if (text == _signature)
			{
				return false;
			}
			_signature = text;
			return true;
		}

		public static float MultiplierFor(string group)
		{
			if (!HasTier(group))
			{
				Ungated(group);
			}
			float num = YokeConfig.ProgressionBase.Value;
			int num2 = -1;
			foreach (Tier item in Tiers())
			{
				if (Applies(item, group) && Earned(item.BossKey) && !(item.Multiplier <= num))
				{
					num = item.Multiplier;
					num2 = OrderIndex(item.BossKey);
				}
			}
			if (num2 < 0)
			{
				return num;
			}
			float value = YokeConfig.ProgressionStep.Value;
			if (value <= 0f)
			{
				return num;
			}
			string[] array = Order();
			for (int i = num2 + 1; i < array.Length; i++)
			{
				if (Earned(array[i]))
				{
					num *= 1f + value;
				}
			}
			return num;
		}

		private static int OrderIndex(string bossKey)
		{
			return Array.IndexOf(Order(), bossKey);
		}

		private static string[] Order()
		{
			string text = YokeConfig.ProgressionOrder.Value ?? "";
			if (_order != null && text == _orderFrom)
			{
				return _order;
			}
			string[] array = text.Split(new char[1] { ',' });
			List<string> list = new List<string>(array.Length);
			string[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				string text2 = array2[i].Trim().ToLowerInvariant();
				if (text2.Length > 0)
				{
					list.Add(text2);
				}
			}
			_orderFrom = text;
			_order = list.ToArray();
			return _order;
		}

		public static string PendingFor(string group)
		{
			float value = YokeConfig.ProgressionBase.Value;
			string text = null;
			foreach (Tier item in Tiers())
			{
				if (Applies(item, group) && !Earned(item.BossKey) && !(item.Multiplier <= value) && text == null)
				{
					text = item.BossKey;
				}
			}
			return text;
		}

		public static bool MetalTierEarned(string group)
		{
			if (!YokeConfig.ScaleWithProgression.Value)
			{
				return false;
			}
			foreach (Tier item in Tiers())
			{
				if (Applies(item, group) && Earned(item.BossKey))
				{
					return true;
				}
			}
			return false;
		}

		public static string BiomeFor(string bossKey)
		{
			if (string.IsNullOrEmpty(bossKey))
			{
				return null;
			}
			string text = bossKey.ToLowerInvariant();
			foreach (Tier item in Tiers())
			{
				if (item.BossKey == text)
				{
					return item.Group;
				}
			}
			return null;
		}

		private static bool HasTier(string group)
		{
			foreach (Tier item in Tiers())
			{
				if (Applies(item, group))
				{
					return true;
				}
			}
			return false;
		}

		private static void Ungated(string group)
		{
			if (!(group == "none") && _ungated.Add(group))
			{
				YokePlugin.Log.LogInfo((object)("No ProgressionTiers row names '" + group + "', so its items stay at the base multiplier however many bosses fall. Add a 'boss:" + group + ":2' row if they should grow."));
			}
		}

		private static void ForgetUngated()
		{
			_ungated.Clear();
		}

		private static bool Applies(Tier tier, string group)
		{
			if (!(tier.Group == group))
			{
				return tier.Group == "all";
			}
			return true;
		}

		private static bool Earned(string bossKey)
		{
			if (string.IsNullOrEmpty(bossKey))
			{
				return false;
			}
			if (UtangardPresent())
			{
				return EarnedThroughUtangard(bossKey);
			}
			ZoneSystem instance = ZoneSystem.instance;
			if ((Object)(object)instance != (Object)null)
			{
				return instance.GetGlobalKey(bossKey);
			}
			return false;
		}

		private static bool UtangardPresent()
		{
			if (!_utangardChecked)
			{
				_utangardChecked = true;
				_utangardPresent = YokeConfig.DeferToUtangard.Value && Chainloader.PluginInfos.ContainsKey("ezomic.valheim.utangard");
				if (_utangardPresent)
				{
					YokePlugin.Log.LogInfo((object)"Utangard is installed; stacks follow what the group has earned, not what the world has seen.");
				}
			}
			return _utangardPresent;
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private static bool EarnedThroughUtangard(string bossKey)
		{
			return UtangardApi.GroupHasKey(bossKey);
		}

		private static List<Tier> Tiers()
		{
			string text = YokeConfig.ProgressionTiers.Value ?? "";
			if (_tiers != null && text == _parsedFrom)
			{
				return _tiers;
			}
			_parsedFrom = text;
			_tiers = Parse(text);
			return _tiers;
		}

		private static List<Tier> Parse(string raw)
		{
			List<Tier> list = new List<Tier>();
			string[] array = raw.Split(new char[1] { ',' });
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length == 0)
				{
					continue;
				}
				string[] array2 = text.Split(new char[1] { ':' });
				if (array2.Length != 3)
				{
					YokePlugin.Log.LogWarning((object)("Ignoring tier '" + text + "': expected boss:group:multiplier."));
					continue;
				}
				if (!float.TryParse(array2[2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
				{
					YokePlugin.Log.LogWarning((object)("Ignoring tier '" + text + "': '" + array2[2].Trim() + "' is not a number."));
					continue;
				}
				string text2 = array2[1].Trim().ToLowerInvariant();
				if (Array.IndexOf(BiomeIndex.All, text2) < 0 && text2 != "all")
				{
					YokePlugin.Log.LogWarning((object)("Ignoring tier '" + text + "': '" + text2 + "' is not one of " + string.Join(", ", BiomeIndex.All) + ", all."));
				}
				else
				{
					list.Add(new Tier
					{
						BossKey = array2[0].Trim().ToLowerInvariant(),
						Group = text2,
						Multiplier = result
					});
				}
			}
			return list;
		}

		private static string Signature()
		{
			if (!YokeConfig.ScaleWithProgression.Value)
			{
				return "flat:" + YokeConfig.StackMultiplier.Value.ToString(CultureInfo.InvariantCulture);
			}
			StringBuilder stringBuilder = new StringBuilder();
			string[] all = BiomeIndex.All;
			foreach (string text in all)
			{
				stringBuilder.Append(text).Append(':').Append(MultiplierFor(text).ToString(CultureInfo.InvariantCulture))
					.Append('|');
			}
			return stringBuilder.ToString();
		}

		public static string Describe()
		{
			if (!YokeConfig.ScaleWithProgression.Value)
			{
				return "off - one flat multiplier";
			}
			StringBuilder stringBuilder = new StringBuilder();
			string[] all = BiomeIndex.All;
			foreach (string text in all)
			{
				if (HasTier(text))
				{
					if (stringBuilder.Length > 0)
					{
						stringBuilder.Append(", ");
					}
					stringBuilder.Append(text).Append(' ').Append(MultiplierFor(text).ToString("0.##", CultureInfo.InvariantCulture))
						.Append('x');
				}
			}
			if (stringBuilder.Length != 0)
			{
				return stringBuilder.ToString();
			}
			return "nothing - no tiers are configured";
		}
	}
	internal static class StackTuner
	{
		private readonly struct Original
		{
			public readonly int Stack;

			public readonly float Weight;

			public Original(int stack, float weight)
			{
				Stack = stack;
				Weight = weight;
			}
		}

		internal const string Excluded = "excluded";

		internal const string Equipment = "equipment";

		internal const string MetalHeld = "metal-held";

		internal const string Trophy = "trophy";

		internal const string Unguarded = "unguarded";

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

		public static void Rebuild()
		{
			BiomeIndex.Invalidate();
			Progression.Invalidate();
			Apply();
		}

		public static void ApplyIfProgressionChanged()
		{
			if (Progression.Refresh())
			{
				YokePlugin.Log.LogInfo((object)("World progression changed: " + Progression.Describe()));
				Apply();
			}
		}

		public static void Apply()
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null || instance.m_items == null || instance.m_items.Count == 0)
			{
				return;
			}
			Progression.Refresh();
			BiomeIndex.Prepare();
			bool armed = LoadGuard.Armed;
			if (!armed)
			{
				YokePlugin.Log.LogError((object)"Yoke's load guard is not in place, so every stack is being held at its vanilla size. Raising them without the guard clamps and then permanently destroys the excess of every stored stack on the next world load. Nothing is being changed until the guard is back.");
			}
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			List<ItemDump.Row> list = new List<ItemDump.Row>(instance.m_items.Count);
			LiveItems.Forget();
			foreach (GameObject item in instance.m_items)
			{
				if ((Object)(object)item == (Object)null)
				{
					continue;
				}
				ItemDrop component = item.GetComponent<ItemDrop>();
				SharedData val = (((Object)(object)component != (Object)null && component.m_itemData != null) ? component.m_itemData.m_shared : null);
				if (val == null)
				{
					continue;
				}
				if (!Originals.TryGetValue(((Object)item).name, out var value))
				{
					value = new Original(val.m_maxStackSize, val.m_weight);
					Originals[((Object)item).name] = value;
				}
				string text = BiomeIndex.BiomeOf(((Object)item).name);
				string text2 = SkipReason(((Object)item).name, val, value, text);
				string pending = null;
				if (text2 == null)
				{
					float num4 = (YokeConfig.ScaleWithProgression.Value ? Progression.MultiplierFor(text) : YokeConfig.StackMultiplier.Value);
					int num5 = Mathf.Clamp(Mathf.RoundToInt((float)value.Stack * num4), 1, Mathf.Max(1, YokeConfig.StackCap.Value));
					num5 = Mathf.Max(num5, value.Stack);
					if (!armed)
					{
						num5 = Mathf.Min(num5, value.Stack);
					}
					float num6 = Mathf.Max(0f, value.Weight * YokeConfig.WeightMultiplier.Value);
					if (val.m_maxStackSize == num5 && Mathf.Approximately(val.m_weight, num6))
					{
						num2++;
					}
					else
					{
						val.m_maxStackSize = num5;
						val.m_weight = num6;
						num++;
						if (YokeConfig.Verbose.Value)
						{
							ManualLogSource log = YokePlugin.Log;
							string[] obj = new string[9]
							{
								((Object)item).name,
								": stack ",
								null,
								null,
								null,
								null,
								null,
								null,
								null
							};
							int stack = value.Stack;
							obj[2] = stack.ToString();
							obj[3] = " -> ";
							obj[4] = num5.ToString();
							obj[5] = ", weight ";
							float weight = value.Weight;
							obj[6] = weight.ToString("0.##", CultureInfo.InvariantCulture);
							obj[7] = " -> ";
							obj[8] = num6.ToString("0.##", CultureInfo.InvariantCulture);
							log.LogInfo((object)string.Concat(obj));
						}
					}
					if (armed && YokeConfig.ScaleWithProgression.Value && num5 == value.Stack)
					{
						pending = Progression.PendingFor(text);
					}
				}
				else
				{
					num3++;
				}
				LiveItems.Record(((Object)item).name, val.m_maxStackSize, val.m_weight);
				list.Add(new ItemDump.Row(((Object)item).name, DisplayName(val.m_name, ((Object)item).name), ((object)Unsafe.As<ItemType, ItemType>(ref val.m_itemType)/*cast due to .constrained prefix*/).ToString(), text, value.Stack, val.m_maxStackSize, value.Weight, val.m_weight, Note(text2, pending, text, armed)));
			}
			YokePlugin.Log.LogInfo((object)("Retuned " + num + " item(s); " + num2 + " already correct; left " + num3 + " alone."));
			LiveItems.SweepPlayer();
			ItemDump.Write(list);
		}

		internal static string Explain(GameObject prefab)
		{
			ItemDrop component = prefab.GetComponent<ItemDrop>();
			SharedData val = (((Object)(object)component != (Object)null && component.m_itemData != null) ? component.m_itemData.m_shared : null);
			if (val == null)
			{
				return "yoke item=" + ((Object)prefab).name + " has no item data";
			}
			if (!Originals.TryGetValue(((Object)prefab).name, out var value))
			{
				return "yoke item=" + ((Object)prefab).name + " has not been through a stack pass yet";
			}
			string text = BiomeIndex.BiomeOf(((Object)prefab).name);
			string text2 = SkipReason(((Object)prefab).name, val, value, text);
			float num = (YokeConfig.ScaleWithProgression.Value ? Progression.MultiplierFor(text) : YokeConfig.StackMultiplier.Value);
			string[] obj = new string[18]
			{
				"yoke item=",
				((Object)prefab).name,
				" index=",
				BiomeIndex.Complete ? "complete" : "incomplete",
				" guard=",
				LoadGuard.Armed ? "armed" : "absent",
				" biome=",
				text,
				" rule=",
				text2 ?? "none",
				" multiplier=",
				num.ToString("0.##", CultureInfo.InvariantCulture),
				" stack=",
				val.m_maxStackSize.ToString(),
				" vanilla=",
				null,
				null,
				null
			};
			int stack = value.Stack;
			obj[15] = stack.ToString();
			obj[16] = " raised=";
			obj[17] = ((val.m_maxStackSize > value.Stack) ? "yes" : "no");
			return string.Concat(obj);
		}

		private static string Note(string reason, string pending, string group, bool guarded)
		{
			if (reason == null)
			{
				if (!guarded)
				{
					return "unguarded";
				}
				if (pending == null)
				{
					return null;
				}
				return "awaiting " + pending;
			}
			if (reason != "metal-held" || !YokeConfig.ScaleWithProgression.Value)
			{
				return reason;
			}
			string text = Progression.PendingFor(group);
			if (text != null)
			{
				return reason + " until " + text;
			}
			return reason;
		}

		private static string SkipReason(string prefabName, SharedData shared, Original original, string group)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Invalid comparison between Unknown and I4
			if (YokeConfig.IsExcluded(prefabName))
			{
				return "excluded";
			}
			if (original.Stack <= 1)
			{
				return "equipment";
			}
			if (!shared.m_teleportable && !YokeConfig.IncludeNonTeleportable.Value && !Progression.MetalTierEarned(group))
			{
				return "metal-held";
			}
			if ((int)shared.m_itemType == 13 && !YokeConfig.IncludeTrophies.Value)
			{
				return "trophy";
			}
			return null;
		}

		private static string DisplayName(string token, string fallback)
		{
			if (string.IsNullOrEmpty(token))
			{
				return fallback;
			}
			try
			{
				Localization instance = Localization.instance;
				if (instance != null)
				{
					string text = instance.Localize(token);
					if (!string.IsNullOrEmpty(text))
					{
						return text;
					}
				}
			}
			catch (Exception)
			{
			}
			return token;
		}
	}
	internal static class YokeConfig
	{
		public static ConfigEntry<float> StackMultiplier;

		public static ConfigEntry<int> StackCap;

		public static ConfigEntry<float> WeightMultiplier;

		public static ConfigEntry<bool> IncludeNonTeleportable;

		public static ConfigEntry<bool> IncludeTrophies;

		public static ConfigEntry<bool> ScaleWithProgression;

		public static ConfigEntry<float> ProgressionBase;

		public static ConfigEntry<string> ProgressionTiers;

		public static ConfigEntry<string> ProgressionOrder;

		public static ConfigEntry<float> ProgressionStep;

		public static ConfigEntry<string> BiomeOverrides;

		public static ConfigEntry<bool> DeferToUtangard;

		public static ConfigEntry<string> ExcludeItems;

		public static ConfigEntry<bool> WriteItemList;

		public static ConfigEntry<bool> Verbose;

		private static HashSet<string> _excluded;

		public static string ConfigPath { get; private set; }

		public static string ItemListPath { get; private set; }

		public static void Bind(ConfigFile config)
		{
			ConfigPath = config.ConfigFilePath;
			ItemListPath = Path.ChangeExtension(config.ConfigFilePath, ".items.txt");
			StackMultiplier = config.Bind<float>("Stacks", "StackMultiplier", 2f, "Multiplies the vanilla stack size of anything that already stacks. Always applied to the original value, so raising and lowering it is safe.");
			StackCap = config.Bind<int>("Stacks", "StackCap", 200, "Ceiling on stack GROWTH, whatever the multiplier says. An item whose vanilla stack is already above it keeps its vanilla stack - coins are 999 out of the box and this cap must not cut them.");
			WeightMultiplier = config.Bind<float>("Stacks", "WeightMultiplier", 1f, "Multiplies item weight. Left at 1 by default: carry weight is the main thing standing between you and hauling a mountain home in one trip, and bigger stacks already ease the inventory-slot half of that problem.");
			IncludeNonTeleportable = config.Bind<bool>("Stacks", "IncludeNonTeleportable", false, "Give ore and metal bars their bigger stacks straight away, instead of waiting for the biome they come from. Off by default because hauling metal is a deliberate part of the pacing rather than an oversight.\nThis is only about STACK SIZE. Nothing here changes what may go through a portal - the game's own no-portal flag is simply the cleanest way to find the metals, and is never written to.\nLeft off, metals are not held forever: each stacks once the boss of ITS OWN biome falls, the same rule every other item follows. Copper and tin at the Elder, iron at Bonemass, silver at Moder, black metal at Yagluth. This switch is for wanting all of them sooner than that.");
			IncludeTrophies = config.Bind<bool>("Stacks", "IncludeTrophies", true, "Also affect trophies. Harmless - they are decoration and turn-ins.");
			ExcludeItems = config.Bind<string>("Stacks", "ExcludeItems", "", "Comma-separated item prefab names to leave completely alone.");
			ScaleWithProgression = config.Bind<bool>("Progression", "ScaleWithProgression", true, "Each boss raises one group of stacks instead of everything being multiplied from the first minute. Meadows scarcity is the game teaching you to plan; your ninth trip to the same copper deposit is not teaching you anything, and a flat multiplier cannot tell those two apart. Off falls back to StackMultiplier for everything.");
			ProgressionBase = config.Bind<float>("Progression", "ProgressionBase", 1f, "The multiplier for a group no boss has unlocked yet. 1 is vanilla, which is the point: the early game is supposed to be tight.");
			ProgressionTiers = config.Bind<string>("Progression", "ProgressionTiers", "defeated_eikthyr:meadows:2, defeated_gdking:blackforest:2, defeated_bonemass:swamp:2, defeated_bonemass:ocean:2, defeated_dragon:mountain:2, defeated_goblinking:plains:2, defeated_queen:mistlands:2, defeated_fader:ashlands:2", "boss:biome:multiplier, comma separated. Kill a biome's boss and the things that biome gives you stack better. The highest earned entry naming a biome wins, so entries never compound; one boss may name several biomes.\nBonemass carries the Ocean as well as the Swamp, because no boss lives in the water and without a row naming it every fish, and chitin, and bait would sit at vanilla for the whole game. Bonemass rather than an earlier one because the longship is the boat that makes fishing worth hauling.\nBiomes: meadows, blackforest, ocean, swamp, mountain, plains, mistlands, ashlands, deepnorth, and all.\nWhich biome an item belongs to is worked out from the game's own tables, not a list here: the vegetation table places a copper deposit in the Black Forest and the deposit says it drops copper ore, so copper ore is a Black Forest item. Creatures come through the spawn table and their drops, and bars and cooked food inherit from what they are made of. Items no table reaches stay at vanilla - see BiomeOverrides.\nAn item found in several biomes belongs to the earliest, because that is where you first had to carry it home.\nAny global key works, not only boss keys, so a modded key can be named.");
			ProgressionOrder = config.Bind<string>("Progression", "ProgressionOrder", "defeated_eikthyr, defeated_gdking, defeated_bonemass, defeated_dragon, defeated_goblinking, defeated_queen, defeated_fader", "The order bosses come in, which is what makes ProgressionStep mean 'later'. A key not named here still unlocks its own group; it just never counts as later than anything.\nDoes nothing while ProgressionStep is 0, which is the default.");
			ProgressionStep = config.Bind<float>("Progression", "ProgressionStep", 0f, "How much every boss after the one that unlocked a group raises it again, compounding. 0.1 would make building 2x at Eikthyr, 2.2x once The Elder is down, 2.42x after Bonemass, 3.54x with all seven dead.\nOff, because ten extra wood in a slot is not a reward anyone can feel, and because it produces stack sizes like 133 that read as a bug next to vanilla's round numbers. It also drifts past the flat 2x this mod argues is the honest amount. Progression is meant to finish at Bonemass: by then you have portals, a cart and a longship, which is the game solving hauling by itself.");
			BiomeOverrides = config.Bind<string>("Progression", "BiomeOverrides", "CopperOre:blackforest, SilverOre:mountain, IronScrap:swamp, IronOre:swamp, BlackMetalScrap:plains, FlametalOre:ashlands, FlametalOreNew:ashlands, Flametal:ashlands, FlametalNew:ashlands, CopperScrap:blackforest, BronzeScrap:blackforest, MoltenCore:swamp, YmirRemains:blackforest, FineWood:meadows, RoundLog:meadows, SurtlingCore:blackforest, Carrot:blackforest, Turnip:swamp, Onion:mountain, Barley:plains, Flax:plains, Vineberry:mistlands, Honey:meadows, QueenBee:meadows, MushroomYellow:blackforest, MushroomBlue:mistlands, MushroomBzerker:mistlands, Fiddleheadfern:mistlands, Sap:mistlands, Tar:plains, WitheredBone:swamp, Wisp:mistlands, Larva:mistlands, Amber:blackforest, AmberPearl:blackforest, Ruby:blackforest, SilverNecklace:mountain, GemstoneBlue:mistlands, GemstoneGreen:mistlands, GemstoneRed:mistlands, BlackCore:mistlands, DvergrKeyFragment:mistlands, DvergrNeedle:mistlands, Ectoplasm:mistlands, GroveHeartwood:mistlands, Thunderstone:mistlands, VegvisirShard_Bonemass:swamp, Fish1:ocean, Fish2:ocean, Fish3:ocean, Fish5:ocean, Fish6:ocean, Fish7:ocean, Fish8:ocean, Fish9:ocean, Fish4_cave:mountain, Fish10:ashlands, Fish11:ashlands, Fish12:ashlands, FishRaw:ocean, FishAnglerRaw:ocean, FreshSeaweed:ocean, Chitin:ocean, BonemawSerpentScale:ashlands, TrophyForestTroll:blackforest, TrophySkeletonHildir:blackforest, TrophyGhost:swamp, TrophyDraugrFem:swamp, TrophySkeletonPoison:swamp, TrophySurtling:swamp, BlobVial:swamp, draugr_arrow:swamp, TrophyCultist:mountain, TrophyCultist_Hildir:mountain, TrophyUlv:mountain, WolfClaw:mountain, WolfHairBundle:mountain, TrophyGoblinShaman:plains, TrophyGoblinBruteBrosBrute:plains, TrophyGoblinBruteBrosShaman:plains, GoblinSpear:plains, JuteBlue:plains, JuteRed:plains, BombBlob_Tar:plains, TrophyGrowth:mistlands, TrophyKvastur:ashlands, TrophyCharredMage:ashlands, CuredSquirrelHamstring:mistlands, TurretBoltBone:mistlands, AsksvinEgg:ashlands, AsksvinCarrionNeck:ashlands, AsksvinCarrionPelvic:ashlands, AsksvinCarrionRibcage:ashlands, AsksvinCarrionSkull:ashlands, ChickenEgg:ashlands, ChickenMeat:ashlands, CharredCogwheel:ashlands, BellFragment:ashlands, Pot_Shard_Red:ashlands, PungentPebbles:ashlands, CandleWick:ashlands, FragrantBundle:ashlands, PowderedDragonEgg:ashlands, SpiceForests:blackforest, SpiceMountains:mountain, SpicePlains:plains, SpiceOceans:ocean, SpiceMistlands:mistlands, SpiceAshlands:ashlands, OnionSeeds:mountain, VineberrySeeds:mistlands, VineGreenSeeds:mistlands, FishingBait:ocean, MeadTrollPheromones:blackforest", "prefab:biome, comma separated, for items the game's tables cannot place.\nIron is the reason this exists. Iron scrap is not placed in the world and is not dropped by anything that spawns in one - it is inside Sunken Crypts, and a location holds its prefab as a soft reference that is not loaded until the game wants it. Forcing dungeon interiors to load on the way into a world, to learn something that fits on this line, is not a trade worth making.\nThe item list shows every item that ended up with no biome, so this line can be filled in from what is actually missing rather than guessed at.");
			DeferToUtangard = config.Bind<bool>("Progression", "DeferToUtangard", true, "When Utangard is installed, ask it whether the group has earned a boss rather than reading the world key. Utangard opens a biome only when every member of the group was at that boss's death, so the two answers differ whenever somebody was offline for a kill - and without this, stacks would arrive for a biome Utangard still has fenced off. No effect when Utangard is not installed.");
			WriteItemList = config.Bind<bool>("Diagnostics", "WriteItemList", true, "Write ezomic.valheim.yoke.items.txt beside this file: every item in the game, what Yoke did to it, and which rule left it alone when it did nothing. On by default because the prefab names ExcludeItems takes are not guessable, and because it answers 'why did this item not change' without anyone having to read a log.");
			Verbose = config.Bind<bool>("Diagnostics", "Verbose", false, "Log every item whose stack size or weight was changed. The item list above is usually the better answer; this is for watching a single pass happen.");
		}

		public static bool IsExcluded(string prefabName)
		{
			if (_excluded == null)
			{
				_excluded = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
				string[] array = (ExcludeItems.Value ?? "").Split(new char[1] { ',' });
				for (int i = 0; i < array.Length; i++)
				{
					string text = array[i].Trim();
					if (text.Length > 0)
					{
						_excluded.Add(text);
					}
				}
			}
			if (_excluded.Count > 0)
			{
				return _excluded.Contains(prefabName);
			}
			return false;
		}
	}
	[BepInPlugin("ezomic.valheim.yoke", "Yoke", "1.2.2")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class YokePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "ezomic.valheim.yoke";

		public const string PluginName = "Yoke";

		public const string PluginVersion = "1.2.2";

		public const string PluginAuthor = "Robbin Thijssen";

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

		private const string UtangardGuid = "ezomic.valheim.utangard";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		internal static bool ProgressionDirty;

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			AdoptOldConfig();
			YokeConfig.Bind(((BaseUnityPlugin)this).Config);
			WireBiomeIndex();
			TryRegisterWithCore();
			_harmony = new Harmony("ezomic.valheim.yoke");
			Patch(typeof(LoadGuard), "the stored-stack load guard");
			Patch(typeof(ObjectDbPatches), "the stack tuner's hooks");
			Patch(typeof(LiveItemPatches), "the live-inventory sweep");
			Patch(typeof(DevConsole), "the yoke item console readout");
			if (!LoadGuard.Armed)
			{
				Log.LogError((object)"Yoke is UNGUARDED: the load guard is not in place, so stack sizes are being left at vanilla. Raising them without it destroys the excess of every stored stack the next time the world loads. See the LoadGuard line above for which half failed.");
			}
			Log.LogInfo((object)"Yoke 1.2.2 by Robbin Thijssen - ready.");
		}

		private void Patch(Type type, string what)
		{
			try
			{
				_harmony.PatchAll(type);
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Yoke could not install " + what + " (" + type.Name + "). That feature is off for this session; the rest of the mod carries on. " + ex));
			}
		}

		private void WireBiomeIndex()
		{
			BiomeIndex.Log = ((BaseUnityPlugin)this).Logger;
			BiomeIndex.BiomeForKey = Progression.BiomeFor;
			BiomeIndex.Overrides = () => YokeConfig.BiomeOverrides.Value;
			BiomeIndex.OverrideSetting = "BiomeOverrides";
		}

		private void AdoptOldConfig()
		{
			try
			{
				string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
				if (File.Exists(configFilePath))
				{
					return;
				}
				string directoryName = Path.GetDirectoryName(configFilePath);
				if (!string.IsNullOrEmpty(directoryName))
				{
					string text = Path.Combine(directoryName, "ezomic.valheim.hoard.cfg");
					if (File.Exists(text))
					{
						File.Copy(text, configFilePath);
						Log.LogInfo((object)"Adopted Hoard's config, so its settings carry over unchanged.");
					}
				}
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("Could not adopt Hoard's config: " + ex.Message));
			}
		}

		private void TryRegisterWithCore()
		{
			if (!Chainloader.PluginInfos.ContainsKey("ezomic.valheim.core"))
			{
				Log.LogInfo((object)"Core not installed - running standalone, without the version gate.");
			}
			else
			{
				RegisterWithCore();
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		private void RegisterWithCore()
		{
			Suite.Register("ezomic.valheim.yoke", "Yoke", "1.2.2", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null);
		}

		private void Update()
		{
			if (ProgressionDirty)
			{
				ProgressionDirty = false;
				StackTuner.ApplyIfProgressionChanged();
			}
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class ObjectDbPatches
	{
		[HarmonyPostfix]
		[HarmonyPatch(typeof(ObjectDB), "Awake")]
		private static void TuneOnAwake()
		{
			StackTuner.Rebuild();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
		private static void TuneOnCopy()
		{
			StackTuner.Rebuild();
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(ZoneSystem), "GlobalKeyAdd", new Type[]
		{
			typeof(string),
			typeof(bool)
		})]
		private static void TuneOnGlobalKey()
		{
			YokePlugin.ProgressionDirty = true;
		}

		[HarmonyPostfix]
		[HarmonyPatch(typeof(SpawnSystem), "Awake")]
		private static void TuneWhenSpawnersExist()
		{
			if (!BiomeIndex.Complete)
			{
				StackTuner.Rebuild();
			}
		}
	}
}