Decompiled source of TamedTransport v1.0.0

BepInEx/plugins/dannanboi_TamedTransport.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.Rendering;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("TamedTransport")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TamedTransport
{
	internal sealed class AnimalDataCache
	{
		private bool initialized;

		private string payload;

		private AnimalData data;

		internal AnimalData Read(string value)
		{
			if (!initialized || payload != value)
			{
				initialized = true;
				payload = value;
				data = null;
				try
				{
					data = AnimalData.Decode(value);
				}
				catch (Exception ex) when (ex is FormatException || ex is IOException || ex is ArgumentException)
				{
				}
			}
			return data;
		}
	}
	internal sealed class AnimalData
	{
		internal string Token;

		internal string Prefab;

		internal string Name;

		internal string CustomName;

		internal int Level;

		internal float Health;

		internal float Age;

		internal bool Tamed = true;

		internal float TamingTimeLeft = -1f;

		internal string Quality => (Level == 1) ? "0 stars" : (Level - 1 + ((Level == 2) ? " star" : " stars"));

		internal string Encode()
		{
			Validate();
			using MemoryStream memoryStream = new MemoryStream();
			using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
			{
				binaryWriter.Write(2);
				binaryWriter.Write(Token);
				binaryWriter.Write(Prefab);
				binaryWriter.Write(Name);
				binaryWriter.Write(CustomName);
				binaryWriter.Write(Level);
				binaryWriter.Write(Health);
				binaryWriter.Write(Age);
				binaryWriter.Write(Tamed);
				binaryWriter.Write(TamingTimeLeft);
			}
			return Convert.ToBase64String(memoryStream.ToArray());
		}

		internal static AnimalData Decode(string value)
		{
			if (value == null || value.Length > 4096)
			{
				throw new FormatException("Invalid cage contents.");
			}
			using MemoryStream memoryStream = new MemoryStream(Convert.FromBase64String(value));
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			int num = binaryReader.ReadInt32();
			if (num != 1 && num != 2)
			{
				throw new FormatException("Unknown animal data version.");
			}
			AnimalData animalData = new AnimalData
			{
				Token = binaryReader.ReadString(),
				Prefab = binaryReader.ReadString(),
				Name = binaryReader.ReadString(),
				CustomName = binaryReader.ReadString(),
				Level = binaryReader.ReadInt32(),
				Health = binaryReader.ReadSingle(),
				Age = binaryReader.ReadSingle()
			};
			if (num == 2)
			{
				animalData.Tamed = binaryReader.ReadBoolean();
				animalData.TamingTimeLeft = binaryReader.ReadSingle();
			}
			else if (Catalog.IsAdult(animalData.Prefab))
			{
				throw new FormatException("Legacy cage data supports offspring only.");
			}
			if (memoryStream.Position != memoryStream.Length)
			{
				throw new FormatException("Unexpected animal data.");
			}
			animalData.Validate();
			return animalData;
		}

		internal void Validate()
		{
			if (!Guid.TryParseExact(Token, "N", out var _) || Catalog.OffspringFor(Prefab) == null || Name == null || Name.Length > 100 || CustomName == null || CustomName.Length > 100 || Level < 1 || Level > 100 || !Finite(Health) || Health <= 0f || Health > 1f || !Finite(Age) || Age < 0f || Age > 8640000f || !Finite(TamingTimeLeft) || (TamingTimeLeft != -1f && (TamingTimeLeft < 0f || TamingTimeLeft > 8640000f)))
			{
				throw new FormatException("Invalid animal state.");
			}
		}

		internal static bool Finite(float value)
		{
			return !float.IsNaN(value) && !float.IsInfinity(value);
		}
	}
	internal sealed class AnimalGhostTint : MonoBehaviour
	{
		private readonly HashSet<Material> materials = new HashSet<Material>();

		private bool? shownState;

		private static readonly int ColorId = Shader.PropertyToID("_Color");

		private static readonly int EmissionId = Shader.PropertyToID("_EmissionColor");

		internal void Initialize()
		{
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_00eb: Expected O, but got Unknown
			if (materials.Count != 0)
			{
				return;
			}
			Shader val = Shader.Find("Sprites/Default") ?? Shader.Find("UI/Default") ?? Shader.Find("Particles/Standard Unlit");
			Renderer[] componentsInChildren = ((Component)this).GetComponentsInChildren<Renderer>(true);
			foreach (Renderer val2 in componentsInChildren)
			{
				if (!(val2 is MeshRenderer) && !(val2 is SkinnedMeshRenderer))
				{
					continue;
				}
				val2.shadowCastingMode = (ShadowCastingMode)0;
				val2.receiveShadows = false;
				Material[] sharedMaterials = val2.sharedMaterials;
				bool flag = ((Component)val2).gameObject.activeInHierarchy && sharedMaterials.Length != 0 && Object.op_Implicit((Object)(object)sharedMaterials[0]);
				for (int j = 0; j < sharedMaterials.Length; j++)
				{
					Material val3 = sharedMaterials[j];
					if (Object.op_Implicit((Object)(object)val3) && !flag)
					{
						int num = j;
						Material val4 = new Material(val3);
						val3 = val4;
						sharedMaterials[num] = val4;
					}
					if (Object.op_Implicit((Object)(object)val3) && materials.Add(val3))
					{
						if (Object.op_Implicit((Object)(object)val))
						{
							val3.shader = val;
						}
						if (val3.HasProperty("_MainTex"))
						{
							val3.SetTexture("_MainTex", (Texture)(object)Texture2D.whiteTexture);
						}
						if (val3.HasProperty("_SrcBlend"))
						{
							val3.SetInt("_SrcBlend", 5);
						}
						if (val3.HasProperty("_DstBlend"))
						{
							val3.SetInt("_DstBlend", 10);
						}
						if (val3.HasProperty("_ZWrite"))
						{
							val3.SetInt("_ZWrite", 0);
						}
						val3.renderQueue = 3000;
					}
				}
				val2.sharedMaterials = sharedMaterials;
			}
			if (!Object.op_Implicit((Object)(object)val))
			{
				Plugin.Warn("Transparent preview shader unavailable; using the native preview material with green/red tint.");
			}
			Show(valid: false);
		}

		internal void Show(bool valid)
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			Color val = (valid ? new Color(0.15f, 1f, 0.2f, 0.45f) : new Color(1f, 0.12f, 0.08f, 0.45f));
			if (shownState != valid)
			{
				foreach (Material material in materials)
				{
					if (Object.op_Implicit((Object)(object)material) && material.HasProperty(ColorId))
					{
						material.SetColor(ColorId, val);
					}
				}
				shownState = valid;
			}
			if (Object.op_Implicit((Object)(object)MaterialMan.instance))
			{
				MaterialMan.instance.SetValue<Color>(((Component)this).gameObject, ColorId, val, false);
				MaterialMan.instance.SetValue<Color>(((Component)this).gameObject, EmissionId, new Color(0f, 0f, 0f, 0f), false);
			}
		}

		private void OnDestroy()
		{
			foreach (Material material in materials)
			{
				if (Object.op_Implicit((Object)(object)material))
				{
					Object.Destroy((Object)(object)material);
				}
			}
			materials.Clear();
		}
	}
	internal sealed class AttackGuard
	{
		private bool armed;

		private float until;

		internal bool Armed => armed;

		internal void Arm(float now)
		{
			armed = true;
			until = now + 0.15f;
		}

		internal bool Update(bool placing, bool held, float now)
		{
			if (placing)
			{
				Arm(now);
				return true;
			}
			if (!armed)
			{
				return false;
			}
			if (held || now < until)
			{
				return true;
			}
			armed = false;
			return false;
		}
	}
	internal sealed class Ingredient
	{
		internal string Item;

		internal int Amount;
	}
	internal sealed class CageDefinition
	{
		internal string Item;

		internal string Name;

		internal string Description;

		internal string Station;

		internal string Animal;

		internal string UseModel;

		internal string EmptyItem;

		internal string LoadedItem;

		internal bool IsLoaded;

		internal int StationLevel;

		internal float Weight;

		internal float LoadedWeight;

		internal readonly List<Ingredient> Ingredients = new List<Ingredient>();
	}
	internal sealed class Catalog
	{
		internal const int MaxBytes = 32768;

		internal readonly List<CageDefinition> Cages = new List<CageDefinition>();

		internal const string PlacementIconSlot = "dbtt_PlaceAnimal";

		internal const string DefaultModel = "dvergrprops_crate";

		internal string Yaml;

		internal string PlacementIcon = "dbtt_PlaceAnimal";

		internal bool Migrated;

		internal bool PlacementIconAdded;

		internal static readonly string[] EmptyNames = new string[3] { "dbtt_WoodenCage", "dbtt_ReinforcedCage", "dbtt_HeavyCage" };

		internal static readonly string[] LoadedNames = EmptyNames.Select((string name) => name + "Loaded").ToArray();

		internal static readonly string[] ItemNames = EmptyNames.Concat(LoadedNames).ToArray();

		internal static readonly string[] LegacyNames = new string[3] { "dbtt_WoodenAnimalCage", "dbtt_ReinforcedAnimalCage", "dbtt_HeavyAnimalCage" };

		internal static readonly string[] RegisteredNames = ItemNames.Concat(LegacyNames).ToArray();

		internal static readonly string[] AnimalNames = new string[3] { "Boar_piggy", "Wolf_cub", "Lox_Calf" };

		internal static readonly string[] AdultNames = new string[3] { "Boar", "Wolf", "Lox" };

		private static readonly Regex IngredientPattern = new Regex("^([A-Za-z_][A-Za-z0-9_]*)\\[([0-9]+)\\]$");

		internal static string DefaultYaml => "# TamedTransport cages. Server/host controls definitions in multiplayer.\n# Models use useModel; icons use icons/<top-level cage name>.png.\n# Only empty cages are craftable; loaded cages are created by capturing offspring.\n\ndbtt_WoodenCage:\n  basics:\n    - displayName: \"Wooden Animal Cage\"\n    - description: \"A wooden cage for transporting a wild animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 25\n    - craftingStation: piece_workbench\n    - stationLevel: 3\n  ingredients:\n    - Wood[30]\n    - LeatherScraps[10]\n    - Resin[15]\n\ndbtt_WoodenCageLoaded:\n  basics:\n    - displayName: \"Wooden Animal Cage (Loaded)\"\n    - description: \"A wooden cage for transporting a wild animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 450\n\ndbtt_ReinforcedCage:\n  basics:\n    - displayName: \"Reinforced Animal Cage\"\n    - description: \"A reinforced cage for transporting a feisty animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 50\n    - craftingStation: forge\n    - stationLevel: 3\n  ingredients:\n    - FineWood[30]\n    - Iron[15]\n    - DeerHide[10]\n\ndbtt_ReinforcedCageLoaded:\n  basics:\n    - displayName: \"Reinforced Animal Cage (Loaded)\"\n    - description: \"A reinforced cage for transporting a feisty animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 750\n\ndbtt_HeavyCage:\n  basics:\n    - displayName: \"Heavy Animal Cage\"\n    - description: \"A heavy iron cage for transporting a huge animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 100\n    - craftingStation: forge\n    - stationLevel: 5\n  ingredients:\n    - FineWood[40]\n    - BlackMetal[30]\n    - Chain[8]\n\ndbtt_HeavyCageLoaded:\n  basics:\n    - displayName: \"Heavy Animal Cage (Loaded)\"\n    - description: \"A heavy iron cage for transporting a huge animal.\"\n    - useModel: dvergrprops_crate\n    - weightPerUnit: 1000\n\n# Build-menu icon: icons/<placement icon>.png; missing files use Wood.\n\nplacement icon: dbtt_PlaceAnimal\n";

		internal CageDefinition FindItem(string item)
		{
			return Cages.Find((CageDefinition c) => c.Item == CanonicalItem(item));
		}

		internal CageDefinition FindAnimal(string animal)
		{
			return Cages.Find((CageDefinition c) => !c.IsLoaded && MatchesAnimal(c.Animal, animal));
		}

		internal static bool IsAdult(string animal)
		{
			return Array.IndexOf(AdultNames, animal) >= 0;
		}

		internal static string OffspringFor(string animal)
		{
			int num = Array.IndexOf(AnimalNames, animal);
			if (num < 0)
			{
				num = Array.IndexOf(AdultNames, animal);
			}
			return (num < 0) ? null : AnimalNames[num];
		}

		internal static string AdultFor(string offspring)
		{
			int num = Array.IndexOf(AnimalNames, offspring);
			return (num < 0) ? null : AdultNames[num];
		}

		internal static bool MatchesAnimal(string offspring, string animal)
		{
			return offspring != null && OffspringFor(animal) == offspring;
		}

		internal static string IconFileName(string item)
		{
			return item + ".png";
		}

		internal static bool IsLoadedItem(string item)
		{
			return Array.IndexOf(LoadedNames, item) >= 0;
		}

		internal static string CanonicalItem(string item)
		{
			int num = Array.IndexOf(LegacyNames, item);
			return (num < 0) ? item : EmptyNames[num];
		}

		internal static string ItemForState(string item, bool loaded)
		{
			string value = CanonicalItem(item);
			int num = Array.IndexOf(EmptyNames, value);
			if (num < 0)
			{
				num = Array.IndexOf(LoadedNames, value);
			}
			return (num < 0) ? null : (loaded ? LoadedNames[num] : EmptyNames[num]);
		}

		internal static Catalog Parse(string text)
		{
			if (text == null || Encoding.UTF8.GetByteCount(text) > 32768)
			{
				throw new FormatException("cages.yaml exceeds 32 KiB.");
			}
			Catalog result = new Catalog
			{
				Yaml = text
			};
			CageDefinition cageDefinition = null;
			string text2 = null;
			HashSet<string> hashSet = new HashSet<string>();
			HashSet<string> hashSet2 = new HashSet<string>();
			Dictionary<string, float> dictionary = new Dictionary<string, float>();
			bool flag = false;
			string[] array = text.TrimStart('\ufeff').Replace("\r", "").Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string text3 = StripComment(array[i]).TrimEnd();
				if (text3.Trim().Length == 0)
				{
					continue;
				}
				if (text3.Contains("\t"))
				{
					throw new FormatException("Line " + (i + 1) + ": use spaces for indentation.");
				}
				int num = text3.Length - text3.TrimStart().Length;
				string text4 = text3.Trim();
				if (num == 0)
				{
					int num2 = text4.IndexOf(':');
					if (num2 >= 0 && text4.Substring(0, num2) == "placement icon")
					{
						if (flag)
						{
							throw new FormatException("Duplicate field: placement icon");
						}
						result.PlacementIcon = Scalar(text4.Substring(num2 + 1).Trim());
						flag = true;
						if (result.PlacementIcon.Length > 128 || !Regex.IsMatch(result.PlacementIcon, "^[A-Za-z_][A-Za-z0-9_]*$"))
						{
							throw new FormatException("placement icon must be a PNG filename without its extension or directory.");
						}
						cageDefinition = null;
						text2 = null;
						continue;
					}
					if (!text4.EndsWith(":"))
					{
						throw new FormatException("Line " + (i + 1) + ": expected CagePrefabName:.");
					}
					string text5 = text4.Substring(0, text4.Length - 1);
					string text6 = CanonicalItem(text5);
					bool flag2 = LoadedNames.Contains(text6);
					int num3 = Array.IndexOf(flag2 ? LoadedNames : EmptyNames, text6);
					if (num3 < 0 || result.FindItem(text6) != null)
					{
						throw new FormatException("Unknown or duplicate cage: " + text6);
					}
					if (text5 != text6)
					{
						result.Migrated = true;
					}
					CageDefinition cageDefinition2 = new CageDefinition();
					cageDefinition2.Item = text6;
					cageDefinition2.EmptyItem = EmptyNames[num3];
					cageDefinition2.LoadedItem = LoadedNames[num3];
					cageDefinition2.IsLoaded = flag2;
					cageDefinition2.Animal = AnimalNames[num3];
					cageDefinition2.UseModel = "dvergrprops_crate";
					cageDefinition2.StationLevel = ((num3 == 2) ? 5 : 3);
					cageDefinition2.Station = ((num3 == 0) ? "piece_workbench" : "forge");
					cageDefinition2.Weight = (new float[3] { 25f, 50f, 100f })[num3];
					cageDefinition2.LoadedWeight = (new float[3] { 450f, 750f, 1000f })[num3];
					cageDefinition2.Name = (new string[3] { "Wooden Animal Cage", "Reinforced Animal Cage", "Heavy Animal Cage" })[num3] + (flag2 ? " (Loaded)" : "");
					cageDefinition2.Description = "A reusable cage for transporting tamed offspring.";
					cageDefinition = cageDefinition2;
					if (flag2)
					{
						cageDefinition.Weight = cageDefinition.LoadedWeight;
					}
					result.Cages.Add(cageDefinition);
					text2 = null;
					hashSet.Clear();
					hashSet2.Clear();
					continue;
				}
				if (cageDefinition == null)
				{
					throw new FormatException("Start with a cage prefab name.");
				}
				if (num == 2 && (text4 == "basics:" || text4 == "stats:" || text4 == "ingredients:"))
				{
					text2 = text4.TrimEnd(':');
					if (!hashSet2.Add(text2))
					{
						throw new FormatException("Duplicate section: " + text2);
					}
					continue;
				}
				if (num != 4 || !text4.StartsWith("- ") || text2 == null)
				{
					throw new FormatException("Line " + (i + 1) + ": use two spaces for sections and four for dashed fields.");
				}
				text4 = text4.Substring(2);
				if (text2 == "ingredients")
				{
					if (cageDefinition.IsLoaded)
					{
						throw new FormatException("Loaded cages are filled by capture and cannot have a crafting recipe.");
					}
					Match match = IngredientPattern.Match(Scalar(text4));
					if (!match.Success || !int.TryParse(match.Groups[2].Value, out var result2) || result2 < 1 || result2 > 10000 || cageDefinition.Ingredients.Count >= 16)
					{
						throw new FormatException("Invalid ingredient at line " + (i + 1) + ". Use Prefab[amount], amount 1–10000.");
					}
					string item = match.Groups[1].Value;
					if (RegisteredNames.Contains(item) || cageDefinition.Ingredients.Any((Ingredient x) => x.Item == item))
					{
						throw new FormatException("Duplicate ingredient or cage ingredient: " + item);
					}
					cageDefinition.Ingredients.Add(new Ingredient
					{
						Item = item,
						Amount = result2
					});
					continue;
				}
				int num4 = text4.IndexOf(':');
				if (num4 < 1)
				{
					throw new FormatException("Expected field: value.");
				}
				string text7 = text4.Substring(0, num4);
				string text8 = Scalar(text4.Substring(num4 + 1).Trim());
				if (!hashSet.Add(text7))
				{
					throw new FormatException("Duplicate field: " + text7);
				}
				int num5;
				switch (text7)
				{
				default:
					num5 = ((text7 == "useModel") ? 1 : 0);
					break;
				case "displayName":
				case "description":
				case "craftingStation":
				case "stationLevel":
				case "weightPerUnit":
					num5 = 1;
					break;
				}
				bool flag3 = (byte)num5 != 0;
				if (text2 == "basics" != flag3)
				{
					throw new FormatException("Field " + text7 + " is in the wrong section.");
				}
				if (cageDefinition.IsLoaded && (text7 == "craftingStation" || text7 == "stationLevel" || text7 == "loadedWeightPerUnit"))
				{
					throw new FormatException("Loaded cage fields describe the item, not a crafting recipe.");
				}
				switch (text7)
				{
				case "displayName":
					cageDefinition.Name = text8;
					break;
				case "description":
					cageDefinition.Description = text8;
					break;
				case "useModel":
					cageDefinition.UseModel = text8;
					break;
				case "craftingStation":
					cageDefinition.Station = text8;
					break;
				case "stationLevel":
					if (!int.TryParse(text8, out cageDefinition.StationLevel) || cageDefinition.StationLevel < 1 || cageDefinition.StationLevel > 10)
					{
						throw new FormatException("stationLevel must be 1–10.");
					}
					break;
				case "weightPerUnit":
					cageDefinition.Weight = Number(text8);
					break;
				case "loadedWeightPerUnit":
					dictionary[cageDefinition.Item] = Number(text8);
					result.Migrated = true;
					break;
				default:
					throw new FormatException("Unknown field: " + text7);
				}
			}
			if (EmptyNames.Any((string item2) => result.FindItem(item2) == null))
			{
				throw new FormatException("All three empty cage definitions are required.");
			}
			string[] emptyNames = EmptyNames;
			foreach (string text9 in emptyNames)
			{
				CageDefinition cageDefinition3 = result.FindItem(text9);
				CageDefinition cageDefinition4 = result.FindItem(cageDefinition3.LoadedItem);
				if (cageDefinition4 == null)
				{
					cageDefinition4 = new CageDefinition
					{
						Item = cageDefinition3.LoadedItem,
						EmptyItem = text9,
						LoadedItem = cageDefinition3.LoadedItem,
						IsLoaded = true,
						Animal = cageDefinition3.Animal,
						Name = cageDefinition3.Name + " (Loaded)",
						Description = cageDefinition3.Description,
						UseModel = cageDefinition3.UseModel,
						Station = cageDefinition3.Station,
						StationLevel = cageDefinition3.StationLevel,
						Weight = (dictionary.TryGetValue(text9, out var value) ? value : cageDefinition3.LoadedWeight)
					};
					result.Cages.Add(cageDefinition4);
					result.Migrated = true;
				}
				else if (dictionary.ContainsKey(text9))
				{
					throw new FormatException("Use the loaded cage's weightPerUnit instead of also specifying loadedWeightPerUnit.");
				}
				cageDefinition3.LoadedWeight = (cageDefinition4.LoadedWeight = cageDefinition4.Weight);
			}
			result.Cages.Sort((CageDefinition a, CageDefinition b) => Array.IndexOf(ItemNames, a.Item).CompareTo(Array.IndexOf(ItemNames, b.Item)));
			foreach (CageDefinition cage in result.Cages)
			{
				if (string.IsNullOrWhiteSpace(cage.Name) || cage.Name.Length > 120 || cage.Description.Length > 1000 || cage.Station.Length > 128 || cage.UseModel.Length > 128 || cage.Ingredients.Any((Ingredient x) => x.Item.Length > 128) || (!cage.IsLoaded && cage.Ingredients.Count == 0) || !Regex.IsMatch(cage.Station, "^[A-Za-z_][A-Za-z0-9_]*$") || !Regex.IsMatch(cage.UseModel, "^[A-Za-z_][A-Za-z0-9_]*$") || RegisteredNames.Contains(cage.UseModel))
				{
					throw new FormatException("Invalid or incomplete cage: " + cage.Item);
				}
			}
			result.PlacementIconAdded = !flag;
			if (result.Migrated)
			{
				result.Yaml = result.ToYaml();
			}
			else if (result.PlacementIconAdded)
			{
				result.Yaml = text.TrimStart('\ufeff').TrimEnd('\r', '\n') + "\n\n# Build-menu icon: icons/<placement icon>.png; missing files use Wood.\n\nplacement icon: " + result.PlacementIcon + "\n";
			}
			if (Encoding.UTF8.GetByteCount(result.Yaml) > 32768)
			{
				throw new FormatException("Updated cages.yaml exceeds 32 KiB.");
			}
			return result;
		}

		internal string ToYaml()
		{
			StringBuilder stringBuilder = new StringBuilder("# TamedTransport cages. Server/host controls definitions in multiplayer.\n# Models use useModel; icons use icons/<top-level cage name>.png.\n# Only empty cages are craftable; loaded cages are created by capturing offspring.\n\n");
			string[] emptyNames = EmptyNames;
			foreach (string text in emptyNames)
			{
				string[] array = new string[2]
				{
					text,
					ItemForState(text, loaded: true)
				};
				foreach (string text2 in array)
				{
					CageDefinition cageDefinition = FindItem(text2);
					stringBuilder.Append(text2).Append(":\n  basics:\n    - displayName: ").Append(Quote(cageDefinition.Name))
						.Append("\n    - description: ")
						.Append(Quote(cageDefinition.Description));
					stringBuilder.Append("\n    - useModel: ").Append(cageDefinition.UseModel).Append("\n    - weightPerUnit: ")
						.Append(cageDefinition.Weight.ToString(CultureInfo.InvariantCulture))
						.Append('\n');
					if (!cageDefinition.IsLoaded)
					{
						stringBuilder.Append("    - craftingStation: ").Append(cageDefinition.Station).Append("\n    - stationLevel: ")
							.Append(cageDefinition.StationLevel)
							.Append("\n  ingredients:\n");
						foreach (Ingredient ingredient in cageDefinition.Ingredients)
						{
							stringBuilder.Append("    - ").Append(ingredient.Item).Append('[')
								.Append(ingredient.Amount)
								.Append("]\n");
						}
					}
					stringBuilder.Append('\n');
				}
			}
			stringBuilder.Append("# Build-menu icon: icons/<placement icon>.png; missing files use Wood.\n\nplacement icon: ").Append(PlacementIcon).Append('\n');
			return stringBuilder.ToString();
		}

		private static string Quote(string value)
		{
			return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\n", "\\n") + "\"";
		}

		private static float Number(string s)
		{
			if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || float.IsNaN(result) || float.IsInfinity(result) || result < 0f || result > 100000f)
			{
				throw new FormatException("Weight must be 0–100000.");
			}
			return result;
		}

		private static string StripComment(string s)
		{
			char c = '\0';
			bool flag = false;
			for (int i = 0; i < s.Length; i++)
			{
				char c2 = s[i];
				if (flag)
				{
					flag = false;
				}
				else if (c == '"' && c2 == '\\')
				{
					flag = true;
				}
				else if (c != 0)
				{
					if (c2 == c)
					{
						c = '\0';
					}
				}
				else if (c2 == '\'' || c2 == '"')
				{
					c = c2;
				}
				else if (c2 == '#')
				{
					return s.Substring(0, i);
				}
			}
			if (c != 0)
			{
				throw new FormatException("Unterminated quoted string.");
			}
			return s;
		}

		private static string Scalar(string s)
		{
			if (s.StartsWith("\""))
			{
				if (!s.EndsWith("\"") || s.Length < 2)
				{
					throw new FormatException("Invalid quoted string.");
				}
				return s.Substring(1, s.Length - 2).Replace("\\n", "\n").Replace("\\\"", "\"")
					.Replace("\\\\", "\\");
			}
			if (s.StartsWith("'"))
			{
				if (!s.EndsWith("'") || s.Length < 2)
				{
					throw new FormatException("Invalid quoted string.");
				}
				return s.Substring(1, s.Length - 2).Replace("''", "'");
			}
			if (s.Contains(": ") || s.StartsWith("&") || s.StartsWith("*") || s.StartsWith("!"))
			{
				throw new FormatException("Quote text containing ': '. YAML anchors and tags are unsupported.");
			}
			return s;
		}
	}
	internal static class GameApi
	{
		internal static readonly FieldInfo SceneHashes = AccessTools.Field(typeof(ZNetScene), "m_namedPrefabs");

		internal static readonly FieldInfo BuildTable = AccessTools.Field(typeof(Player), "m_buildPieces");

		internal static readonly FieldInfo Ghost = AccessTools.Field(typeof(Player), "m_placementGhost");

		internal static readonly FieldInfo PlacementStatus = AccessTools.Field(typeof(Player), "m_placementStatus");

		internal static readonly MethodInfo Registers = AccessTools.Method(typeof(ObjectDB), "UpdateRegisters", (Type[])null, (Type[])null);

		internal static readonly MethodInfo Changed = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null);

		internal static readonly MethodInfo RefreshPieces = AccessTools.Method(typeof(Player), "UpdateAvailablePiecesList", (Type[])null, (Type[])null);

		internal static readonly MethodInfo SetupGhost = AccessTools.Method(typeof(Player), "SetupPlacementGhost", (Type[])null, (Type[])null);

		internal static readonly MethodInfo SetPlaceMode = AccessTools.DeclaredMethod(typeof(Player), "SetPlaceMode", (Type[])null, (Type[])null);

		internal static readonly MethodInfo UpdateGhost = AccessTools.Method(typeof(Player), "UpdatePlacementGhost", (Type[])null, (Type[])null);

		internal static readonly MethodInfo TakeInput = AccessTools.DeclaredMethod(typeof(Player), "TakeInput", (Type[])null, (Type[])null);

		internal static readonly MethodInfo PeerInfo = AccessTools.Method(typeof(ZNet), "RPC_PeerInfo", (Type[])null, (Type[])null);

		internal static readonly MethodInfo Disconnect = AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null);

		internal static readonly FieldInfo WardAreas = AccessTools.Field(typeof(PrivateArea), "m_allAreas");

		internal static readonly MethodInfo WardEnabled = AccessTools.Method(typeof(PrivateArea), "IsEnabled", (Type[])null, (Type[])null);

		internal static readonly MethodInfo WardPermitted = AccessTools.Method(typeof(PrivateArea), "IsPermitted", (Type[])null, (Type[])null);

		internal static readonly MethodInfo WardInside = AccessTools.Method(typeof(PrivateArea), "IsInside", (Type[])null, (Type[])null);

		private static readonly MethodInfo CloneMethod = AccessTools.Method(typeof(object), "MemberwiseClone", (Type[])null, (Type[])null);

		private static readonly FieldInfo[] AttackInputs = new FieldInfo[4]
		{
			AccessTools.Field(typeof(Character), "m_attack"),
			AccessTools.Field(typeof(Character), "m_attackHold"),
			AccessTools.Field(typeof(Character), "m_secondaryAttack"),
			AccessTools.Field(typeof(Character), "m_secondaryAttackHold")
		};

		private static readonly FieldInfo[] AttackQueues = new FieldInfo[2]
		{
			AccessTools.Field(typeof(Player), "m_queuedAttackTimer"),
			AccessTools.Field(typeof(Player), "m_queuedSecondAttackTimer")
		};

		internal static void ClearAttackInput(Player player)
		{
			FieldInfo[] attackInputs = AttackInputs;
			foreach (FieldInfo fieldInfo in attackInputs)
			{
				fieldInfo.SetValue(player, false);
			}
			FieldInfo[] attackQueues = AttackQueues;
			foreach (FieldInfo fieldInfo2 in attackQueues)
			{
				fieldInfo2.SetValue(player, 0f);
			}
		}

		internal static T Clone<T>(T value) where T : class
		{
			return (T)CloneMethod.Invoke(value, null);
		}

		internal static void InventoryChanged(Inventory inventory)
		{
			Changed.Invoke(inventory, new object[2] { false, false });
		}

		internal static void Validate()
		{
			FieldInfo[] array = new FieldInfo[5] { SceneHashes, BuildTable, Ghost, PlacementStatus, WardAreas };
			foreach (FieldInfo fieldInfo in array)
			{
				if (fieldInfo == null)
				{
					throw new MissingFieldException("The Valheim interface has changed.");
				}
			}
			MethodInfo[] array2 = new MethodInfo[12]
			{
				Registers, Changed, RefreshPieces, SetupGhost, SetPlaceMode, UpdateGhost, TakeInput, PeerInfo, Disconnect, WardEnabled,
				WardPermitted, WardInside
			};
			foreach (MethodInfo methodInfo in array2)
			{
				if (methodInfo == null)
				{
					throw new MissingMethodException("The Valheim interface has changed.");
				}
			}
			FieldInfo[] attackInputs = AttackInputs;
			foreach (FieldInfo fieldInfo2 in attackInputs)
			{
				if (fieldInfo2 == null)
				{
					throw new MissingFieldException("The Valheim attack input interface has changed.");
				}
			}
			FieldInfo[] attackQueues = AttackQueues;
			foreach (FieldInfo fieldInfo3 in attackQueues)
			{
				if (fieldInfo3 == null)
				{
					throw new MissingFieldException("The Valheim attack queue interface has changed.");
				}
			}
		}
	}
	internal sealed class IconCatalog : IDisposable
	{
		internal const int MaxIconBytes = 1048576;

		internal const int MaxPacketBytes = 7372928;

		private static readonly string[] Names = Catalog.ItemNames.Concat(new string[1] { "dbtt_PlaceAnimal" }).ToArray();

		private readonly Dictionary<string, byte[]> bytes = new Dictionary<string, byte[]>();

		private readonly Dictionary<string, Sprite> sprites = new Dictionary<string, Sprite>();

		internal static IconCatalog Empty()
		{
			IconCatalog iconCatalog = new IconCatalog();
			string[] names = Names;
			foreach (string key in names)
			{
				iconCatalog.bytes[key] = Array.Empty<byte>();
			}
			return iconCatalog;
		}

		internal static IconCatalog Load(string directory, Catalog catalog)
		{
			IconCatalog iconCatalog = Empty();
			string[] names = Names;
			foreach (string text in names)
			{
				string text2 = Catalog.IconFileName((text == "dbtt_PlaceAnimal") ? catalog.PlacementIcon : text);
				string text3 = Path.Combine(directory, text2);
				if (File.Exists(text3))
				{
					FileInfo fileInfo = new FileInfo(text3);
					if (fileInfo.Length > 1048576)
					{
						throw new FormatException(text2 + " exceeds 1 MiB.");
					}
					iconCatalog.bytes[text] = File.ReadAllBytes(text3);
					Validate(iconCatalog.bytes[text]);
				}
			}
			return iconCatalog;
		}

		internal bool Same(IconCatalog other)
		{
			return other != null && Names.All((string name) => bytes[name].SequenceEqual(other.bytes[name]));
		}

		internal void ValidateImages()
		{
			string[] names = Names;
			foreach (string item in names)
			{
				ForItem(item, Array.Empty<Sprite>());
			}
		}

		internal void Write(ZPackage p)
		{
			string[] names = Names;
			foreach (string key in names)
			{
				p.Write(bytes[key]);
			}
		}

		internal static IconCatalog Read(ZPackage p)
		{
			IconCatalog iconCatalog = Empty();
			string[] names = Names;
			foreach (string key in names)
			{
				int pos = p.GetPos();
				int num = p.ReadInt();
				if (num < 0 || num > 1048576 || num > p.Size() - p.GetPos())
				{
					throw new FormatException("Invalid icon packet length.");
				}
				p.SetPos(pos);
				byte[] array = p.ReadByteArray();
				if (array.Length != num)
				{
					throw new FormatException("Incomplete icon packet.");
				}
				if (num > 0)
				{
					Validate(array);
				}
				iconCatalog.bytes[key] = array;
			}
			return iconCatalog;
		}

		internal Sprite[] ForItem(string item, Sprite[] fallback)
		{
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			if (!bytes.TryGetValue(item, out var value) || value.Length == 0)
			{
				return (Sprite[])fallback.Clone();
			}
			if (!sprites.TryGetValue(item, out var value2))
			{
				Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
				try
				{
					if (!ImageConversion.LoadImage(val, value, false))
					{
						throw new FormatException("Cannot decode " + Catalog.IconFileName(item));
					}
					((Object)val).name = item + "_icon";
					((Texture)val).wrapMode = (TextureWrapMode)1;
					value2 = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
					((Object)value2).name = item + "_icon";
					sprites[item] = value2;
				}
				catch
				{
					Object.Destroy((Object)(object)val);
					throw;
				}
			}
			return (Sprite[])(object)new Sprite[1] { value2 };
		}

		private static void Validate(byte[] data)
		{
			byte[] first = new byte[8] { 137, 80, 78, 71, 13, 10, 26, 10 };
			if (data.Length < 33 || data.Length > 1048576 || !first.SequenceEqual(data.Take(8)) || data[12] != 73 || data[13] != 72 || data[14] != 68 || data[15] != 82)
			{
				throw new FormatException("Icons must be PNG images.");
			}
			uint num = Size(data, 16);
			uint num2 = Size(data, 20);
			if (num < 1 || num2 < 1 || num > 1024 || num2 > 1024)
			{
				throw new FormatException("Icon dimensions must be 1–1024 pixels.");
			}
		}

		private static uint Size(byte[] data, int at)
		{
			return (uint)((data[at] << 24) | (data[at + 1] << 16) | (data[at + 2] << 8) | data[at + 3]);
		}

		public void Dispose()
		{
			foreach (Sprite value in sprites.Values)
			{
				if (Object.op_Implicit((Object)(object)value))
				{
					Texture2D texture = value.texture;
					Object.Destroy((Object)(object)value);
					if (Object.op_Implicit((Object)(object)texture))
					{
						Object.Destroy((Object)(object)texture);
					}
				}
			}
			sprites.Clear();
		}
	}
	internal sealed class Network : IDisposable
	{
		private sealed class Connection
		{
			internal ZNetPeer Peer;

			internal bool Verified;

			internal bool Ready;

			internal float Started;

			internal ZPackage Deferred;

			internal readonly SettingsTransfer Settings = new SettingsTransfer();
		}

		internal const string Hello = "dannanboi.tamedtransport.Hello";

		internal const string Settings = "dannanboi.tamedtransport.Settings";

		internal const string Ack = "dannanboi.tamedtransport.Ack";

		internal const string Gameplay = "dannanboi.tamedtransport.Gameplay";

		internal const string Request = "dannanboi.tamedtransport.Transport";

		internal const string Reply = "dannanboi.tamedtransport.TransportResult";

		private readonly Dictionary<ZRpc, Connection> connections = new Dictionary<ZRpc, Connection>();

		private readonly Dictionary<long, float> lastRequest = new Dictionary<long, float>();

		private readonly Queue<Receipt> reconcile = new Queue<Receipt>();

		private ZNet net;

		private ZRoutedRpc routed;

		private ReceiptStore receipts;

		private bool ledgerFailed;

		private int settingsSerial;

		private byte[][] settingsPackets;

		private float nextMaintenance;

		private static readonly int GroundMask = LayerMask.GetMask(new string[4] { "terrain", "piece", "static_solid", "Default" });

		internal void Begin(ZNet value)
		{
			if (net == value)
			{
				return;
			}
			foreach (ZRpc key in connections.Keys)
			{
				key.Unregister("dannanboi.tamedtransport.Hello");
				key.Unregister("dannanboi.tamedtransport.Settings");
				key.Unregister("dannanboi.tamedtransport.Ack");
				key.Unregister("dannanboi.tamedtransport.Gameplay");
			}
			connections.Clear();
			lastRequest.Clear();
			reconcile.Clear();
			receipts?.Dispose();
			receipts = null;
			ledgerFailed = false;
			routed = null;
			net = value;
			settingsPackets = null;
			nextMaintenance = 0f;
			Plugin.Instance.SetSession((Object)(object)value == (Object)null || value.IsServer());
		}

		internal void Connect(ZNet value, ZNetPeer peer)
		{
			Begin(value);
			if (peer?.m_rpc == null || connections.ContainsKey(peer.m_rpc))
			{
				return;
			}
			Connection c = new Connection
			{
				Peer = peer,
				Started = Time.realtimeSinceStartup
			};
			connections.Add(peer.m_rpc, c);
			peer.m_rpc.Register<string>("dannanboi.tamedtransport.Hello", (Action<ZRpc, string>)delegate(ZRpc rpc, string version)
			{
				if (version != "1.0.0")
				{
					Reject(c, "TamedTransport requires matching version 1.0.0 on clients and server.");
				}
				else if (!c.Verified)
				{
					c.Verified = true;
					if (net.IsServer())
					{
						SendSettings(c);
					}
				}
			});
			peer.m_rpc.Register<ZPackage>("dannanboi.tamedtransport.Settings", (Action<ZRpc, ZPackage>)delegate(ZRpc rpc, ZPackage package)
			{
				//IL_0102: Unknown result type (might be due to invalid IL or missing references)
				//IL_0109: Expected O, but got Unknown
				if (net.IsServer() || !c.Peer.m_server || !c.Verified)
				{
					return;
				}
				try
				{
					if (package == null || package.Size() > 32832 || package.ReadInt() != 2)
					{
						throw new FormatException("Invalid settings packet.");
					}
					int id = package.ReadInt();
					int index = package.ReadInt();
					int total = package.ReadInt();
					int pos = package.GetPos();
					int num = package.ReadInt();
					if (num < 1 || num > 32768 || num != package.Size() - package.GetPos())
					{
						throw new FormatException("Invalid settings chunk length.");
					}
					package.SetPos(pos);
					byte[] array = c.Settings.Accept(id, index, total, package.ReadByteArray());
					if (array != null)
					{
						ZPackage val = new ZPackage(array);
						if (val.ReadInt() != 4)
						{
							throw new FormatException("Invalid settings contents.");
						}
						bool enabled = val.ReadBool();
						bool adults = val.ReadBool();
						bool untamed = val.ReadBool();
						bool portals = val.ReadBool();
						string text = val.ReadString();
						IconCatalog icons = IconCatalog.Read(val);
						if (val.GetPos() != val.Size())
						{
							throw new FormatException("Unexpected settings data.");
						}
						Plugin.Instance.ApplyServer(Catalog.Parse(text), enabled, adults, untamed, portals, icons);
						c.Ready = true;
						rpc.Invoke("dannanboi.tamedtransport.Ack", new object[1] { 2 });
						Resume(c);
					}
				}
				catch (Exception ex)
				{
					Reject(c, "TamedTransport settings could not be loaded: " + ex.Message);
				}
			});
			peer.m_rpc.Register<int>("dannanboi.tamedtransport.Ack", (Action<ZRpc, int>)delegate(ZRpc rpc, int protocol)
			{
				if (net.IsServer() && c.Verified && protocol == 2)
				{
					c.Ready = true;
					Resume(c);
				}
			});
			peer.m_rpc.Register<int>("dannanboi.tamedtransport.Gameplay", (Action<ZRpc, int>)delegate(ZRpc rpc, int mask)
			{
				if (net.IsServer() || !c.Peer.m_server || !c.Verified || !c.Ready)
				{
					return;
				}
				try
				{
					Plugin.Instance.ApplyGameplay(new GameplayState(mask));
				}
				catch (Exception ex)
				{
					Reject(c, "TamedTransport gameplay settings could not be loaded: " + ex.Message);
				}
			});
			peer.m_rpc.Invoke("dannanboi.tamedtransport.Hello", new object[1] { "1.0.0" });
		}

		private void Reject(Connection c, string reason)
		{
			Plugin.Warn(reason);
			c.Deferred = null;
			if (net.IsServer())
			{
				c.Peer.m_rpc.Invoke("Error", new object[1] { 3 });
			}
			GameApi.Disconnect.Invoke(net, new object[1] { c.Peer });
		}

		internal bool Admit(ZRpc rpc, ZPackage package)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Expected O, but got Unknown
			if (!connections.TryGetValue(rpc, out var value))
			{
				return false;
			}
			if (value.Ready)
			{
				return true;
			}
			if (value.Deferred == null && package != null && package.Size() < 1048576)
			{
				value.Deferred = new ZPackage(package.GetArray());
				value.Deferred.SetPos(package.GetPos());
			}
			return false;
		}

		private void Resume(Connection c)
		{
			if (c.Deferred != null)
			{
				ZPackage deferred = c.Deferred;
				c.Deferred = null;
				GameApi.PeerInfo.Invoke(net, new object[2]
				{
					c.Peer.m_rpc,
					deferred
				});
			}
		}

		private byte[][] BuildSettingsPackets()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(4);
			val.Write(Plugin.Instance.LocalEnabled);
			val.Write(Plugin.Instance.LocalAdults);
			val.Write(Plugin.Instance.LocalUntamed);
			val.Write(Plugin.Instance.LocalPortals);
			val.Write(Plugin.Instance.Local.Yaml);
			Plugin.Instance.LocalIcons.Write(val);
			byte[] array = val.GetArray();
			settingsSerial = ((settingsSerial == int.MaxValue) ? 1 : (settingsSerial + 1));
			byte[][] array2 = new byte[(array.Length + 32768 - 1) / 32768][];
			int num = 0;
			int num2 = 0;
			while (num < array.Length)
			{
				byte[] array3 = new byte[Math.Min(32768, array.Length - num)];
				Buffer.BlockCopy(array, num, array3, 0, array3.Length);
				ZPackage val2 = new ZPackage();
				val2.Write(2);
				val2.Write(settingsSerial);
				val2.Write(num2);
				val2.Write(array.Length);
				val2.Write(array3);
				array2[num2] = val2.GetArray();
				num += 32768;
				num2++;
			}
			return array2;
		}

		private void SendSettings(Connection c)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			if (settingsPackets == null)
			{
				settingsPackets = BuildSettingsPackets();
			}
			byte[][] array = settingsPackets;
			foreach (byte[] array2 in array)
			{
				c.Peer.m_rpc.Invoke("dannanboi.tamedtransport.Settings", new object[1] { (object)new ZPackage(array2) });
			}
		}

		internal void Publish()
		{
			settingsPackets = null;
			if (!Object.op_Implicit((Object)(object)net) || !net.IsServer())
			{
				return;
			}
			Connection[] array = connections.Values.ToArray();
			foreach (Connection connection in array)
			{
				if (connection.Verified && connection.Peer.m_rpc.IsConnected())
				{
					SendSettings(connection);
				}
			}
		}

		internal void PublishGameplay()
		{
			settingsPackets = null;
			if (!Object.op_Implicit((Object)(object)net) || !net.IsServer())
			{
				return;
			}
			int mask = Plugin.Instance.LocalState.Mask;
			Connection[] array = connections.Values.ToArray();
			foreach (Connection connection in array)
			{
				if (connection.Verified && connection.Peer.m_rpc.IsConnected())
				{
					if (connection.Ready)
					{
						connection.Peer.m_rpc.Invoke("dannanboi.tamedtransport.Gameplay", new object[1] { mask });
					}
					else
					{
						SendSettings(connection);
					}
				}
			}
		}

		private void MaintainConnections()
		{
			if (Time.realtimeSinceStartup < nextMaintenance)
			{
				return;
			}
			nextMaintenance = Time.realtimeSinceStartup + 1f;
			KeyValuePair<ZRpc, Connection>[] array = connections.ToArray();
			for (int i = 0; i < array.Length; i++)
			{
				KeyValuePair<ZRpc, Connection> keyValuePair = array[i];
				Connection value = keyValuePair.Value;
				if (!keyValuePair.Key.IsConnected())
				{
					lastRequest.Remove(value.Peer.m_uid);
					connections.Remove(keyValuePair.Key);
				}
				else if (!value.Ready && Time.realtimeSinceStartup - value.Started > 30f)
				{
					Reject(value, "TamedTransport handshake timed out. Install 1.0.0 on clients and server.");
					lastRequest.Remove(value.Peer.m_uid);
					connections.Remove(keyValuePair.Key);
				}
			}
		}

		internal void Tick()
		{
			Begin(ZNet.instance);
			if (!Object.op_Implicit((Object)(object)net))
			{
				return;
			}
			MaintainConnections();
			if (ZRoutedRpc.instance != null && routed != ZRoutedRpc.instance)
			{
				routed = ZRoutedRpc.instance;
				routed.Register<ZPackage>("dannanboi.tamedtransport.Transport", (Action<long, ZPackage>)ReceiveRequest);
				routed.Register<ZPackage>("dannanboi.tamedtransport.TransportResult", (Action<long, ZPackage>)ReceiveReply);
			}
			if (net.IsServer() && receipts == null && !ledgerFailed && Object.op_Implicit((Object)(object)ZNetScene.instance) && ZDOMan.instance != null)
			{
				try
				{
					receipts = new ReceiptStore(Path.Combine(Plugin.Instance.DirectoryPath, "state", net.GetWorldUID() + ".receipts"));
					foreach (Receipt value in receipts.Records.Values)
					{
						if (value.SourceId != 0)
						{
							reconcile.Enqueue(value);
						}
					}
				}
				catch (Exception ex)
				{
					ledgerFailed = true;
					Plugin.Warn("Animal transport is unavailable: " + ex.Message);
				}
			}
			for (int i = 0; i < 32; i++)
			{
				if (reconcile.Count <= 0)
				{
					break;
				}
				RemoveSource(reconcile.Dequeue());
			}
		}

		internal void Send(ZPackage request)
		{
			if (!((Object)(object)net == (Object)null) && routed != null)
			{
				long num = (net.IsServer() ? ZNet.GetUID() : (net.GetServerPeer()?.m_uid ?? 0));
				if (num != 0)
				{
					routed.InvokeRoutedRPC(num, "dannanboi.tamedtransport.Transport", new object[1] { request });
				}
			}
		}

		private void ReceiveReply(long sender, ZPackage package)
		{
			if (Object.op_Implicit((Object)(object)net) && !(net.IsServer() ? (sender != ZNet.GetUID()) : (sender != net.GetServerPeer()?.m_uid)))
			{
				Plugin.Instance.Transport.Receive(package);
			}
		}

		private ZDO PlayerFor(long sender)
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			if (sender == ZNet.GetUID() && Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				return ZDOMan.instance.GetZDO(((Character)Player.m_localPlayer).GetZDOID());
			}
			ZNetPeer val = net.GetPeers().Find((ZNetPeer x) => x.m_uid == sender);
			if (val == null || !connections.TryGetValue(val.m_rpc, out var value) || !value.Ready)
			{
				return null;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(val.m_characterID);
			return (zDO != null && zDO.GetOwner() == sender) ? zDO : null;
		}

		private void ReceiveRequest(long sender, ZPackage p)
		{
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_05d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0203: Unknown result type (might be due to invalid IL or missing references)
			//IL_0250: Unknown result type (might be due to invalid IL or missing references)
			//IL_0257: Unknown result type (might be due to invalid IL or missing references)
			//IL_06a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_0328: Unknown result type (might be due to invalid IL or missing references)
			//IL_06b1: Unknown result type (might be due to invalid IL or missing references)
			//IL_06bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_06c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e2: Unknown result type (might be due to invalid IL or missing references)
			//IL_06e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_06ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_06f4: Invalid comparison between Unknown and I4
			//IL_0709: Unknown result type (might be due to invalid IL or missing references)
			//IL_06fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0731: Unknown result type (might be due to invalid IL or missing references)
			//IL_0733: Unknown result type (might be due to invalid IL or missing references)
			//IL_073d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0742: Unknown result type (might be due to invalid IL or missing references)
			//IL_0747: Unknown result type (might be due to invalid IL or missing references)
			//IL_0761: Unknown result type (might be due to invalid IL or missing references)
			//IL_0766: Unknown result type (might be due to invalid IL or missing references)
			//IL_07b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_081c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0858: Unknown result type (might be due to invalid IL or missing references)
			//IL_0865: Unknown result type (might be due to invalid IL or missing references)
			//IL_09a8: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)net) || !net.IsServer() || p == null || p.Size() > 8192)
			{
				return;
			}
			string text = "";
			int num = 0;
			try
			{
				if (receipts == null)
				{
					throw new InvalidOperationException("Transport storage is not ready. Please try again.");
				}
				ZDO val = PlayerFor(sender);
				if (val == null || (lastRequest.TryGetValue(sender, out var value) && Time.realtimeSinceStartup - value < 0.25f))
				{
					return;
				}
				lastRequest[sender] = Time.realtimeSinceStartup;
				if (p.ReadInt() != 1)
				{
					return;
				}
				num = p.ReadInt();
				text = p.ReadString();
				if (!Guid.TryParseExact(text, "N", out var _))
				{
					return;
				}
				string item = p.ReadString();
				CageDefinition cageDefinition = Plugin.Instance.Current.FindItem(item);
				if (cageDefinition == null)
				{
					return;
				}
				switch (num)
				{
				case 1:
				{
					ZDOID val7 = p.ReadZDOID();
					if (p.GetPos() != p.Size())
					{
						break;
					}
					Receipt receipt3 = receipts.Find(text);
					if (receipt3 != null)
					{
						if (receipt3.SourceUser == ((ZDOID)(ref val7)).UserID && receipt3.SourceId == ((ZDOID)(ref val7)).ID)
						{
							RemoveSource(receipt3);
							Result(sender, text, num, success: true, receipt3.Released ? "" : receipt3.Payload);
						}
						break;
					}
					if (!Plugin.Instance.Enabled)
					{
						throw new InvalidOperationException("TamedTransport is disabled by the server.");
					}
					if (cageDefinition.IsLoaded)
					{
						throw new InvalidOperationException("Use the matching empty cage to pick up an animal.");
					}
					ZDO zDO = ZDOMan.instance.GetZDO(val7);
					if (zDO == null)
					{
						throw new InvalidOperationException("The animal is no longer available.");
					}
					if (receipts.FindSource(((ZDOID)(ref val7)).UserID, ((ZDOID)(ref val7)).ID) != null)
					{
						throw new InvalidOperationException("This animal has already been picked up.");
					}
					if (Vector3.Distance(val.GetPosition(), zDO.GetPosition()) > 4f)
					{
						throw new InvalidOperationException("Move closer to the animal.");
					}
					string text2 = Catalog.AdultFor(cageDefinition.Animal);
					string text3 = ((zDO.GetPrefab() == StringExtensionMethods.GetStableHashCode(cageDefinition.Animal)) ? cageDefinition.Animal : ((zDO.GetPrefab() == StringExtensionMethods.GetStableHashCode(text2)) ? text2 : null));
					if (text3 == null)
					{
						throw new InvalidOperationException("This cage does not match the animal.");
					}
					bool tamed = zDO.GetBool(ZDOVars.s_tamed, false);
					if (!TransportPolicy.CanCapture(Plugin.Instance.Ready, Plugin.Instance.Enabled, text3, tamed, Plugin.Instance.AllowAdults, Plugin.Instance.AllowUntamed))
					{
						throw new InvalidOperationException("The server settings do not allow capturing this animal.");
					}
					if (!WardAllows(zDO.GetPosition(), val.GetLong(ZDOVars.s_playerID, 0L)))
					{
						throw new InvalidOperationException("This area is protected by a ward.");
					}
					GameObject prefab2 = ZNetScene.instance.GetPrefab(text3);
					Character val8 = (Object.op_Implicit((Object)(object)prefab2) ? prefab2.GetComponent<Character>() : null);
					if (!Object.op_Implicit((Object)(object)val8))
					{
						throw new InvalidOperationException("The animal prefab could not be found.");
					}
					float num2 = 0f;
					if (!Catalog.IsAdult(text3))
					{
						Growup component = prefab2.GetComponent<Growup>();
						if (!Object.op_Implicit((Object)(object)component))
						{
							throw new InvalidOperationException("The offspring prefab has no growth component.");
						}
						long num3 = zDO.GetLong(ZDOVars.s_spawnTime, net.GetTime().Ticks);
						num2 = Mathf.Max(0f, (float)(net.GetTime().Ticks - num3) / 10000000f);
						if (num2 >= component.m_growTime)
						{
							throw new InvalidOperationException("This animal is already ready to grow up.");
						}
					}
					float num4 = zDO.GetFloat(ZDOVars.s_health, val8.m_health);
					float num5 = zDO.GetFloat(ZDOVars.s_maxHealth, val8.m_health);
					if (num4 <= 0f)
					{
						throw new InvalidOperationException("This animal is dead.");
					}
					string text4 = zDO.GetString(ZDOVars.s_tamedName, zDO.GetString(ZDOVars.s_overrideHoverName, ""));
					AnimalData animalData3 = new AnimalData
					{
						Token = text,
						Prefab = text3,
						Name = (string.IsNullOrEmpty(text4) ? val8.m_name : text4),
						CustomName = text4,
						Level = zDO.GetInt(ZDOVars.s_level, 1),
						Health = Mathf.Clamp(num4 / Mathf.Max(1f, num5), 0.001f, 1f),
						Age = num2,
						Tamed = tamed,
						TamingTimeLeft = zDO.GetFloat(ZDOVars.s_tameTimeLeft, -1f)
					};
					Receipt receipt4 = new Receipt
					{
						Token = text,
						Payload = animalData3.Encode(),
						SourceUser = ((ZDOID)(ref val7)).UserID,
						SourceId = ((ZDOID)(ref val7)).ID,
						Captor = val.GetLong(ZDOVars.s_playerID, 0L)
					};
					receipts.Put(receipt4);
					RemoveSource(receipt4);
					Result(sender, text, num, success: true, receipt4.Payload);
					break;
				}
				case 2:
				{
					AnimalData animalData = AnimalData.Decode(p.ReadString());
					Vector3 val2 = p.ReadVector3();
					Quaternion val3 = p.ReadQuaternion();
					if (p.GetPos() != p.Size() || animalData.Token != text || !Catalog.MatchesAnimal(cageDefinition.Animal, animalData.Prefab))
					{
						break;
					}
					Receipt receipt = receipts.Find(text);
					if (receipt != null && receipt.Released)
					{
						Result(sender, text, num, success: true, "");
						break;
					}
					if (!Plugin.Instance.Enabled)
					{
						throw new InvalidOperationException("TamedTransport is disabled by the server.");
					}
					AnimalData animalData2 = ((receipt == null) ? animalData : AnimalData.Decode(receipt.Payload));
					if (!Catalog.MatchesAnimal(cageDefinition.Animal, animalData2.Prefab))
					{
						break;
					}
					if (!Finite(val2) || !Finite(val3) || Vector3.Distance(val.GetPosition(), val2) > 8f)
					{
						throw new InvalidOperationException("Choose a nearby position on the ground.");
					}
					PlacementStatus val4 = PlacementRules.Restriction(val2, val.GetPosition());
					if ((int)val4 > 0)
					{
						throw new InvalidOperationException(PlacementRules.Message(val4));
					}
					if (!WardAllows(val2, val.GetLong(ZDOVars.s_playerID, 0L)))
					{
						throw new InvalidOperationException("This area is protected by a ward.");
					}
					RaycastHit val5 = default(RaycastHit);
					if (!Physics.Raycast(val2 + Vector3.up * 0.25f, Vector3.down, ref val5, 0.6f, GroundMask) || Vector3.Distance(((RaycastHit)(ref val5)).point, val2) > 0.3f)
					{
						throw new InvalidOperationException("Choose a supported position for the animal.");
					}
					GameObject prefab = ZNetScene.instance.GetPrefab(animalData2.Prefab);
					if (!Object.op_Implicit((Object)(object)prefab))
					{
						throw new InvalidOperationException("The animal prefab could not be found.");
					}
					if (!Registry.HasAnimalSpace(val2, animalData2.Prefab))
					{
						throw new InvalidOperationException("There is not enough space for the animal.");
					}
					if (receipt == null)
					{
						receipt = new Receipt
						{
							Token = text,
							Payload = animalData2.Encode()
						};
						receipts.Put(receipt);
					}
					RemoveSource(receipt);
					ZDO val6 = ZDOMan.instance.CreateNewZDO(val2, StringExtensionMethods.GetStableHashCode(animalData2.Prefab));
					try
					{
						val6.SetPrefab(StringExtensionMethods.GetStableHashCode(animalData2.Prefab));
						val6.Persistent = true;
						val6.SetType(prefab.GetComponent<ZNetView>().m_type);
						val6.SetRotation(val3);
						val6.SetOwner(sender);
						val6.Set(ZDOVars.s_tamed, animalData2.Tamed);
						val6.Set(ZDOVars.s_level, animalData2.Level, false);
						val6.Set(ZDOVars.s_tamedName, animalData2.CustomName);
						if (animalData2.TamingTimeLeft >= 0f)
						{
							val6.Set(ZDOVars.s_tameTimeLeft, animalData2.TamingTimeLeft);
						}
						val6.Set(ZDOVars.s_overrideHoverName, animalData2.CustomName);
						val6.Set(ZDOVars.s_spawnTime, net.GetTime().Ticks - (long)(animalData2.Age * 10000000f));
						val6.Set("dannanboi.tamedtransport.health", animalData2.Health);
						val6.Set("dannanboi.tamedtransport.released", text);
						Receipt receipt2 = new Receipt
						{
							Token = receipt.Token,
							Payload = receipt.Payload,
							SourceUser = receipt.SourceUser,
							SourceId = receipt.SourceId,
							Captor = receipt.Captor,
							Released = true
						};
						receipts.Put(receipt2);
						ZDOMan.instance.ForceSendZDO(val6.m_uid);
					}
					catch
					{
						val6.SetOwner(ZNet.GetUID());
						ZDOMan.instance.DestroyZDO(val6);
						throw;
					}
					Result(sender, text, num, success: true, "");
					break;
				}
				}
			}
			catch (Exception ex)
			{
				Plugin.Warn("Transport request: " + ex.Message);
				if (text.Length == 32 && (num != 1 || receipts?.Find(text) == null))
				{
					Result(sender, text, num, success: false, ex.Message);
				}
			}
		}

		private void RemoveSource(Receipt r)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			if (r.SourceId == 0)
			{
				return;
			}
			ZDO zDO = ZDOMan.instance.GetZDO(new ZDOID(r.SourceUser, r.SourceId));
			if (zDO != null)
			{
				zDO.SetOwner(ZNet.GetUID());
				ZNetView val = ZNetScene.instance.FindInstance(zDO);
				if (Object.op_Implicit((Object)(object)val))
				{
					ZNetScene.instance.Destroy(((Component)val).gameObject);
				}
				else
				{
					ZDOMan.instance.DestroyZDO(zDO);
				}
			}
		}

		private void Result(long sender, string token, int op, bool success, string message)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(token);
			val.Write(op);
			val.Write(success);
			val.Write(message);
			routed.InvokeRoutedRPC(sender, "dannanboi.tamedtransport.TransportResult", new object[1] { val });
		}

		private static bool Finite(Vector3 v)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			return AnimalData.Finite(v.x) && AnimalData.Finite(v.y) && AnimalData.Finite(v.z);
		}

		private static bool Finite(Quaternion q)
		{
			//IL_0000: 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_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0096: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			return AnimalData.Finite(q.x) && AnimalData.Finite(q.y) && AnimalData.Finite(q.z) && AnimalData.Finite(q.w) && q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w > 0.9f && q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w < 1.1f;
		}

		private static bool WardAllows(Vector3 position, long player)
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			List<PrivateArea> list = (List<PrivateArea>)GameApi.WardAreas.GetValue(null);
			bool flag = false;
			foreach (PrivateArea item in list)
			{
				if (!Object.op_Implicit((Object)(object)item) || !(bool)GameApi.WardEnabled.Invoke(item, null) || !(bool)GameApi.WardInside.Invoke(item, new object[2] { position, 0f }))
				{
					continue;
				}
				flag = true;
				if (!(bool)GameApi.WardPermitted.Invoke(item, new object[1] { player }))
				{
					ZNetView component = ((Component)item).GetComponent<ZNetView>();
					if (component == null)
					{
						continue;
					}
					ZDO zDO = component.GetZDO();
					if (((zDO != null) ? new long?(zDO.GetLong("creator", 0L)) : ((long?)null)) != player)
					{
						continue;
					}
				}
				return true;
			}
			return !flag;
		}

		public void Dispose()
		{
			Begin(null);
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	internal static class DatabaseAwake
	{
		private static void Postfix(ObjectDB __instance)
		{
			Plugin.Instance.Registry.RegisterDB(__instance);
		}
	}
	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	internal static class DatabaseCopy
	{
		private static void Postfix(ObjectDB __instance)
		{
			Plugin.Instance.Registry.RegisterDB(__instance);
		}
	}
	[HarmonyPatch]
	internal static class InventoryMigration
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			return from m in typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.Public)
				where m.Name == "Load"
				select m;
		}

		private static void Postfix(Inventory __instance)
		{
			Plugin.Instance.Registry.NormalizeInventory(__instance);
		}
	}
	[HarmonyPatch(typeof(Inventory), "Changed")]
	internal static class InventoryCacheInvalidation
	{
		[HarmonyPriority(800)]
		private static void Prefix(Inventory __instance)
		{
			Plugin.Instance?.Transport?.InvalidateInventory(__instance);
		}
	}
	[HarmonyPatch(typeof(ItemDrop), "Load")]
	internal static class DropMigration
	{
		private static void Postfix(ItemDrop __instance)
		{
			Plugin.Instance.Registry.NormalizeCage(__instance.m_itemData);
		}
	}
	[HarmonyPatch(typeof(Inventory), "IsTeleportable")]
	internal static class CagePortalRestriction
	{
		private static void Postfix(Inventory __instance, bool __0, ref bool __result)
		{
			if (__result && !__0 && !Plugin.Instance.AllowPortals && __instance.GetAllItems().Any(Transport.BlocksPortal))
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(Player), "AddKnownItem")]
	internal static class LoadedCageDiscovery
	{
		private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			MethodInfo queue = AccessTools.Method(typeof(MessageHud), "QueueUnlockMsg", new Type[3]
			{
				typeof(Sprite),
				typeof(string),
				typeof(string)
			}, (Type[])null);
			MethodInfo replacement = AccessTools.Method(typeof(LoadedCageDiscovery), "QueueDiscovery", (Type[])null, (Type[])null);
			int calls = 0;
			foreach (CodeInstruction instruction in instructions)
			{
				if (CodeInstructionExtensions.Calls(instruction, queue))
				{
					yield return CodeInstructionExtensions.MoveBlocksFrom(CodeInstructionExtensions.MoveLabelsFrom(new CodeInstruction(OpCodes.Ldarg_1, (object)null), instruction), instruction);
					instruction.opcode = OpCodes.Call;
					instruction.operand = replacement;
					calls++;
				}
				yield return instruction;
			}
			if (calls == 0)
			{
				throw new InvalidOperationException("The native item discovery notification hook could not be found.");
			}
		}

		private static void QueueDiscovery(MessageHud hud, Sprite icon, string topic, string text, ItemData item)
		{
			if (!Catalog.IsLoadedItem(Transport.ItemName(item)))
			{
				hud.QueueUnlockMsg(icon, topic, text);
			}
		}
	}
	[HarmonyPatch(typeof(ZNetScene), "Awake")]
	internal static class SceneAwake
	{
		private static void Prefix(ZNetScene __instance)
		{
			Plugin.Instance.Registry.RegisterScene(__instance, before: true);
		}

		private static void Postfix(ZNetScene __instance)
		{
			Plugin.Instance.Registry.RegisterScene(__instance, before: false);
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class ConnectionHook
	{
		[HarmonyPriority(800)]
		private static void Prefix(ZNet __instance, ZNetPeer __0)
		{
			Plugin.Instance.Network.Connect(__instance, __0);
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class AdmissionHook
	{
		[HarmonyPriority(800)]
		private static bool Prefix(ZRpc __0, ZPackage __1)
		{
			return Plugin.Instance.Network.Admit(__0, __1);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Shutdown")]
	internal static class ShutdownHook
	{
		private static void Postfix()
		{
			Plugin.Instance.Network.Begin(null);
		}
	}
	[HarmonyPatch(typeof(Character), "GetHoverText")]
	internal static class HoverHook
	{
		private static void Postfix(Character __instance, ref string __result)
		{
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			if (Plugin.Instance.Transport.EmptyCage(__instance) != null)
			{
				string text = Plugin.Instance.PickupMessage.Value.Replace("<animal>", __instance.GetHoverName());
				__result = ((__result.Length == 0) ? __instance.GetHoverName() : __result) + "\n[<color=yellow><b>" + ((object)Plugin.Instance.PickupKey.Value/*cast due to .constrained prefix*/).ToString() + "</b></color>] " + text;
			}
		}
	}
	[HarmonyPatch(typeof(ItemData), "GetWeight")]
	internal static class WeightHook
	{
		private static void Postfix(ItemData __instance, int __0, ref float __result)
		{
			if (Transport.IsCage(__instance))
			{
				CageDefinition cageDefinition = Plugin.Instance.Current.FindItem(Transport.ItemName(__instance));
				if (cageDefinition != null)
				{
					__result = (Transport.Occupied(__instance) ? cageDefinition.LoadedWeight : cageDefinition.Weight) * (float)((__0 < 0) ? __instance.m_stack : __0);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class TooltipHook
	{
		private static MethodBase TargetMethod()
		{
			return typeof(ItemData).GetMethods(BindingFlags.Static | BindingFlags.Public).Single((MethodInfo m) => m.Name == "GetTooltip");
		}

		private static void Postfix(ItemData __0, ref string __result)
		{
			string text = Transport.HoldingText(__0);
			if (text.Length != 0)
			{
				string text2 = Localization.instance.Localize(__0.m_shared.m_description);
				int num = __result.IndexOf(text2, StringComparison.Ordinal);
				if (text2.Length > 0 && num >= 0)
				{
					__result = __result.Insert(num + text2.Length, text);
				}
				else
				{
					__result += text;
				}
			}
		}
	}
	[HarmonyPatch(typeof(PieceTable), "UpdateAvailable")]
	internal static class PieceAvailability
	{
		private static void Prefix(HashSet<string> __0, Player __1)
		{
			GameObject placePrefab = Plugin.Instance.Registry.PlacePrefab;
			if (Object.op_Implicit((Object)(object)placePrefab))
			{
				Piece component = placePrefab.GetComponent<Piece>();
				component.m_enabled = (Object)(object)__1 == (Object)(object)Player.m_localPlayer && Plugin.Instance.Transport.CanPlace;
				if (component.m_enabled)
				{
					__0.Add(component.m_name);
				}
				else
				{
					__0.Remove(component.m_name);
				}
			}
		}
	}
	[HarmonyPatch]
	internal static class RequirementsHook
	{
		private static MethodBase TargetMethod()
		{
			return AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2]
			{
				typeof(Piece),
				typeof(RequirementMode)
			}, (Type[])null);
		}

		private static bool Prefix(Piece __0, ref bool __result)
		{
			if (!Object.op_Implicit((Object)(object)Plugin.Instance.Registry.PlacePrefab) || (Object)(object)((Component)__0).gameObject != (Object)(object)Plugin.Instance.Registry.PlacePrefab)
			{
				return true;
			}
			__result = Plugin.Instance.Transport.CanPlace;
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "TryPlacePiece")]
	internal static class PlaceHook
	{
		private static bool Prefix(Player __instance, Piece __0, ref bool __result)
		{
			if (!Object.op_Implicit((Object)(object)Plugin.Instance.Registry.PlacePrefab) || (Object)(object)((Component)__0).gameObject != (Object)(object)Plugin.Instance.Registry.PlacePrefab)
			{
				return true;
			}
			Plugin.Instance.Transport.Place(__instance, __0);
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "SetupPlacementGhost")]
	internal static class GhostSetup
	{
		private static void Postfix(Player __instance)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			Piece selectedPiece = __instance.GetSelectedPiece();
			if (!((Object)(object)((selectedPiece != null) ? ((Component)selectedPiece).gameObject : null) != (Object)(object)Plugin.Instance.Registry.PlacePrefab))
			{
				object? value = GameApi.Ghost.GetValue(__instance);
				GameObject val = (GameObject)((value is GameObject) ? value : null);
				if (Object.op_Implicit((Object)(object)val))
				{
					AnimalGhostTint animalGhostTint = val.GetComponent<AnimalGhostTint>() ?? val.AddComponent<AnimalGhostTint>();
					animalGhostTint.Initialize();
					Plugin.Instance.Transport.ArmAttackGuard(__instance);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Player), "PlayerAttackInput")]
	internal static class PlacementAttackInput
	{
		[HarmonyPriority(800)]
		private static bool Prefix(Player __instance)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !Plugin.Instance.Transport.BlockAttacks)
			{
				return true;
			}
			GameApi.ClearAttackInput(__instance);
			return false;
		}
	}
	[HarmonyPatch(typeof(Humanoid), "StartAttack")]
	internal static class PlacementAttackStart
	{
		[HarmonyPriority(800)]
		private static bool Prefix(Humanoid __instance, ref bool __result)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !Plugin.Instance.Transport.BlockAttacks)
			{
				return true;
			}
			GameApi.ClearAttackInput(Player.m_localPlayer);
			__result = false;
			return false;
		}
	}
	[HarmonyPatch(typeof(Player), "UpdatePlacementGhost")]
	internal static class GhostUpdate
	{
		private static void Postfix(Player __instance)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Invalid comparison between Unknown and I4
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0102: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Invalid comparison between Unknown and I4
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			object? value = GameApi.Ghost.GetValue(__instance);
			GameObject val = (GameObject)((value is GameObject) ? value : null);
			if (!Object.op_Implicit((Object)(object)val))
			{
				return;
			}
			AnimalGhostTint component = val.GetComponent<AnimalGhostTint>();
			if (Object.op_Implicit((Object)(object)component))
			{
				PlacementStatus val2 = PlacementRules.Restriction(val.transform.position, ((Component)__instance).transform.position);
				if ((int)val2 > 0)
				{
					GameApi.PlacementStatus.SetValue(__instance, val2);
				}
				else if (!Plugin.Instance.Transport.CanPlace)
				{
					GameApi.PlacementStatus.SetValue(__instance, (object)(PlacementStatus)1);
				}
				else if ((int)__instance.GetPlacementStatus() == 0 && !Registry.HasAnimalSpace(val.transform.position, Plugin.Instance.Registry.PreviewAnimal))
				{
					GameApi.PlacementStatus.SetValue(__instance, (object)(PlacementStatus)5);
				}
				component.Show((int)__instance.GetPlacementStatus() == 0);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "OnDestroy")]
	internal static class AnimalHudCleanup
	{
		[HarmonyPriority(800)]
		private static void Prefix(Character __instance)
		{
			if (Catalog.OffspringFor(((Object)((Component)__instance).gameObject).name.Replace("(Clone)", "").Trim()) != null)
			{
				Character.GetAllCharacters().Remove(__instance);
				if (Object.op_Implicit((Object)(object)EnemyHud.instance))
				{
					EnemyHud.instance.RemoveCharacterHud(__instance);
				}
			}
		}
	}
	[HarmonyPatch(typeof(Character), "Awake")]
	internal static class RestoreHealth
	{
		private static void Postfix(Character __instance)
		{
			ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid() && component.IsOwner())
			{
				float num = component.GetZDO().GetFloat("dannanboi.tamedtransport.health", -1f);
				if (num > 0f)
				{
					__instance.SetLevel(component.GetZDO().GetInt(ZDOVars.s_level, 1));
					__instance.SetHealth(__instance.GetMaxHealth() * num);
					component.GetZDO().RemoveFloat("dannanboi.tamedtransport.health");
				}
			}
		}
	}
	internal static class PlacementRules
	{
		internal static void CopyWorkbench(Piece target, Piece source)
		{
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			target.m_groundPiece = source.m_groundPiece;
			target.m_groundOnly = source.m_groundOnly;
			target.m_allowAltGroundPlacement = source.m_allowAltGroundPlacement;
			target.m_cultivatedGroundOnly = source.m_cultivatedGroundOnly;
			target.m_waterPiece = source.m_waterPiece;
			target.m_clipGround = source.m_clipGround;
			target.m_clipEverything = source.m_clipEverything;
			target.m_noInWater = true;
			target.m_allowedInDungeons = false;
			target.m_notOnWood = source.m_notOnWood;
			target.m_notOnTiltingSurface = source.m_notOnTiltingSurface;
			target.m_inCeilingOnly = source.m_inCeilingOnly;
			target.m_notOnFloor = source.m_notOnFloor;
			target.m_noClipping = source.m_noClipping;
			target.m_onlyInTeleportArea = source.m_onlyInTeleportArea;
			target.m_allowedInDeepSnow = source.m_allowedInDeepSnow;
			target.m_requireDeepSnow = source.m_requireDeepSnow;
			target.m_spaceRequirement = source.m_spaceRequirement;
			target.m_vegetationGroundOnly = source.m_vegetationGroundOnly;
			target.m_blockingPieces = new List<Piece>(source.m_blockingPieces);
			target.m_blockRadius = source.m_blockRadius;
			target.m_mustConnectTo = source.m_mustConnectTo;
			target.m_connectRadius = source.m_connectRadius;
			target.m_mustBeAboveConnected = source.m_mustBeAboveConnected;
			target.m_noVines = source.m_noVines;
			target.m_extraPlacementDistance = source.m_extraPlacementDistance;
			target.m_onlyInBiome = source.m_onlyInBiome;
			target.m_craftingStation = source.m_craftingStation;
			target.m_allowRotatedOverlap = source.m_allowRotatedOverlap;
		}

		internal static PlacementStatus Restriction(Vector3 position, Vector3 playerPosition)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			if (Location.IsInsideNoBuildLocation(position))
			{
				return (PlacementStatus)3;
			}
			if (Character.InInterior(position) || Character.InInterior(playerPosition))
			{
				return (PlacementStatus)11;
			}
			if (position.y < Floating.GetLiquidLevel(position, 0f, (LiquidType)10))
			{
				return (PlacementStatus)1;
			}
			return (PlacementStatus)0;
		}

		internal static string Message(PlacementStatus status)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected I4, but got Unknown
			return (status - 2) switch
			{
				1 => "$msg_nobuildzone", 
				9 => "$msg_notindungeon", 
				2 => "$msg_privatezone", 
				3 => "$msg_needspace", 
				0 => "$msg_blocked", 
				11 => "$msg_snowtoodeep", 
				6 => "$msg_wrongbiome", 
				_ => "$msg_invalidplacement", 
			};
		}
	}
	[BepInPlugin("dannanboi.tamedtransport", "TamedTransport", "1.0.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "dannanboi.tamedtransport";

		public const string Version = "1.0.0";

		public const string BuildVersion = "1.0.0";

		internal static Plugin Instance;

		internal Registry Registry;

		internal Network Network;

		internal Transport Transport;

		internal Catalog Local;

		internal Catalog Current;

		internal IconCatalog LocalIcons = IconCatalog.Empty();

		internal IconCatalog Icons;

		internal bool Ready = true;

		internal bool Enabled = true;

		internal bool AllowAdults;

		internal bool AllowUntamed;

		internal bool AllowPortals;

		internal string DirectoryPath;

		internal ConfigEntry<KeyboardShortcut> PickupKey;

		internal ConfigEntry<string> PickupMessage;

		internal ConfigEntry<string> HoldingMessage;

		internal ConfigEntry<string> MultipleMessage;

		private ConfigEntry<bool> enabledSetting;

		private ConfigEntry<bool> adultSetting;

		private ConfigEntry<bool> untamedSetting;

		private ConfigEntry<bool> portalSetting;

		private Harmony harmony;

		private FileSystemWatcher watcher;

		private FileSystemWatcher iconWatcher;

		private long reloadAt;

		private float nextConfig;

		private bool authority = true;

		private bool gameplayDirty;

		private long configWriteTicks;

		private long configLength = -1L;

		internal bool LocalEnabled => enabledSetting.Value;

		internal bool LocalAdults => adultSetting.Value;

		internal bool LocalUntamed => untamedSetting.Value;

		internal bool LocalPortals => portalSetting.Value;

		internal GameplayState LocalState => new GameplayState(LocalEnabled, LocalAdults, LocalUntamed, LocalPortals);

		private GameplayState CurrentState => new GameplayState(Enabled, AllowAdults, AllowUntamed, AllowPortals);

		private void Awake()
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f9: Expected O, but got Unknown
			Instance = this;
			enabledSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Enable Mod", true, "Server/host controls capture and placement. Existing cage data is preserved when disabled.");
			adultSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Allow capturing adults", false, "Allow adult boars, wolves, and lox to use their species' cage. Server/host controls this setting. Untamed adults also require Allow capturing untamed.");
			untamedSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Allow capturing untamed", false, "Allow untamed boars, wolves, and lox to be captured. They remain untamed when released. Adult animals also require Allow capturing adults. Server/host controls this setting.");
			portalSetting = ((BaseUnityPlugin)this).Config.Bind<bool>("Settings", "Allow through portals", false, "Allow loaded cages through ordinary portals. Empty cages can always be teleported, and stone portals follow their native ability to teleport loaded cages. When disabled, loaded cages block ordinary portals, including worlds without normal item restrictions. Other game/mod restrictions still apply. Server/host controls this setting.");
			Enabled = enabledSetting.Value;
			AllowAdults = LocalAdults;
			AllowUntamed = LocalUntamed;
			AllowPortals = LocalPortals;
			PickupKey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Keybindings", "Pick up", new KeyboardShortcut((KeyCode)121, Array.Empty<KeyCode>()), "Pick up an eligible animal when the matching empty cage is in inventory. Local keybinding; eligibility follows server/host settings.");
			PickupMessage = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Pick up text", "Pick up <animal>", "Local hover text. <animal> is replaced by the offspring's name.");
			HoldingMessage = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Contents text", "Currently holding:", "Local occupied-cage description heading.");
			MultipleMessage = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "Multiple cages message", "Can only place one animal at a time, please clear inventory.", "Local notification shown when more than one loaded cage is in inventory.");
			DirectoryPath = Path.Combine(Paths.ConfigPath, "TamedTransport");
			Directory.CreateDirectory(DirectoryPath);
			string text = Path.Combine(DirectoryPath, "icons");
			Directory.CreateDirectory(text);
			string path = Path.Combine(DirectoryPath, "cages.yaml");
			if (!File.Exists(path))
			{
				File.WriteAllText(path, Catalog.DefaultYaml);
			}
			Local = (Current = Catalog.Parse(Catalog.DefaultYaml));
			try
			{
				Local = (Current = LoadLocalCatalog());
			}
			catch (Exception ex)
			{
				Warn("Invalid cages.yaml; using defaults for this startup. " + ex.Message);
			}
			IconCatalog iconCatalog = null;
			try
			{
				iconCatalog = IconCatalog.Load(text, Local);
				iconCatalog.ValidateImages();
				LocalIcons = iconCatalog;
			}
			catch (Exception ex2)
			{
				iconCatalog?.Dispose();
				Warn("Icons could not be loaded; using Wood icons. " + ex2.Message);
			}
			Icons = LocalIcons;
			GameApi.Validate();
			Registry = new Registry();
			Network = new Network();
			Transport = new Transport();
			ConfigEntry<bool>[] array = new ConfigEntry<bool>[4] { enabledSetting, adultSetting, untamedSetting, portalSetting };
			foreach (ConfigEntry<bool> val in array)
			{
				val.SettingChanged += delegate
				{
					if (authority)
					{
						gameplayDirty = true;
					}
				};
			}
			harmony = new Harmony("dannanboi.tamedtransport");
			harmony.PatchAll(typeof(Plugin).Assembly);
			if (Object.op_Implicit((Object)(object)ObjectDB.instance))
			{
				Registry.RegisterDB(ObjectDB.instance);
			}
			if (Object.op_Implicit((Object)(object)ZNetScene.instance))
			{
				Registry.RegisterScene(ZNetScene.instance, before: false);
			}
			watcher = new FileSystemWatcher(DirectoryPath, "cages.yaml")
			{
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
			};
			watcher.Changed += QueueReload;
			watcher.Created += QueueReload;
			watcher.Renamed += delegate(object s, RenamedEventArgs e)
			{
				QueueReload(s, e);
			};
			watcher.EnableRaisingEvents = true;
			iconWatcher = new FileSystemWatcher(text, "*.png")
			{
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite)
			};
			iconWatcher.Changed += QueueReload;
			iconWatcher.Created += QueueReload;
			iconWatcher.Deleted += QueueReload;
			iconWatcher.Renamed += delegate(object s, RenamedEventArgs e)
			{
				QueueReload(s, e);
			};
			iconWatcher.EnableRaisingEvents = true;
			Enabled = enabledSetting.Value;
			((BaseUnityPlugin)this).Logger.LogInfo((object)"TamedTransport 1.0.0 loaded.");
		}

		private void QueueReload(object sender, FileSystemEventArgs args)
		{
			Interlocked.Exchange(ref reloadAt, DateTime.UtcNow.AddMilliseconds(750.0).Ticks);
		}

		private void Update()
		{
			try
			{
				Network?.Tick();
				Transport?.Tick();
				long num = Interlocked.Read(in reloadAt);
				if (authority && num != 0L && DateTime.UtcNow.Ticks >= num && Interlocked.CompareExchange(ref reloadAt, 0L, num) == num)
				{
					ReloadDefinitions();
				}
				if (Time.realtimeSinceStartup >= nextConfig)
				{
					nextConfig = Time.realtimeSinceStartup + 2f;
					ReloadConfigIfChanged();
				}
				if (authority && gameplayDirty)
				{
					gameplayDirty = false;
					GameplayState localState = LocalState;
					if (localState.Mask != CurrentState.Mask)
					{
						ApplyGameplay(localState);
						Network.PublishGameplay();
					}
				}
			}
			catch (Exception ex)
			{
				Warn("TamedTransport update: " + ex.Message);
			}
		}

		private void ReloadDefinitions()
		{
			IconCatalog iconCatalog = null;
			try
			{
				Catalog catalog = LoadLocalCatalog();
				iconCatalog = IconCatalog.Load(Path.Combine(DirectoryPath, "icons"), catalog);
				if (iconCatalog.Same(LocalIcons))
				{
					iconCatalog.Dispose();
					iconCatalog = LocalIcons;
				}
				else
				{
					iconCatalog.ValidateImages();
				}
				if (!(catalog.Yaml == Local.Yaml) || iconCatalog != LocalIcons)
				{
					Icons = iconCatalog;
					Registry.Apply(catalog, Ready && Enabled);
					IconCatalog localIcons = LocalIcons;
					LocalIcons = iconCatalog;
					Local = (Current = catalog);
					if (localIcons != LocalIcons)
					{
						localIcons.Dispose();
					}
					Network.Publish();
					Refresh();
				}
			}
			catch (Exception ex)
			{
				Icons = LocalIcons;
				if (iconCatalog != null && iconCatalog != LocalIcons)
				{
					iconCatalog.Dispose();
				}
				Warn("YAML/icons were not applied; keeping the last valid settings. " + ex.Message);
			}
		}

		private void ReloadConfigIfChanged()
		{
			FileInfo fileInfo = new FileInfo(((BaseUnityPlugin)this).Config.ConfigFilePath);
			if (fileInfo.Exists)
			{
				long ticks = fileInfo.LastWriteTimeUtc.Ticks;
				long length = fileInfo.Length;
				if (ticks != configWriteTicks || length != configLength)
				{
					((BaseUnityPlugin)this).Config.Reload();
					configWriteTicks = ticks;
					configLength = length;
				}
			}
		}

		internal void ApplyGameplay(GameplayState state)
		{
			if (state.Mask != CurrentState.Mask)
			{
				Enabled = state.Enabled;
				AllowAdults = state.AllowAdults;
				AllowUntamed = state.AllowUntamed;
				AllowPortals = state.AllowPortals;
				Registry.ApplyState(Ready && Enabled);
				Refresh();
			}
		}

		private Catalog LoadLocalCatalog()
		{
			string text = Path.Combine(DirectoryPath, "cages.yaml");
			Catalog catalog = Catalog.Parse(File.ReadAllText(text));
			if (catalog.Migrated || catalog.PlacementIconAdded)
			{
				string text2 = text + (catalog.Migrated ? ".1.0.0.1.bak" : ".1.0.0.2.bak");
				if (!File.Exists(text2))
				{
					File.Copy(text, text2);
				}
				string path = Path.Combine(DirectoryPath, "icons");
				int num = 0;
				while (catalog.Migrated && num < Catalog.LegacyNames.Length)
				{
					string text3 = Path.Combine(path, Catalog.IconFileName(Catalog.LegacyNames[num]));
					string[] array = new string[2]
					{
						Catalog.EmptyNames[num],
						Catalog.LoadedNames[num]
					};
					foreach (string item in array)
					{
						string text4 = Path.Combine(path, Catalog.IconFileName(item));
						if (File.Exists(text3) && !File.Exists(text4))
						{
							File.Copy(text3, text4);
						}
					}
					num++;
				}
				File.WriteAllText(text, catalog.Yaml);
				((BaseUnityPlugin)this).Logger.LogInfo((object)"Updated cages.yaml with current cage definitions and placement icon; saved a backup.");
			}
			return catalog;
		}

		internal void SetSession(bool local)
		{
			IconCatalog icons = Icons;
			Icons = LocalIcons;
			authority = local;
			Ready = local;
			Current = Local;
			Enabled = local && enabledSetting.Value;
			AllowAdults = local && LocalAdults;
			AllowUntamed = local && LocalUntamed;
			AllowPortals = local && LocalPortals;
			gameplayDirty = false;
			Registry?.Apply(Current, Ready && Enabled);
			Refresh();
			if (icons != null && icons != LocalIcons)
			{
				icons.Dispose();
			}
		}

		internal void ApplyServer(Catalog catalog, bool enabled, bool adults, bool untamed, bool portals, IconCatalog icons)
		{
			IconCatalog icons2 = Icons;
			if (icons.Same(icons2))
			{
				icons.Dispose();
				icons = icons2;
			}
			if (Ready && catalog.Yaml == Current.Yaml && icons == icons2)
			{
				ApplyGameplay(new GameplayState(enabled, adults, untamed, portals));
				return;
			}
			Icons = icons;
			bool allowAdults = AllowAdults;
			bool allowUntamed = AllowUntamed;
			bool allowPortals = AllowPortals;
			AllowAdults = adults;
			AllowUntamed = untamed;
			AllowPortals = portals;
			try
			{
				icons.ValidateImages();
				Registry.Apply(catalog, enabled);
				Current = catalog;
				Enabled = enabled;
				Ready = true;
				Refresh();
			}
			catch
			{
				Icons = icons2;
				AllowAdults = allowAdults;
				AllowUntamed = allowUntamed;
				AllowPortals = allowPortals;
				if (icons != icons2)
				{
					icons.Dispose();
				}
				throw;
			}
			if (icons2 != null && icons2 != LocalIcons && icons2 != icons)
			{
				icons2.Dispose();
			}
		}

		private void Refresh()
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer))
			{
				GameApi.InventoryChanged(((Humanoid)Player.m_localPlayer).GetInventory());
				if (((Character)Player.m_localPlayer).InPlaceMode())
				{
					GameApi.RefreshPieces.Invoke(Player.m_localPlayer, null);
				}
			}
		}

		internal static void Warn(string text)
		{
			Plugin instance = Instance;
			if (instance != null)
			{
				((BaseUnityPlugin)instance).Logger.LogWarning((object)text);
			}
		}

		internal static void Notice(string text)
		{
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !string.IsNullOrEmpty(text))
			{
				((Character)Player.m_localPlayer).Message((MessageType)2, text, 0, (Sprite)null, false);
			}
		}

		private void OnDestroy()
		{
			watcher?.Dispose();
			iconWatcher?.Dispose();
			Network?.Dispose();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Registry?.Dispose();
			Icons?.Dispose();
			if (Icons != LocalIcons)
			{
				LocalIcons.Dispose();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}
	}
	internal sealed class Receipt
	{
		internal string Token;

		internal string Payload;

		internal long SourceUser;

		internal long Captor;

		internal uint SourceId;

		internal bool Released;
	}
	internal sealed class ReceiptStore : IDisposable
	{
		internal readonly Dictionary<string, Receipt> Records = new Dictionary<string, Receipt>();

		private readonly Dictionary<(long User, uint Id), Receipt> sources = new Dictionary<(long, uint), Receipt>();

		private readonly FileStream file;

		internal ReceiptStore(string path)
		{
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			file = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
			try
			{
				using (BinaryReader binaryReader = new BinaryReader(file, Encoding.UTF8, leaveOpen: true))
				{
					while (file.Position < file.Length)
					{
						long position = file.Position;
						if (file.Length - position < 4)
						{
							file.SetLength(position);
							break;
						}
						int num = binaryReader.ReadInt32();
						if (num < 1 || num > 8192)
						{
							throw new IOException("Invalid transport receipt length.");
						}
						if (file.Length - file.Position < num + 32)
						{
							file.SetLength(position);
							break;
						}
						byte[] array = binaryReader.ReadBytes(num);
						byte[] b = binaryReader.ReadBytes(32);
						using (SHA256 sHA = SHA256.Create())
						{
							if (!Equal(sHA.ComputeHash(array), b))
							{
								throw new IOException("Damaged transport receipt; restore a backup before transporting animals.");
							}
						}
						Receipt receipt = Read(array);
						Index(receipt);
					}
				}
				file.Seek(0L, SeekOrigin.End);
			}
			catch
			{
				file.Dispose();
				throw;
			}
		}

		internal Receipt Find(string token)
		{
			Receipt value;
			return Records.TryGetValue(token, out value) ? value : null;
		}

		internal Receipt FindSource(long user, uint id)
		{
			Receipt value;
			return sources.TryGetValue((user, id), out value) ? value : null;
		}

		private void Index(Receipt receipt)
		{
			Records[receipt.Token] = receipt;
			if (receipt.SourceId != 0)
			{
				sources[(receipt.SourceUser, receipt.SourceId)] = receipt;
			}
		}

		internal void Put(Receipt receipt)
		{
			if (AnimalData.Decode(receipt.Payload).Token != receipt.Token)
			{
				throw new FormatException("Receipt token does not match the animal.");
			}
			Receipt receipt2 = Find(receipt.Token);
			if (receipt2 != null && (receipt2.Payload != receipt.Payload || (receipt2.Released && !receipt.Released)))
			{
				throw new FormatException("A transport receipt cannot change contents or undo a release.");
			}
			byte[] array;
			using (MemoryStream memoryStream = new MemoryStream())
			{
				using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true))
				{
					binaryWriter.Write(1);
					binaryWriter.Write(receipt.Token);
					binaryWriter.Write(receipt.Payload);
					binaryWriter.Write(receipt.SourceUser);
					binaryWriter.Write(receipt.SourceId);
					binaryWriter.Write(receipt.Captor);
					binaryWriter.Write(receipt.Released);
				}
				array = memoryStream.ToArray();
			}
			long length = file.Length;
			try
			{
				using (BinaryWriter binaryWriter2 = new BinaryWriter(file, Encoding.UTF8, leaveOpen: true))
				{
					binaryWriter2.Write(array.Length);
					binaryWriter2.Write(array);
					using (SHA256 sHA = SHA256.Create())
					{
						binaryWriter2.Write(sHA.ComputeHash(array));
					}
					binaryWriter2.Flush();
				}
				file.Flush(flushToDisk: true);
			}
			catch
			{
				file.SetLength(length);
				file.Position = length;
				throw;
			}
			Index(receipt);
		}

		private static Receipt Read(byte[] bytes)
		{
			using MemoryStream memoryStream = new MemoryStream(bytes);
			using BinaryReader binaryReader = new BinaryReader(memoryStream);
			if (binaryReader.ReadInt32() != 1)
			{
				throw new IOException("Unknown receipt journal version.");
			}
			Receipt receipt = new Receipt
			{
				Token = binaryReader.ReadString(),
				Payload = binaryReader.ReadString(),
				SourceUser = binaryReader.ReadInt64(),
				SourceId = binaryReader.ReadUInt32(),
				Captor = binaryReader.ReadInt64(),
				Released = binaryReader.ReadBoolean()
			};
			if (memoryStream.Position != memoryStream.Length || AnimalData.Decode(receipt.Payload).Token != receipt.Token)
			{
				throw new IOException("Invalid receipt journal.");
			}
			return receipt;
		}

		private static bool Equal(byte[] a, byte[] b)
		{
			if (a.Length != b.Length)
			{
				return false;
			}
			for (int i = 0; i < a.Length; i++)
			{
				if (a[i] != b[i])
				{
					return false;
				}
			}
			return true;
		}

		public void Dispose()
		{
			file.Dispose();
		}
	}
	internal sealed class Registry : IDisposable
	{
		internal readonly Dictionary<string, GameObject> Items = new Dictionary<string, GameObject>();

		internal readonly Dictionary<string, GameObject> Previews = new Dictionary<string, GameObject>();

		private readonly Dictionary<string, Recipe> recipes = new Dictionary<string, Recipe>();

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

		private readonly Dictionary<string, Renderer[]> baseRenderers = new Dictionary<string, Renderer[]>();

		private readonly Dictionary<string, bool[]> baseVisibility = new Dictionary<string, bool[]>();

		private readonly GameObject root;

		private ObjectDB database;

		private PieceTable hammer;

		private float workbenchSpace;

		private Sprite placementIcon;

		private static readonly int AnimalSpaceMask = LayerMask.GetMask(new string[3] { "character", "piece", "static_solid" });

		internal string PreviewAnimal;

		internal GameObject PlacePrefab;

		internal Registry()
		{
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			root = new GameObject("TamedTransport_Prefabs");
			root.SetActive(false);
			Object.DontDestroyOnLoad((Object)(object)root);
		}

		internal void RegisterDB(ObjectDB db)
		{
			//IL_034e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0358: Expected O, but got Unknown
			//IL_039d: Unknown result type (might be due to invalid IL or missing references)
			//IL_047d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0491: Unknown result type (might be due to invalid IL or missing references)
			if (!Object.op_Implicit((Object)(object)db) || !Object.op_Implicit((Object)(object)db.GetItemPrefab("Wood")))
			{
				return;
			}
			database = db;
			GameObject itemPrefab = db.GetItemPrefab("Hammer");
			hammer = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>().m_itemData.m_shared.m_buildPieces : null);
			string[] registeredNames = Catalog.RegisteredNames;
			foreach (string text in registeredNames)
			{
				if (!Items.TryGetValue(text, out var value))
				{
					value = Object.Instantiate<GameObject>(db.GetItemPrefab("Wood"),