plugins/Yoke/Yoke.dll

Decompiled 10 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
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 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.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+2678e8ef04152af27e72802258351c5820f018ab")]
[assembly: AssemblyProduct("Yoke")]
[assembly: AssemblyTitle("Yoke")]
[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]
	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 Yoke
{
	internal static class BiomeIndex
	{
		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 static Dictionary<string, int> _biomeOf;

		private static bool _complete;

		public static bool Complete => _complete;

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

		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].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)
			//IL_00fc: 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, int> dictionary = new Dictionary<string, int>();
			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, item.m_biome);
					}
				}
			}
			if ((Object)(object)val != (Object)null && val.m_spawnLists != null)
			{
				foreach (SpawnSystemList spawnList in val.m_spawnLists)
				{
					if ((Object)(object)spawnList == (Object)null || spawnList.m_spawners == null)
					{
						continue;
					}
					foreach (SpawnData spawner in spawnList.m_spawners)
					{
						if (spawner != null && spawner.m_enabled)
						{
							Harvest(dictionary, spawner.m_prefab, spawner.m_biome);
						}
					}
				}
			}
			if ((Object)(object)instance2 != (Object)null && instance2.m_prefabs != null)
			{
				Bosses(dictionary, instance2);
			}
			ApplyOverrides(dictionary);
			for (int i = 0; i < 8; i++)
			{
				bool flag = Craft(dictionary);
				if ((Object)(object)instance2 != (Object)null && instance2.m_prefabs != null)
				{
					flag |= Convert(dictionary, instance2);
				}
				if (!flag)
				{
					break;
				}
			}
			ApplyOverrides(dictionary);
			_biomeOf = dictionary;
			_complete = (Object)(object)instance != (Object)null && (Object)(object)val != (Object)null && (Object)(object)instance2 != (Object)null;
			if (_complete)
			{
				YokePlugin.Log.LogInfo((object)("Biome index built: " + dictionary.Count + " item(s) placed."));
				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");
			}
			YokePlugin.Log.LogInfo((object)("Biome index partial: " + dictionary.Count + " item(s) placed, still waiting on " + string.Join(" and ", list.ToArray()) + "."));
		}

		private static void Harvest(Dictionary<string, int> found, GameObject prefab, Biome mask)
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)prefab == (Object)null)
			{
				return;
			}
			int num = Earliest(mask);
			if (num < 0)
			{
				return;
			}
			Pickable[] componentsInChildren = prefab.GetComponentsInChildren<Pickable>(true);
			foreach (Pickable val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null)
				{
					Record(found, val.m_itemPrefab, num);
				}
			}
			MineRock5[] componentsInChildren2 = prefab.GetComponentsInChildren<MineRock5>(true);
			foreach (MineRock5 val2 in componentsInChildren2)
			{
				if ((Object)(object)val2 != (Object)null)
				{
					Record(found, val2.m_dropItems, num);
				}
			}
			DropOnDestroyed[] componentsInChildren3 = prefab.GetComponentsInChildren<DropOnDestroyed>(true);
			foreach (DropOnDestroyed val3 in componentsInChildren3)
			{
				if ((Object)(object)val3 != (Object)null)
				{
					Record(found, val3.m_dropWhenDestroyed, num);
				}
			}
			CharacterDrop[] componentsInChildren4 = prefab.GetComponentsInChildren<CharacterDrop>(true);
			foreach (CharacterDrop val4 in componentsInChildren4)
			{
				if ((Object)(object)val4 == (Object)null || val4.m_drops == null)
				{
					continue;
				}
				foreach (Drop drop in val4.m_drops)
				{
					if (drop != null)
					{
						Record(found, drop.m_prefab, num);
					}
				}
			}
			SpawnArea[] componentsInChildren5 = prefab.GetComponentsInChildren<SpawnArea>(true);
			foreach (SpawnArea val5 in componentsInChildren5)
			{
				if ((Object)(object)val5 == (Object)null || val5.m_prefabs == null)
				{
					continue;
				}
				foreach (SpawnData prefab2 in val5.m_prefabs)
				{
					if (prefab2 == null || (Object)(object)prefab2.m_prefab == (Object)null)
					{
						continue;
					}
					componentsInChildren4 = prefab2.m_prefab.GetComponentsInChildren<CharacterDrop>(true);
					foreach (CharacterDrop val6 in componentsInChildren4)
					{
						if ((Object)(object)val6 == (Object)null || val6.m_drops == null)
						{
							continue;
						}
						foreach (Drop drop2 in val6.m_drops)
						{
							if (drop2 != null)
							{
								Record(found, drop2.m_prefab, num);
							}
						}
					}
				}
			}
			TreeBase[] componentsInChildren6 = prefab.GetComponentsInChildren<TreeBase>(true);
			foreach (TreeBase val7 in componentsInChildren6)
			{
				if ((Object)(object)val7 == (Object)null)
				{
					continue;
				}
				Record(found, val7.m_dropWhenDestroyed, num);
				if ((Object)(object)val7.m_logPrefab == (Object)null)
				{
					continue;
				}
				TreeLog[] componentsInChildren7 = val7.m_logPrefab.GetComponentsInChildren<TreeLog>(true);
				foreach (TreeLog val8 in componentsInChildren7)
				{
					if ((Object)(object)val8 != (Object)null)
					{
						Record(found, val8.m_dropWhenDestroyed, num);
					}
				}
			}
		}

		private static bool Convert(Dictionary<string, int> 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, int> 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 = Progression.BiomeFor(component.m_defeatSetGlobalKey);
				if (text == null)
				{
					continue;
				}
				int num = IndexOf(text);
				if (num < 0)
				{
					continue;
				}
				CharacterDrop[] componentsInChildren = prefab.GetComponentsInChildren<CharacterDrop>(true);
				foreach (CharacterDrop val in componentsInChildren)
				{
					if ((Object)(object)val == (Object)null || val.m_drops == null)
					{
						continue;
					}
					foreach (Drop drop in val.m_drops)
					{
						if (drop != null)
						{
							Record(found, drop.m_prefab, 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, int> found)
		{
			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 = -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) && found.TryGetValue(((Object)((Component)val.m_resItem).gameObject).name, out var value) && value > num)
					{
						num = value;
					}
				}
				if (num >= 0)
				{
					string name = ((Object)((Component)recipe.m_item).gameObject).name;
					if (!found.TryGetValue(name, out var value2) || value2 > num)
					{
						found[name] = num;
						result = true;
					}
				}
			}
			return result;
		}

		private static bool Inherit(Dictionary<string, int> 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;
			}
			if (found.TryGetValue(((Object)((Component)to).gameObject).name, out var value2) && value2 <= value)
			{
				return false;
			}
			found[((Object)((Component)to).gameObject).name] = value;
			return true;
		}

		private static void ApplyOverrides(Dictionary<string, int> found)
		{
			string[] array = (YokeConfig.BiomeOverrides.Value ?? "").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)
				{
					YokePlugin.Log.LogWarning((object)("Ignoring biome override '" + text + "': expected prefab:biome."));
					continue;
				}
				string text2 = array2[1].Trim().ToLowerInvariant();
				int num = -1;
				for (int j = 0; j < Ordered.Length; j++)
				{
					if (Ordered[j].Key == text2)
					{
						num = j;
						break;
					}
				}
				if (num < 0)
				{
					YokePlugin.Log.LogWarning((object)("Ignoring biome override '" + text + "': '" + text2 + "' is not a biome."));
				}
				else
				{
					found[array2[0].Trim()] = num;
				}
			}
		}

		private static void Record(Dictionary<string, int> found, DropTable table, int index)
		{
			//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);
				}
			}
		}

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

		private static void Record(Dictionary<string, int> found, string prefabName, int index)
		{
			if (!string.IsNullOrEmpty(prefabName) && (!found.TryGetValue(prefabName, out var value) || value > index))
			{
				found[prefabName] = index;
			}
		}

		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;
		}
	}
	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[4] { "equipment", "portal-blocked", "trophy", "excluded" };

		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.0.0 - 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 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 const string UtangardGuid = "ezomic.valheim.utangard";

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

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

		public static float MultiplierFor(string 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 PortalRuleLifted()
		{
			if (!YokeConfig.ScaleWithProgression.Value)
			{
				return false;
			}
			string text = PortalRuleOwner();
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			foreach (Tier item in Tiers())
			{
				if ((!(item.Group != text) || !(item.Group != "all")) && Earned(item.BossKey))
				{
					return true;
				}
			}
			return false;
		}

		public static string PortalRuleOwner()
		{
			string value = YokeConfig.LiftPortalRuleAt.Value;
			if (value != null)
			{
				return value.Trim().ToLowerInvariant();
			}
			return "";
		}

		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 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.Append(PortalRuleLifted() ? "portal-on" : "portal-off").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 PortalBlocked = "portal-blocked";

		internal const string Trophy = "trophy";

		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();
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			List<ItemDump.Row> list = new List<ItemDump.Row>(instance.m_items.Count);
			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);
				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));
					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 (YokeConfig.ScaleWithProgression.Value && num5 == value.Stack)
					{
						pending = Progression.PendingFor(text);
					}
				}
				else
				{
					num3++;
				}
				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)));
			}
			YokePlugin.Log.LogInfo((object)("Retuned " + num + " item(s); " + num2 + " already correct; left " + num3 + " alone."));
			ItemDump.Write(list);
		}

		private static string Note(string reason, string pending, string group)
		{
			if (reason == null)
			{
				if (pending == null)
				{
					return null;
				}
				return "awaiting " + pending;
			}
			if (reason != "portal-blocked" || !YokeConfig.ScaleWithProgression.Value)
			{
				return reason;
			}
			string text = Progression.PendingFor(Progression.PortalRuleOwner());
			if (text != null)
			{
				return reason + " until " + text;
			}
			return reason;
		}

		private static string SkipReason(string prefabName, SharedData shared, Original original)
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: 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.PortalRuleLifted())
			{
				return "portal-blocked";
			}
			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> LiftPortalRuleAt;

		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, "Hard ceiling on the resulting stack size, whatever the multiplier says.");
			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, "Also affect items that cannot go through a portal - ore, metal bars and the like. Off by default because hauling metal by cart and boat is a deliberate part of the game's pacing, not an oversight.\nLeaving this off does not mean metal never stacks: an earned metal tier in ProgressionTiers lifts the rule, which is the intended way for ore stacking to arrive. This switch is for wanting it 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_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.\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.");
			LiftPortalRuleAt = config.Bind<string>("Progression", "LiftPortalRuleAt", "swamp", "The biome whose tier lifts the portal rule, so ore and bars start stacking when its boss falls. The Swamp, because Bonemass is where iron begins and by then the copper runs are behind you. Blank to keep the rule until IncludeNonTeleportable is turned on by hand.");
			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.0.0")]
	[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.0.0";

		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;

		private void Awake()
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			AdoptOldConfig();
			YokeConfig.Bind(((BaseUnityPlugin)this).Config);
			TryRegisterWithCore();
			_harmony = new Harmony("ezomic.valheim.yoke");
			_harmony.PatchAll(typeof(ObjectDbPatches));
			Log.LogInfo((object)"Yoke 1.0.0 by Robbin Thijssen - ready.");
		}

		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.0.0", ((BaseUnityPlugin)this).Config, (Requirement)0, (Assembly)null);
		}

		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()
		{
			StackTuner.ApplyIfProgressionChanged();
		}

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