Decompiled source of HarrisonCustomLootTable v0.0.3

BepInEx/plugins/HarrisonCustomLootTable/HarrisonCustomLootTable.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using HarrisonCustomLootTable.Managers;
using HarrisonCustomLootTable.Models;
using HarrisonCustomLootTable.Patches;
using HarrisonCustomLootTable.Trackers;
using Jotunn.Entities;
using Jotunn.Managers;
using Jotunn.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: InternalsVisibleTo("HarrisonCustomLootTable.Checks")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyCompany("HarrisonCustomLootTable")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.0.3.0")]
[assembly: AssemblyInformationalVersion("0.0.3")]
[assembly: AssemblyProduct("HarrisonCustomLootTable")]
[assembly: AssemblyTitle("HarrisonCustomLootTable")]
[assembly: AssemblyVersion("0.0.3.0")]
namespace HarrisonCustomLootTable
{
	[BepInPlugin("com.harrison.customloottable", "HarrisonCustomLootTable", "0.0.3")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[NetworkCompatibility(/*Could not decode attribute arguments.*/)]
	[SynchronizationMode(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		[HarmonyPatch]
		private static class RejectRemoteSettingsPatch
		{
			private static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(SynchronizationManager), "ConfigRPC_OnServerReceive", new Type[2]
				{
					typeof(long),
					typeof(ZPackage)
				}, (Type[])null) ?? throw new MissingMethodException("Jotunn ConfigRPC_OnServerReceive API changed.");
			}

			private static bool Prefix(ref ZPackage package, ref IEnumerator __result)
			{
				if ((Object)(object)Instance == (Object)null || !Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer())
				{
					return true;
				}
				try
				{
					package = ServerConfigPacketFilter.RemoveModEntries(package, "HarrisonCustomLootTable.cfg");
					return true;
				}
				catch (Exception ex)
				{
					((BaseUnityPlugin)Instance).Logger.LogWarning((object)("Rejected invalid config packet: " + ex.Message));
					__result = Empty();
					return false;
				}
			}

			private static IEnumerator Empty()
			{
				yield break;
			}
		}

		[HarmonyPatch]
		private static class ReceiveServerSettingsPatch
		{
			private static MethodBase TargetMethod()
			{
				return AccessTools.Method(typeof(SynchronizationManager), "ApplyConfigZPackage", new Type[3]
				{
					typeof(ZPackage),
					typeof(bool).MakeByRefType(),
					typeof(HashSet<string>).MakeByRefType()
				}, (Type[])null) ?? throw new MissingMethodException("Jotunn ApplyConfigZPackage API changed.");
			}

			private static void Prefix(out bool __state)
			{
				__state = (Object)(object)Instance != (Object)null && Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer();
				if (__state)
				{
					Instance.receivingServerConfig++;
				}
			}

			private static Exception Finalizer(Exception __exception, bool __state)
			{
				if (__state && (Object)(object)Instance != (Object)null)
				{
					Instance.receivingServerConfig--;
				}
				return __exception;
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnDestroy")]
		private static class SessionEndedPatch
		{
			[HarmonyPriority(800)]
			private static void Prefix()
			{
				if ((Object)(object)Instance != (Object)null)
				{
					Instance.receivedServerConfig = false;
					Instance.LootConfig?.EndSession();
					CharacterDropPatch.Clear();
					PlayerConsumePatch.Clear();
				}
			}
		}

		[HarmonyPatch(typeof(ZNet), "Start")]
		private static class SessionStartedPatch
		{
			private static void Prefix(ZNet __instance)
			{
				CharacterDropPatch.Clear();
				PlayerConsumePatch.Clear();
				Instance?.LootConfig?.BeginSession(__instance.IsServer());
			}
		}

		private sealed class AcceptableDistributionMode : AcceptableValueBase
		{
			public AcceptableDistributionMode()
				: base(typeof(DistributionMode))
			{
			}

			public override bool IsValid(object value)
			{
				if (value is DistributionMode distributionMode)
				{
					return Enum.IsDefined(typeof(DistributionMode), distributionMode);
				}
				return false;
			}

			public override object Clamp(object value)
			{
				if (!((AcceptableValueBase)this).IsValid(value))
				{
					return DistributionMode.LastHit;
				}
				return value;
			}

			public override string ToDescriptionString()
			{
				return "# Acceptable values: LastHit, MostDamage, EveryPlayer";
			}
		}

		public const string PluginGuid = "com.harrison.customloottable";

		public const string PluginName = "HarrisonCustomLootTable";

		public const string PluginVersion = "0.0.3";

		public const string ConfigFileName = "HarrisonCustomLootTable.cfg";

		private readonly List<ConfigurationManagerAttributes> configAttributes = new List<ConfigurationManagerAttributes>();

		private readonly Dictionary<ConfigEntryBase, object> serverValues = new Dictionary<ConfigEntryBase, object>();

		private Harmony harmony;

		private int receivingServerConfig;

		private bool receivedServerConfig;

		private bool revertingClientEdit;

		public static Plugin Instance { get; private set; }

		public ConfigFile ServerConfig { get; private set; }

		public ConfigEntry<float> DropChanceMultiplierPerStar { get; private set; }

		public ConfigEntry<float> DropAmountMultiplierPerStar { get; private set; }

		public ConfigEntry<DistributionMode> DefaultDistributionMode { get; private set; }

		public ConfigEntry<float> MaxParticipantDistance { get; private set; }

		public ConfigEntry<bool> DropOnGroundIfInventoryFull { get; private set; }

		public ConfigEntry<bool> ShowLootNotification { get; private set; }

		public PouchManager Pouches { get; private set; }

		public LootConfigManager LootConfig { get; private set; }

		private void Awake()
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			Instance = this;
			try
			{
				ServerConfig = new ConfigFile(Path.Combine(Paths.ConfigPath, "HarrisonCustomLootTable.cfg"), false, ((BaseUnityPlugin)this).Info.Metadata);
				ServerConfig.SaveOnConfigSet = false;
				BindSettings();
				ServerConfig.SettingChanged += OnSettingChanged;
				ServerConfig.Save();
				ServerConfig.SaveOnConfigSet = true;
				SynchronizationManager.Instance.RegisterCustomConfig(ServerConfig);
				harmony = new Harmony("com.harrison.customloottable");
				harmony.PatchAll(typeof(RejectRemoteSettingsPatch));
				harmony.PatchAll(typeof(ReceiveServerSettingsPatch));
				harmony.PatchAll(typeof(SessionEndedPatch));
				harmony.PatchAll(typeof(SessionStartedPatch));
				harmony.PatchAll(typeof(PlayerConsumePatch));
				harmony.PatchAll(typeof(DamageTracker.ApplyDamagePatch));
				harmony.PatchAll(typeof(CharacterDropPatch));
				harmony.PatchAll(typeof(CharacterDropPatch.DestroyedZdoPatch));
				harmony.PatchAll(typeof(CharacterDropPatch.VanillaDropPatch));
				SynchronizationManager.OnConfigurationSynchronized += OnConfigurationSynchronized;
				Pouches = new PouchManager(Path.Combine(Paths.ConfigPath, "HarrisonCustomLootTable"), ((BaseUnityPlugin)this).Logger);
				Pouches.Initialize();
				LootConfig = new LootConfigManager(Path.Combine(Paths.ConfigPath, "HarrisonCustomLootTable"), ((BaseUnityPlugin)this).Logger);
				LootConfig.Initialize();
				CharacterDropPatch.InitializeNetwork();
				PlayerConsumePatch.InitializeNetwork();
				((BaseUnityPlugin)this).Logger.LogInfo((object)("HarrisonCustomLootTable 0.0.3 initialized. Config: " + ServerConfig.ConfigFilePath));
			}
			catch (Exception arg)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)$"Initialization failed: {arg}");
				Cleanup();
				throw;
			}
		}

		private void BindSettings()
		{
			DropChanceMultiplierPerStar = BindNumericFloat("Star Scaling", "DropChanceMultiplierPerStar", 0.5f, "Bonus chance per star: BaseChance * (1 + Stars * Value).");
			DropAmountMultiplierPerStar = BindNumericFloat("Star Scaling", "DropAmountMultiplierPerStar", 1f, "Bonus quantity per star: BaseAmount * (1 + Stars * Value).");
			DefaultDistributionMode = Bind("Distribution", "DefaultDistributionMode", DistributionMode.LastHit, "Default recipients: LastHit, MostDamage, or EveryPlayer.", (AcceptableValueBase)(object)new AcceptableDistributionMode());
			MaxParticipantDistance = BindNumericFloat("Distribution", "MaxParticipantDistance", 60f, "Maximum participant distance in metres.");
			DropOnGroundIfInventoryFull = Bind("Distribution", "DropOnGroundIfInventoryFull", value: true, "Drop excess configured loot at the recipient's feet when inventory is full.");
			ShowLootNotification = BindLootNotification(ServerConfig);
		}

		internal static ConfigEntry<bool> BindLootNotification(ConfigFile config)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			return config.Bind<bool>("UI / Notification", "ShowLootNotification", true, new ConfigDescription("Show top-left HUD messages for rewards deposited into inventory. Disabling also hides deposited amounts in pouch summaries; opening messages and overflow warnings remain. Client-side preference; editable locally and never synchronized.", (AcceptableValueBase)null, new object[1] { (object)new ConfigurationManagerAttributes
			{
				IsAdminOnly = false,
				ReadOnly = false,
				HideDefaultButton = false
			} }));
		}

		private ConfigEntry<float> BindNumericFloat(string section, string key, float value, string description)
		{
			ConfigEntry<float> val = Bind(section, key, value, description + " Must be finite and non-negative; invalid values become 0.");
			val.Value = NormalizeNumericValue(val.Value);
			serverValues[(ConfigEntryBase)(object)val] = val.Value;
			return val;
		}

		internal static float NormalizeNumericValue(float value)
		{
			if (!float.IsNaN(value) && !float.IsInfinity(value) && !(value < 0f))
			{
				return value;
			}
			return 0f;
		}

		private ConfigEntry<T> Bind<T>(string section, string key, T value, string description, AcceptableValueBase acceptable = null)
		{
			//IL_0000: 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_000d: Expected O, but got Unknown
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			ConfigurationManagerAttributes val = new ConfigurationManagerAttributes
			{
				IsAdminOnly = true
			};
			configAttributes.Add(val);
			ConfigEntry<T> val2 = ServerConfig.Bind<T>(section, key, value, new ConfigDescription(description + " Server/host controlled.", acceptable, new object[1] { val }));
			serverValues.Add((ConfigEntryBase)(object)val2, ((ConfigEntryBase)val2).BoxedValue);
			return val2;
		}

		private void Update()
		{
			CharacterDropPatch.Update();
			PlayerConsumePatch.Update();
			bool value = Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer();
			foreach (ConfigurationManagerAttributes configAttribute in configAttributes)
			{
				configAttribute.ReadOnly = value;
				configAttribute.HideDefaultButton = value;
			}
		}

		private void OnSettingChanged(object sender, SettingChangedEventArgs args)
		{
			if (revertingClientEdit)
			{
				return;
			}
			ConfigEntryBase changedSetting = args.ChangedSetting;
			if (!serverValues.ContainsKey(changedSetting))
			{
				return;
			}
			if (Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer() && receivedServerConfig && receivingServerConfig == 0)
			{
				revertingClientEdit = true;
				try
				{
					changedSetting.BoxedValue = serverValues[changedSetting];
					return;
				}
				finally
				{
					revertingClientEdit = false;
				}
			}
			if (changedSetting.SettingType == typeof(float))
			{
				float value = (float)changedSetting.BoxedValue;
				float num = NormalizeNumericValue(value);
				if (!value.Equals(num))
				{
					changedSetting.BoxedValue = num;
					return;
				}
			}
			serverValues[changedSetting] = changedSetting.BoxedValue;
		}

		private void OnConfigurationSynchronized(object sender, ConfigurationSynchronizationEventArgs args)
		{
			if (!args.UpdatedPluginGUIDs.Contains("com.harrison.customloottable"))
			{
				return;
			}
			receivedServerConfig = Object.op_Implicit((Object)(object)ZNet.instance) && !ZNet.instance.IsServer();
			foreach (ConfigDefinition key in ServerConfig.Keys)
			{
				ConfigEntryBase val = ServerConfig[key];
				if (serverValues.ContainsKey(val))
				{
					serverValues[val] = val.BoxedValue;
				}
			}
			((BaseUnityPlugin)this).Logger.LogInfo((object)(args.InitialSynchronization ? "Received initial server settings." : "Server settings synchronized."));
		}

		private void OnDestroy()
		{
			Cleanup();
		}

		private void Cleanup()
		{
			SynchronizationManager.OnConfigurationSynchronized -= OnConfigurationSynchronized;
			if (ServerConfig != null)
			{
				ServerConfig.SettingChanged -= OnSettingChanged;
			}
			Pouches?.Dispose();
			LootConfig?.Dispose();
			CharacterDropPatch.Shutdown();
			PlayerConsumePatch.Shutdown();
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
		}

		internal void LogLootError(string message, Exception exception)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)$"{message}: {exception}");
		}

		internal void LogPouchSummary(string message)
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		}
	}
	internal static class ServerConfigPacketFilter
	{
		internal static ZPackage RemoveModEntries(ZPackage package, string fileName)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Expected O, but got Unknown
			ZPackage val = new ZPackage(package.GetArray());
			byte b = val.ReadByte();
			int num = val.ReadInt();
			if (num < 0 || num > 65536)
			{
				throw new InvalidDataException("Invalid entry count.");
			}
			List<string[]> list = new List<string[]>();
			for (int i = 0; i < num; i++)
			{
				string[] array = new string[4]
				{
					val.ReadString(),
					val.ReadString(),
					val.ReadString(),
					val.ReadString()
				};
				if (!string.Equals(array[0].Replace('\\', '/').Trim(new char[1] { '/' }), fileName, StringComparison.OrdinalIgnoreCase))
				{
					list.Add(array);
				}
			}
			if (val.GetPos() != val.Size())
			{
				throw new InvalidDataException("Trailing config data.");
			}
			ZPackage val2 = new ZPackage();
			val2.Write(b);
			val2.Write(list.Count);
			foreach (string[] item in list)
			{
				foreach (string text in item)
				{
					val2.Write(text);
				}
			}
			val2.SetPos(0);
			return val2;
		}
	}
}
namespace HarrisonCustomLootTable.Trackers
{
	public static class DamageTracker
	{
		private sealed class LocalRecord
		{
			internal readonly string Epoch = Guid.NewGuid().ToString("N");

			internal readonly Dictionary<ZDOID, double> Damage = new Dictionary<ZDOID, double>();

			internal long Sequence;

			internal ZDOID LastPlayer;
		}

		private sealed class ServerRecord
		{
			internal readonly Dictionary<string, CombatUpdate> Segments = new Dictionary<string, CombatUpdate>();
		}

		[HarmonyPatch(typeof(Character), "ApplyDamage", new Type[]
		{
			typeof(HitData),
			typeof(bool),
			typeof(bool),
			typeof(DamageModifier)
		})]
		internal static class ApplyDamagePatch
		{
			internal sealed class HitState
			{
				internal ZDOID Monster;

				internal ZDOID Player;

				internal float Before;
			}

			[HarmonyPriority(0)]
			private static void Prefix(Character __instance, HitData hit, out HitState __state)
			{
				//IL_001f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0039: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				__state = null;
				if (CharacterDropPatch.IsConfiguredOwner(__instance) && !__instance.IsDead() && hit != null)
				{
					HitState obj = new HitState
					{
						Monster = __instance.GetZDOID()
					};
					Character attacker = hit.GetAttacker();
					Player val = (Player)(object)((attacker is Player) ? attacker : null);
					obj.Player = ((val != null) ? ((Character)val).GetZDOID() : ZDOID.None);
					obj.Before = __instance.GetHealth();
					__state = obj;
				}
			}

			private static void Postfix(Character __instance, HitState __state)
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0035: Unknown result type (might be due to invalid IL or missing references)
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				if (__state == null || !Object.op_Implicit((Object)(object)__instance))
				{
					return;
				}
				double num = EffectiveDamage(__state.Before, __instance.GetHealth());
				if (num <= 0.0)
				{
					return;
				}
				try
				{
					CombatUpdate update = RecordLocal(__state.Monster, __state.Player, num);
					CharacterDropPatch.ReportCombat(__state.Monster, update, death: false);
				}
				catch (Exception exception)
				{
					Plugin.Instance?.LogLootError("Damage reporting failed", exception);
				}
			}
		}

		private static readonly object Gate = new object();

		private static readonly Dictionary<ZDOID, LocalRecord> Local = new Dictionary<ZDOID, LocalRecord>();

		private static readonly Dictionary<ZDOID, ServerRecord> Server = new Dictionary<ZDOID, ServerRecord>();

		internal const int MaxContributors = 256;

		internal static CombatUpdate RecordLocal(ZDOID monster, ZDOID player, double damage)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: 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)
			lock (Gate)
			{
				if (!Local.TryGetValue(monster, out var value))
				{
					Local.Add(monster, value = new LocalRecord());
				}
				value.Sequence++;
				value.LastPlayer = player;
				if (player != ZDOID.None && damage > 0.0 && !double.IsNaN(damage) && !double.IsInfinity(damage))
				{
					if (value.Damage.Count >= 256 && !value.Damage.ContainsKey(player))
					{
						value.LastPlayer = ZDOID.None;
						return Copy(value);
					}
					value.Damage.TryGetValue(player, out var value2);
					value.Damage[player] = value2 + damage;
				}
				return Copy(value);
			}
		}

		internal static CombatUpdate GetLocal(ZDOID monster)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				if (!Local.TryGetValue(monster, out var value))
				{
					Local.Add(monster, value = new LocalRecord());
				}
				return Copy(value);
			}
		}

		private static CombatUpdate Copy(LocalRecord record)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			return new CombatUpdate(record.Epoch, record.Sequence, record.LastPlayer, record.Damage);
		}

		internal static bool AcceptSnapshot(ZDOID monster, long owner, CombatUpdate update)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			bool valid;
			return AcceptSnapshot(monster, owner, update, out valid);
		}

		internal static bool AcceptSnapshot(ZDOID monster, long owner, CombatUpdate update, out bool valid)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				valid = false;
				if (!Server.TryGetValue(monster, out var value))
				{
					Server.Add(monster, value = new ServerRecord());
				}
				string key = owner + ":" + update.Epoch;
				if (value.Segments.TryGetValue(key, out var value2) && value2.Sequence >= update.Sequence)
				{
					valid = value2.Sequence == update.Sequence && value2.LastPlayer == update.LastPlayer && value2.Damage.Count == update.Damage.Count && value2.Damage.All((KeyValuePair<ZDOID, double> entry) => update.Damage.TryGetValue(entry.Key, out var value4) && value4 == entry.Value);
					return false;
				}
				if (value2 != null)
				{
					foreach (KeyValuePair<ZDOID, double> item in value2.Damage)
					{
						if (!update.Damage.TryGetValue(item.Key, out var value3) || value3 < item.Value)
						{
							return false;
						}
					}
				}
				if (value.Segments.Count >= 256 && value2 == null)
				{
					return false;
				}
				value.Segments[key] = update;
				valid = true;
				return true;
			}
		}

		internal static DamageSnapshot Take(ZDOID monster, ZDOID lastPlayer)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				Dictionary<ZDOID, double> dictionary = new Dictionary<ZDOID, double>();
				if (Server.TryGetValue(monster, out var value))
				{
					foreach (CombatUpdate value3 in value.Segments.Values)
					{
						foreach (KeyValuePair<ZDOID, double> item in value3.Damage)
						{
							dictionary.TryGetValue(item.Key, out var value2);
							dictionary[item.Key] = value2 + item.Value;
						}
					}
				}
				Server.Remove(monster);
				return new DamageSnapshot(lastPlayer, dictionary);
			}
		}

		internal static void RemoveLocal(ZDOID monster)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				Local.Remove(monster);
			}
		}

		internal static bool HasContributor(ZDOID monster, ZDOID player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				ServerRecord value;
				return Server.TryGetValue(monster, out value) && value.Segments.Values.Any((CombatUpdate segment) => segment.Damage.ContainsKey(player));
			}
		}

		internal static void Prune(Func<ZDOID, bool> retain)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
			lock (Gate)
			{
				ZDOID[] array = Local.Keys.Where((ZDOID id) => !retain(id)).ToArray();
				foreach (ZDOID key in array)
				{
					Local.Remove(key);
				}
				array = Server.Keys.Where((ZDOID id) => !retain(id)).ToArray();
				foreach (ZDOID key2 in array)
				{
					Server.Remove(key2);
				}
			}
		}

		public static void Clear()
		{
			lock (Gate)
			{
				Local.Clear();
				Server.Clear();
			}
		}

		internal static double EffectiveDamage(double before, double after)
		{
			if (double.IsNaN(before) || double.IsInfinity(before) || double.IsNaN(after) || double.IsInfinity(after))
			{
				return 0.0;
			}
			return Math.Max(0.0, Math.Min(Math.Max(0.0, before), before - after));
		}
	}
	internal sealed class CombatUpdate
	{
		internal string Epoch { get; }

		internal long Sequence { get; }

		internal ZDOID LastPlayer { get; }

		internal IReadOnlyDictionary<ZDOID, double> Damage { get; }

		internal CombatUpdate(string epoch, long sequence, ZDOID lastPlayer, IDictionary<ZDOID, double> damage)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			Epoch = epoch;
			Sequence = sequence;
			LastPlayer = lastPlayer;
			Damage = new ReadOnlyDictionary<ZDOID, double>(new Dictionary<ZDOID, double>(damage));
		}
	}
	public sealed class DamageSnapshot
	{
		public ZDOID LastPlayer { get; }

		public IReadOnlyDictionary<ZDOID, double> Damage { get; }

		internal DamageSnapshot(ZDOID lastPlayer, IDictionary<ZDOID, double> damage)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			LastPlayer = lastPlayer;
			Damage = new ReadOnlyDictionary<ZDOID, double>(new Dictionary<ZDOID, double>(damage));
		}
	}
}
namespace HarrisonCustomLootTable.Patches
{
	[HarmonyPatch(typeof(Character), "OnDeath")]
	public static class CharacterDropPatch
	{
		private sealed class MonsterState
		{
			internal long Owner;

			internal string Prefab;

			internal int Level;

			internal Vector3 Position;

			internal float Time;
		}

		internal sealed class Candidate
		{
			internal ZDOID CharacterId;

			internal long PeerId;

			internal Vector3 Position;
		}

		internal sealed class GrantEntry
		{
			internal string Prefab;

			internal int Amount;
		}

		internal sealed class DeliveryResult
		{
			internal string Name;

			internal int Received;

			internal int Dropped;

			internal int Undelivered;
		}

		[HarmonyPatch(typeof(ZDOMan), "HandleDestroyedZDO", new Type[] { typeof(ZDOID) })]
		internal static class DestroyedZdoPatch
		{
			private static void Prefix(ZDOID __0)
			{
				//IL_0019: Unknown result type (might be due to invalid IL or missing references)
				//IL_0061: Unknown result type (might be due to invalid IL or missing references)
				if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
				{
					MonsterState monster = GetMonster(__0);
					if (monster != null && Plugin.Instance?.LootConfig != null && Plugin.Instance.LootConfig.TryGetSnapshot(out var snapshot) && snapshot.MonsterDrops.ContainsKey(monster.Prefab))
					{
						Destroyed[__0] = monster;
					}
				}
			}
		}

		[HarmonyPatch(typeof(CharacterDrop), "GenerateDropList")]
		internal static class VanillaDropPatch
		{
			private static bool Prefix(CharacterDrop __instance, ref List<KeyValuePair<GameObject, int>> __result)
			{
				Character component = ((Component)__instance).GetComponent<Character>();
				ZNetView component2 = ((Component)__instance).GetComponent<ZNetView>();
				if (!Object.op_Implicit((Object)(object)component) || component.IsPlayer() || !Object.op_Implicit((Object)(object)component2) || Plugin.Instance?.LootConfig == null || !Plugin.Instance.LootConfig.TryGetSnapshot(out var snapshot) || !snapshot.MonsterDrops.TryGetValue(PrefabName(component2), out var value) || (value.KeepVanillaDropsOnGround ?? true))
				{
					return true;
				}
				__result = new List<KeyValuePair<GameObject, int>>();
				return false;
			}
		}

		private const int Protocol = 1;

		internal const int MaxItemsPerGrant = 10000;

		internal const int MaxStacksPerGrant = 256;

		private const float TombstoneSeconds = 120f;

		private static CustomRPC combatRpc;

		private static CustomRPC grantRpc;

		private static readonly Dictionary<ZDOID, MonsterState> Destroyed = new Dictionary<ZDOID, MonsterState>();

		private static readonly Dictionary<ZDOID, float> ProcessedDeaths = new Dictionary<ZDOID, float>();

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

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

		private static readonly Queue<string> GrantOrder = new Queue<string>();

		private static int session;

		private static float nextPrune;

		private static bool active;

		internal static void InitializeNetwork()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_0027: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0053: Expected O, but got Unknown
			combatRpc = NetworkManager.Instance.AddRPC("MonsterCombatV1", new CoroutineHandler(ReceiveCombat), new CoroutineHandler(Ignore));
			grantRpc = NetworkManager.Instance.AddRPC("MonsterLootGrantV1", new CoroutineHandler(Ignore), new CoroutineHandler(ReceiveGrant));
			active = true;
		}

		internal static bool IsConfiguredOwner(Character character)
		{
			if (!Object.op_Implicit((Object)(object)character) || character.IsPlayer() || !character.IsOwner())
			{
				return false;
			}
			ZNetView component = ((Component)character).GetComponent<ZNetView>();
			if (Object.op_Implicit((Object)(object)component) && component.IsValid() && Plugin.Instance?.LootConfig != null && Plugin.Instance.LootConfig.TryGetSnapshot(out var snapshot))
			{
				return snapshot.MonsterDrops.ContainsKey(PrefabName(component));
			}
			return false;
		}

		private static string PrefabName(ZNetView view)
		{
			ZDO val = (Object.op_Implicit((Object)(object)view) ? view.GetZDO() : null);
			GameObject val2 = ((val != null && Object.op_Implicit((Object)(object)ZNetScene.instance)) ? ZNetScene.instance.GetPrefab(val.GetPrefab()) : null);
			if (!Object.op_Implicit((Object)(object)val2))
			{
				return string.Empty;
			}
			return ((Object)val2).name;
		}

		private static void Prefix(Character __instance)
		{
			//IL_0054: 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_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			if (!IsConfiguredOwner(__instance) || __instance.GetHealth() > 0f)
			{
				return;
			}
			ZDOID zDOID = __instance.GetZDOID();
			if (!ReportedDeaths.Add(zDOID))
			{
				return;
			}
			try
			{
				ReportCombat(zDOID, DamageTracker.GetLocal(zDOID), death: true);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Monster death reporting failed", exception);
			}
			finally
			{
				DamageTracker.RemoveLocal(zDOID);
			}
		}

		internal static void ReportCombat(ZDOID monster, CombatUpdate update, bool death)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			if (!active || !Object.op_Implicit((Object)(object)ZNet.instance) || combatRpc == null)
			{
				return;
			}
			if (ZNet.instance.IsServer())
			{
				HandleCombat(ZNet.GetUID(), monster, update, death);
				return;
			}
			ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
			if (serverPeer != null)
			{
				combatRpc.SendPackage(serverPeer.m_uid, WriteCombat(monster, update, death));
			}
		}

		internal static ZPackage WriteCombat(ZDOID monster, CombatUpdate update, bool death)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(monster);
			val.Write(death);
			val.Write(update.Epoch);
			val.Write(update.Sequence);
			val.Write(update.LastPlayer);
			val.Write(update.Damage.Count);
			foreach (KeyValuePair<ZDOID, double> item in update.Damage)
			{
				val.Write(item.Key);
				val.Write(item.Value);
			}
			val.SetPos(0);
			return val;
		}

		internal static CombatUpdate ReadCombat(ZPackage input, out ZDOID monster, out bool death)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: 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_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
			if (input == null || input.Size() > 16384 || input.ReadInt() != 1)
			{
				throw new InvalidOperationException("Invalid combat protocol.");
			}
			monster = input.ReadZDOID();
			death = input.ReadBool();
			string text = input.ReadString();
			if (!Guid.TryParseExact(text, "N", out var _))
			{
				throw new InvalidOperationException("Invalid combat epoch.");
			}
			long num = input.ReadLong();
			ZDOID val = input.ReadZDOID();
			int num2 = input.ReadInt();
			if (monster == ZDOID.None || num < 0 || num2 < 0 || num2 > 256)
			{
				throw new InvalidOperationException("Invalid combat snapshot.");
			}
			Dictionary<ZDOID, double> dictionary = new Dictionary<ZDOID, double>();
			for (int i = 0; i < num2; i++)
			{
				ZDOID val2 = input.ReadZDOID();
				double num3 = input.ReadDouble();
				if (val2 == ZDOID.None || double.IsNaN(num3) || double.IsInfinity(num3) || num3 <= 0.0)
				{
					throw new InvalidOperationException("Invalid damage total.");
				}
				dictionary.Add(val2, num3);
			}
			if ((val != ZDOID.None && !dictionary.ContainsKey(val)) || input.GetPos() != input.Size())
			{
				throw new InvalidOperationException("Invalid last hitter or trailing combat data.");
			}
			return new CombatUpdate(text, num, val, dictionary);
		}

		private static IEnumerator ReceiveCombat(long sender, ZPackage package)
		{
			if (!Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer())
			{
				yield break;
			}
			try
			{
				ZDOID monster;
				bool death;
				CombatUpdate update = ReadCombat(package, out monster, out death);
				HandleCombat(sender, monster, update, death);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Rejected combat report", exception);
			}
		}

		private static void HandleCombat(long sender, ZDOID monster, CombatUpdate update, bool death)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: 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)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_017a: Unknown result type (might be due to invalid IL or missing references)
			if (!active || ProcessedDeaths.ContainsKey(monster) || Plugin.Instance?.LootConfig == null || !Plugin.Instance.LootConfig.TryGetSnapshot(out var snapshot))
			{
				return;
			}
			MonsterState monster2 = GetMonster(monster);
			if (monster2 == null || monster2.Owner != sender || !snapshot.MonsterDrops.TryGetValue(monster2.Prefab, out var value))
			{
				return;
			}
			Dictionary<ZDOID, double> dictionary = new Dictionary<ZDOID, double>();
			foreach (KeyValuePair<ZDOID, double> item in update.Damage)
			{
				ZDO zDO = ZDOMan.instance.GetZDO(item.Key);
				if ((zDO != null && zDO.GetLong("playerID", 0L) != 0L) || DamageTracker.HasContributor(monster, item.Key))
				{
					dictionary.Add(item.Key, item.Value);
				}
			}
			ZDOID lastPlayer = (dictionary.ContainsKey(update.LastPlayer) ? update.LastPlayer : ZDOID.None);
			CombatUpdate update2 = new CombatUpdate(update.Epoch, update.Sequence, lastPlayer, dictionary);
			DamageTracker.AcceptSnapshot(monster, sender, update2, out var valid);
			if (!valid || !death)
			{
				return;
			}
			ProcessedDeaths.Add(monster, Time.realtimeSinceStartup);
			DamageSnapshot damage = DamageTracker.Take(monster, lastPlayer);
			try
			{
				List<Candidate> candidates = GetCandidates();
				DistributionMode mode = value.DistributionMode ?? Plugin.Instance.DefaultDistributionMode.Value;
				List<Candidate> list = SelectRecipients(candidates, damage, mode, monster2.Position, Plugin.Instance.MaxParticipantDistance.Value);
				if (list.Count == 0)
				{
					return;
				}
				List<GrantEntry> list2 = RollLoot(value.Drops, monster2.Level, Plugin.Instance.DropChanceMultiplierPerStar.Value, Plugin.Instance.DropAmountMultiplierPerStar.Value);
				if (list2.Count == 0)
				{
					return;
				}
				foreach (Candidate item2 in list)
				{
					SendGrant(item2, list2);
				}
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Configured loot distribution failed", exception);
			}
		}

		private static MonsterState GetMonster(ZDOID monster)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: 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_0093: Unknown result type (might be due to invalid IL or missing references)
			ZDOMan instance = ZDOMan.instance;
			ZDO val = ((instance != null) ? instance.GetZDO(monster) : null);
			if (val != null)
			{
				GameObject val2 = (Object.op_Implicit((Object)(object)ZNetScene.instance) ? ZNetScene.instance.GetPrefab(val.GetPrefab()) : null);
				if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val2.GetComponent<Character>()) || Object.op_Implicit((Object)(object)val2.GetComponent<Player>()))
				{
					return null;
				}
				return new MonsterState
				{
					Owner = val.GetOwner(),
					Prefab = ((Object)val2).name,
					Level = val.GetInt("level", 1),
					Position = val.GetPosition(),
					Time = Time.realtimeSinceStartup
				};
			}
			if (!Destroyed.TryGetValue(monster, out var value) || !(Time.realtimeSinceStartup - value.Time <= 120f))
			{
				return null;
			}
			return value;
		}

		private static List<Candidate> GetCandidates()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			List<Candidate> result = new List<Candidate>();
			if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && !((Character)Player.m_localPlayer).IsDead())
			{
				AddCandidate(result, ZNet.GetUID(), ((Character)Player.m_localPlayer).GetZDOID());
			}
			foreach (ZNetPeer peer in ZNet.instance.GetPeers())
			{
				if (peer.IsReady())
				{
					AddCandidate(result, peer.m_uid, peer.m_characterID);
				}
			}
			return result;
		}

		private static void AddCandidate(List<Candidate> result, long peerId, ZDOID player)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Unknown result type (might be due to invalid IL or missing references)
			if (!(player == ZDOID.None) && !result.Any((Candidate candidate) => candidate.CharacterId == player))
			{
				ZDO zDO = ZDOMan.instance.GetZDO(player);
				if (zDO != null && zDO.GetOwner() == peerId && !zDO.GetBool("dead", false) && zDO.GetLong("playerID", 0L) != 0L)
				{
					result.Add(new Candidate
					{
						PeerId = peerId,
						CharacterId = player,
						Position = zDO.GetPosition()
					});
				}
			}
		}

		internal static List<Candidate> SelectRecipients(IEnumerable<Candidate> candidates, DamageSnapshot damage, DistributionMode mode, Vector3 position, float distance)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			double squared = (double)distance * (double)distance;
			List<Candidate> list = (from candidate in candidates
				where SquaredDistance(candidate.Position, position) <= squared
				orderby ((ZDOID)(ref candidate.CharacterId)).UserID, ((ZDOID)(ref candidate.CharacterId)).ID
				select candidate).ToList();
			return mode switch
			{
				DistributionMode.EveryPlayer => list, 
				DistributionMode.LastHit => list.Where((Candidate candidate) => candidate.CharacterId == damage.LastPlayer).Take(1).ToList(), 
				_ => (from candidate in list
					where damage.Damage.ContainsKey(candidate.CharacterId)
					orderby damage.Damage[candidate.CharacterId] descending
					select candidate).Take(1).ToList(), 
			};
		}

		private static double SquaredDistance(Vector3 a, Vector3 b)
		{
			//IL_0000: 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_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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)
			double num = (double)a.x - (double)b.x;
			double num2 = (double)a.y - (double)b.y;
			double num3 = (double)a.z - (double)b.z;
			return num * num + num2 * num2 + num3 * num3;
		}

		internal static double ScaleChance(float baseChance, int level, float perStar)
		{
			long num = Math.Max(0L, (long)level - 1L);
			return Math.Min(1.0, Math.Max(0.0, (double)baseChance * (1.0 + (double)((float)num * perStar))));
		}

		internal static int ScaleAmount(int amount, int level, float perStar)
		{
			long num = Math.Max(0L, (long)level - 1L);
			double num2 = Math.Floor((double)amount * (1.0 + (double)((float)num * perStar)));
			if (double.IsNaN(num2) || double.IsInfinity(num2) || num2 < 1.0 || num2 > 10000.0)
			{
				throw new OverflowException($"Scaled loot amount must be in [1, {10000}].");
			}
			return (int)num2;
		}

		internal static bool SuccessfulRoll(double chance, double sample)
		{
			if (!(chance >= 1.0))
			{
				if (chance > 0.0)
				{
					return sample < chance;
				}
				return false;
			}
			return true;
		}

		internal static int RemainingAfterInsertion(int remaining, int chunk, bool added, int uninserted)
		{
			if (chunk < 1 || chunk > remaining || (!added && (uninserted < 0 || uninserted > chunk)))
			{
				throw new InvalidOperationException("Inventory returned an invalid remainder.");
			}
			return remaining - (added ? chunk : (chunk - uninserted));
		}

		private static List<GrantEntry> RollLoot(IReadOnlyList<LootEntryDto> drops, int level, float chanceScale, float amountScale)
		{
			List<GrantEntry> list = new List<GrantEntry>();
			int num = 0;
			long num2 = 0L;
			foreach (LootEntryDto drop in drops)
			{
				try
				{
					if (SuccessfulRoll(ScaleChance(drop.Chance, level, chanceScale), Random.value))
					{
						GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(drop.ItemPrefab) : null);
						if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)val.GetComponent<ItemDrop>()) || !Object.op_Implicit((Object)(object)val.GetComponent<ZNetView>()))
						{
							throw new InvalidOperationException("Configured item prefab '" + drop.ItemPrefab + "' is unavailable or is not a networked item.");
						}
						int num3 = ScaleAmount(drop.MinAmount, level, amountScale);
						int num4 = ScaleAmount(drop.MaxAmount, level, amountScale);
						int num5 = Random.Range(num3, num4 + 1);
						if (num + num5 > 10000)
						{
							throw new OverflowException("Loot grant exceeds the quantity safety limit.");
						}
						int num6 = Math.Max(1, val.GetComponent<ItemDrop>().m_itemData.m_shared.m_maxStackSize);
						long num7 = ((long)num5 + (long)num6 - 1) / num6;
						if (num2 + num7 > 256)
						{
							throw new OverflowException("Loot grant exceeds the stack safety limit.");
						}
						num += num5;
						num2 += num7;
						list.Add(new GrantEntry
						{
							Prefab = drop.ItemPrefab,
							Amount = num5
						});
					}
				}
				catch (Exception exception)
				{
					Plugin.Instance?.LogLootError("Skipped configured loot '" + drop.ItemPrefab + "'", exception);
				}
			}
			return list;
		}

		private static void SendGrant(Candidate recipient, List<GrantEntry> entries)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: 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)
			string id = Guid.NewGuid().ToString("N");
			bool value = Plugin.Instance.DropOnGroundIfInventoryFull.Value;
			if (recipient.PeerId == ZNet.GetUID())
			{
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && ((Character)Player.m_localPlayer).GetZDOID() == recipient.CharacterId)
				{
					Deliver(Player.m_localPlayer, entries, value);
				}
			}
			else
			{
				ZPackage val = WriteGrant(id, recipient.CharacterId, entries, value);
				grantRpc.SendPackage(recipient.PeerId, val);
			}
		}

		internal static ZPackage WriteGrant(string id, ZDOID player, IReadOnlyList<GrantEntry> entries, bool fallback)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			ZPackage val = new ZPackage();
			val.Write(1);
			val.Write(id);
			val.Write(player);
			val.Write(fallback);
			val.Write(entries.Count);
			foreach (GrantEntry entry in entries)
			{
				val.Write(entry.Prefab);
				val.Write(entry.Amount);
			}
			val.SetPos(0);
			return val;
		}

		internal static List<GrantEntry> ReadGrant(ZPackage input, out string id, out ZDOID player, out bool fallback)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: 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_005d: Unknown result type (might be due to invalid IL or missing references)
			if (input == null || input.Size() > 65536 || input.ReadInt() != 1)
			{
				throw new InvalidOperationException("Invalid loot grant protocol.");
			}
			id = input.ReadString();
			player = input.ReadZDOID();
			fallback = input.ReadBool();
			int num = input.ReadInt();
			if (!Guid.TryParseExact(id, "N", out var _) || player == ZDOID.None || num < 1 || num > 256)
			{
				throw new InvalidOperationException("Invalid loot grant.");
			}
			List<GrantEntry> list = new List<GrantEntry>();
			int num2 = 0;
			for (int i = 0; i < num; i++)
			{
				string text = input.ReadString();
				int num3 = input.ReadInt();
				if (string.IsNullOrWhiteSpace(text) || text.Length > 128 || num3 < 1 || num3 > 10000 || num2 + num3 > 10000)
				{
					throw new InvalidOperationException("Invalid grant entry.");
				}
				num2 += num3;
				list.Add(new GrantEntry
				{
					Prefab = text,
					Amount = num3
				});
			}
			if (input.GetPos() != input.Size())
			{
				throw new InvalidOperationException("Trailing grant data.");
			}
			return list;
		}

		private static IEnumerator ReceiveGrant(long sender, ZPackage package)
		{
			if (!active || !Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.IsServer() || sender == 0L || sender != (ZNet.instance.GetServerPeer()?.m_uid ?? 0))
			{
				yield break;
			}
			List<GrantEntry> entries;
			string id;
			ZDOID playerId;
			bool fallback;
			try
			{
				entries = ReadGrant(package, out id, out playerId, out fallback);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Rejected loot grant", exception);
				yield break;
			}
			if (ReceivedGrants.Contains(id))
			{
				yield break;
			}
			int generation = session;
			float deadline = Time.realtimeSinceStartup + 10f;
			while (!Object.op_Implicit((Object)(object)Player.m_localPlayer) && generation == session && Time.realtimeSinceStartup < deadline)
			{
				yield return null;
			}
			Player localPlayer = Player.m_localPlayer;
			if (!active || generation != session || !Object.op_Implicit((Object)(object)localPlayer) || !((Character)localPlayer).IsOwner() || ((Character)localPlayer).GetZDOID() != playerId || !TryClaimGrant(id))
			{
				yield break;
			}
			try
			{
				Deliver(localPlayer, entries, fallback);
			}
			catch (Exception exception2)
			{
				Plugin.Instance?.LogLootError("Loot grant delivery failed", exception2);
			}
		}

		internal static bool TryClaimGrant(string id)
		{
			if (!ReceivedGrants.Add(id))
			{
				return false;
			}
			GrantOrder.Enqueue(id);
			if (GrantOrder.Count > 8192)
			{
				ReceivedGrants.Remove(GrantOrder.Dequeue());
			}
			return true;
		}

		internal static List<DeliveryResult> Deliver(Player player, IReadOnlyList<GrantEntry> entries, bool fallback, bool showOverflowMessages = true)
		{
			//IL_021d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0222: Unknown result type (might be due to invalid IL or missing references)
			//IL_022c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0231: Unknown result type (might be due to invalid IL or missing references)
			//IL_023c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0246: Unknown result type (might be due to invalid IL or missing references)
			//IL_024b: 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)
			List<DeliveryResult> list = new List<DeliveryResult>();
			foreach (GrantEntry entry in entries)
			{
				DeliveryResult deliveryResult = new DeliveryResult
				{
					Name = entry.Prefab,
					Undelivered = entry.Amount
				};
				list.Add(deliveryResult);
				try
				{
					GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(entry.Prefab);
					ItemDrop val = (Object.op_Implicit((Object)(object)itemPrefab) ? itemPrefab.GetComponent<ItemDrop>() : null);
					if (!Object.op_Implicit((Object)(object)val) || !Object.op_Implicit((Object)(object)itemPrefab.GetComponent<ZNetView>()))
					{
						throw new InvalidOperationException("Loot item '" + entry.Prefab + "' is unavailable on the recipient.");
					}
					deliveryResult.Name = ItemName(val);
					Inventory inventory = ((Humanoid)player).GetInventory();
					int num = entry.Amount;
					int val2 = Math.Max(1, val.m_itemData.m_shared.m_maxStackSize);
					while (num > 0 && !((Character)player).IsDead())
					{
						int num2 = Math.Min(num, val2);
						ItemData val3 = val.m_itemData.Clone();
						val3.m_stack = num2;
						val3.m_dropPrefab = itemPrefab;
						val3.m_equipped = false;
						if (!inventory.CanAddItem(val3, 1))
						{
							break;
						}
						bool flag = inventory.AddItem(val3);
						int num3 = num;
						num = RemainingAfterInsertion(num, num2, flag, val3.m_stack);
						deliveryResult.Received += num3 - num;
						deliveryResult.Undelivered = num;
						if (!flag)
						{
							break;
						}
					}
					if (deliveryResult.Received > 0)
					{
						Plugin instance = Plugin.Instance;
						if (instance != null && instance.ShowLootNotification?.Value == true)
						{
							((Character)player).Message((MessageType)1, $"Received {deliveryResult.Received} {deliveryResult.Name}", 0, (Sprite)null, false);
						}
					}
					if (num <= 0)
					{
						continue;
					}
					if (fallback)
					{
						int num4 = num;
						while (num > 0)
						{
							int num5 = Math.Min(num, val2);
							ItemData obj = val.m_itemData.Clone();
							obj.m_dropPrefab = itemPrefab;
							obj.m_stack = num5;
							if (!Object.op_Implicit((Object)(object)ItemDrop.DropItem(obj, num5, ((Component)player).transform.position + Vector3.up * 0.3f + ((Component)player).transform.forward * 0.5f, Quaternion.identity)))
							{
								throw new InvalidOperationException("Ground item creation failed.");
							}
							num -= num5;
							deliveryResult.Dropped += num5;
							deliveryResult.Undelivered = num;
						}
						if (showOverflowMessages)
						{
							((Character)player).Message((MessageType)2, $"Inventory full. {num4} {ItemName(val)} dropped at your feet.", 0, (Sprite)null, false);
						}
					}
					else if (showOverflowMessages)
					{
						((Character)player).Message((MessageType)2, $"Inventory full. {num} {ItemName(val)} could not be collected.", 0, (Sprite)null, false);
					}
				}
				catch (Exception exception)
				{
					Plugin.Instance?.LogLootError("Failed to deliver '" + entry.Prefab + "'", exception);
				}
			}
			return list;
		}

		private static string ItemName(ItemDrop item)
		{
			if (Localization.instance != null)
			{
				return Localization.instance.Localize(item.m_itemData.m_shared.m_name);
			}
			return item.m_itemData.m_shared.m_name;
		}

		private static IEnumerator Ignore(long sender, ZPackage package)
		{
			yield break;
		}

		internal static void Update()
		{
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZDOMan.instance != null && !(Time.realtimeSinceStartup < nextPrune))
			{
				nextPrune = Time.realtimeSinceStartup + 30f;
				float now = Time.realtimeSinceStartup;
				KeyValuePair<ZDOID, MonsterState>[] array = Destroyed.Where((KeyValuePair<ZDOID, MonsterState> pair) => now - pair.Value.Time > 120f).ToArray();
				foreach (KeyValuePair<ZDOID, MonsterState> keyValuePair in array)
				{
					Destroyed.Remove(keyValuePair.Key);
				}
				KeyValuePair<ZDOID, float>[] array2 = ProcessedDeaths.Where((KeyValuePair<ZDOID, float> pair) => now - pair.Value > 120f && ZDOMan.instance.GetZDO(pair.Key) == null).ToArray();
				foreach (KeyValuePair<ZDOID, float> keyValuePair2 in array2)
				{
					ProcessedDeaths.Remove(keyValuePair2.Key);
				}
				DamageTracker.Prune((ZDOID id) => ZDOMan.instance.GetZDO(id) != null || Destroyed.ContainsKey(id));
				ReportedDeaths.RemoveWhere((ZDOID id) => ZDOMan.instance.GetZDO(id) == null);
			}
		}

		internal static void Clear()
		{
			session++;
			Destroyed.Clear();
			ProcessedDeaths.Clear();
			ReportedDeaths.Clear();
			ReceivedGrants.Clear();
			GrantOrder.Clear();
			nextPrune = 0f;
			DamageTracker.Clear();
		}

		internal static void Shutdown()
		{
			active = false;
			Clear();
			combatRpc = null;
			grantRpc = null;
		}
	}
	[HarmonyPatch(typeof(Player), "ConsumeItem", new Type[]
	{
		typeof(Inventory),
		typeof(ItemData),
		typeof(bool)
	})]
	public static class PlayerConsumePatch
	{
		internal enum OpenStatus
		{
			Approved,
			Unavailable,
			InvalidLoot,
			Busy
		}

		internal enum RequestDecision
		{
			Accepted,
			Duplicate,
			Busy
		}

		internal sealed class OpenRequest
		{
			internal string Id;

			internal long Sequence;

			internal ZDOID PlayerId;

			internal string Pouch;
		}

		internal sealed class OpenResponse
		{
			internal OpenRequest Request;

			internal OpenStatus Status;

			internal bool Fallback;

			internal List<CharacterDropPatch.GrantEntry> Entries = new List<CharacterDropPatch.GrantEntry>();
		}

		internal sealed class RequestState
		{
			private long lastSequence;

			private double nextAllowed;

			internal RequestDecision Accept(long sequence, double now)
			{
				if (sequence < 1 || sequence <= lastSequence)
				{
					return RequestDecision.Duplicate;
				}
				lastSequence = sequence;
				if (now < nextAllowed)
				{
					return RequestDecision.Busy;
				}
				nextAllowed = now + 0.10000000149011612;
				return RequestDecision.Accepted;
			}
		}

		private sealed class PendingOpen
		{
			internal OpenRequest Request;

			internal Player Player;

			internal Inventory Inventory;

			internal ItemData Item;

			internal float Deadline;
		}

		private const int Protocol = 1;

		private const float RequestTimeout = 15f;

		private const float MinimumRequestInterval = 0.1f;

		private static CustomRPC requestRpc;

		private static CustomRPC responseRpc;

		private static bool active;

		private static long nextSequence;

		private static PendingOpen pending;

		private static readonly Dictionary<long, RequestState> Requests = new Dictionary<long, RequestState>();

		internal static void InitializeNetwork()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0027: Expected O, but got Unknown
			//IL_0027: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			//IL_0053: Expected O, but got Unknown
			requestRpc = NetworkManager.Instance.AddRPC("PouchOpenRequestV1", new CoroutineHandler(ReceiveRequest), new CoroutineHandler(Ignore));
			responseRpc = NetworkManager.Instance.AddRPC("PouchOpenResponseV1", new CoroutineHandler(Ignore), new CoroutineHandler(ReceiveResponse));
			active = true;
		}

		[HarmonyPriority(800)]
		private static bool Prefix(Player __instance, Inventory __0, ItemData __1, ref bool __result)
		{
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015b: Unknown result type (might be due to invalid IL or missing references)
			string text = (Object.op_Implicit((Object)(object)__1?.m_dropPrefab) ? ((Object)__1.m_dropPrefab).name : null);
			if (text == null || Plugin.Instance?.Pouches == null || !Plugin.Instance.Pouches.IsPouch(text))
			{
				return true;
			}
			__result = false;
			try
			{
				if (!active || !Object.op_Implicit((Object)(object)ZNet.instance) || !Object.op_Implicit((Object)(object)__instance) || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !((Character)__instance).IsOwner() || ((Character)__instance).IsDead() || ((Character)__instance).GetZDOID() == ZDOID.None || __0 != ((Humanoid)__instance).GetInventory() || !__0.ContainsItem(__1) || __1.m_stack < 1)
				{
					return false;
				}
				if (pending != null)
				{
					Show(__instance, "A pouch is already being opened.");
					return false;
				}
				if (Plugin.Instance.LootConfig == null || !Plugin.Instance.LootConfig.TryGetSnapshot(out var _) || (!ZNet.instance.IsServer() && !Plugin.Instance.LootConfig.IsServerSynchronized))
				{
					Show(__instance, "Server loot tables are not ready. Your pouch was kept.");
					return false;
				}
				OpenRequest request = new OpenRequest
				{
					Id = Guid.NewGuid().ToString("N"),
					Sequence = checked(++nextSequence),
					PlayerId = ((Character)__instance).GetZDOID(),
					Pouch = text
				};
				pending = new PendingOpen
				{
					Request = request,
					Player = __instance,
					Inventory = __0,
					Item = __1,
					Deadline = Time.realtimeSinceStartup + 15f
				};
				if (ZNet.instance.IsServer())
				{
					OpenResponse openResponse = HandleRequest(ZNet.GetUID(), request);
					if (openResponse != null)
					{
						__result = Complete(openResponse);
					}
				}
				else
				{
					ZNetPeer serverPeer = ZNet.instance.GetServerPeer();
					if (serverPeer == null)
					{
						throw new InvalidOperationException("Server connection is unavailable.");
					}
					requestRpc.SendPackage(serverPeer.m_uid, WriteRequest(request));
				}
			}
			catch (Exception exception)
			{
				pending = null;
				Plugin.Instance?.LogLootError("Pouch opening failed", exception);
				if (Object.op_Implicit((Object)(object)__instance))
				{
					Show(__instance, "Pouch opening failed. Check the mod log.");
				}
			}
			return false;
		}

		private static IEnumerator ReceiveRequest(long sender, ZPackage package)
		{
			if (!active || !Object.op_Implicit((Object)(object)ZNet.instance) || !ZNet.instance.IsServer())
			{
				yield break;
			}
			try
			{
				OpenResponse openResponse = HandleRequest(sender, ReadRequest(package));
				if (openResponse != null)
				{
					responseRpc.SendPackage(sender, WriteResponse(openResponse));
				}
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Rejected pouch request", exception);
			}
		}

		private static bool OwnsCharacter(long sender, ZDOID character)
		{
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0067: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (sender == ZNet.GetUID())
			{
				if (Object.op_Implicit((Object)(object)Player.m_localPlayer) && ((Character)Player.m_localPlayer).IsOwner() && !((Character)Player.m_localPlayer).IsDead())
				{
					return ((Character)Player.m_localPlayer).GetZDOID() == character;
				}
				return false;
			}
			ZNetPeer peer = ZNet.instance.GetPeer(sender);
			ZDOMan instance = ZDOMan.instance;
			ZDO val = ((instance != null) ? instance.GetZDO(character) : null);
			if (peer != null && peer.m_characterID == character && val != null && val.GetLong("playerID", 0L) != 0L)
			{
				return !val.GetBool("dead", false);
			}
			return false;
		}

		private static OpenResponse HandleRequest(long sender, OpenRequest request)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			if (!OwnsCharacter(sender, request.PlayerId))
			{
				return null;
			}
			if (!Requests.TryGetValue(sender, out var value))
			{
				Requests.Add(sender, value = new RequestState());
			}
			RequestDecision requestDecision = value.Accept(request.Sequence, Time.realtimeSinceStartup);
			if (requestDecision == RequestDecision.Duplicate)
			{
				return null;
			}
			OpenResponse openResponse = new OpenResponse
			{
				Request = request,
				Status = OpenStatus.Unavailable
			};
			if (requestDecision == RequestDecision.Busy)
			{
				openResponse.Status = OpenStatus.Busy;
				return openResponse;
			}
			if (Plugin.Instance?.Pouches == null || !Plugin.Instance.Pouches.IsPouch(request.Pouch) || Plugin.Instance.LootConfig == null || !Plugin.Instance.LootConfig.TryGetSnapshot(out var snapshot) || !snapshot.PouchLoot.TryGetValue(request.Pouch, out var value2))
			{
				return openResponse;
			}
			try
			{
				openResponse.Entries = RollLoot(value2, GetStackSize, () => Random.value, (int min, int max) => Random.Range(min, max + 1));
				openResponse.Fallback = Plugin.Instance.DropOnGroundIfInventoryFull.Value;
				openResponse.Status = OpenStatus.Approved;
			}
			catch (Exception exception)
			{
				openResponse.Status = OpenStatus.InvalidLoot;
				Plugin.Instance.LogLootError("Invalid loot for pouch '" + request.Pouch + "'", exception);
			}
			return openResponse;
		}

		private static IEnumerator ReceiveResponse(long sender, ZPackage package)
		{
			if (!active || !Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.IsServer() || sender == 0L || sender != (ZNet.instance.GetServerPeer()?.m_uid ?? 0))
			{
				yield break;
			}
			try
			{
				Complete(ReadResponse(package));
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Rejected pouch response", exception);
			}
		}

		private static bool Complete(OpenResponse response)
		{
			//IL_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			PendingOpen pendingOpen = pending;
			if (pendingOpen == null || !Matches(pendingOpen.Request, response.Request))
			{
				return false;
			}
			pending = null;
			Player player = pendingOpen.Player;
			if (!Object.op_Implicit((Object)(object)player) || (Object)(object)player != (Object)(object)Player.m_localPlayer || !((Character)player).IsOwner() || ((Character)player).IsDead() || ((Character)player).GetZDOID() != response.Request.PlayerId || Time.realtimeSinceStartup >= pendingOpen.Deadline)
			{
				return false;
			}
			if (response.Status != OpenStatus.Approved)
			{
				Show(player, (response.Status == OpenStatus.Busy) ? "Please wait briefly before opening another pouch." : "This pouch's server loot table is unavailable or invalid. Your pouch was kept.");
				return false;
			}
			if (pendingOpen.Inventory != ((Humanoid)player).GetInventory() || !pendingOpen.Inventory.ContainsItem(pendingOpen.Item) || pendingOpen.Item.m_stack < 1 || !Object.op_Implicit((Object)(object)pendingOpen.Item.m_dropPrefab) || ((Object)pendingOpen.Item.m_dropPrefab).name != response.Request.Pouch)
			{
				Show(player, "The pouch moved out of your inventory. Nothing was consumed.");
				return false;
			}
			try
			{
				ValidateGrant(response.Entries, GetStackSize);
			}
			catch (Exception exception)
			{
				Plugin.Instance?.LogLootError("Pouch rewards unavailable locally", exception);
				Show(player, "A reward item is unavailable. Your pouch was kept. Check the mod log.");
				return false;
			}
			if (Plugin.Instance?.Pouches == null || !Plugin.Instance.Pouches.TryGetOpenMessage(response.Request.Pouch, out var openMessage))
			{
				Show(player, "The pouch definition is unavailable. Your pouch was kept.");
				return false;
			}
			if (!pendingOpen.Inventory.RemoveOneItem(pendingOpen.Item))
			{
				return false;
			}
			openMessage = FormatSummary(CharacterDropPatch.Deliver(player, response.Entries, response.Fallback, showOverflowMessages: false), openMessage, Plugin.Instance.ShowLootNotification.Value);
			((Character)player).Message((MessageType)2, openMessage, 0, (Sprite)null, false);
			return true;
		}

		internal static bool Matches(OpenRequest a, OpenRequest b)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			if (a != null && b != null && a.Id == b.Id && a.Sequence == b.Sequence && a.PlayerId == b.PlayerId)
			{
				return a.Pouch == b.Pouch;
			}
			return false;
		}

		private static int GetStackSize(string name)
		{
			GameObject val = (Object.op_Implicit((Object)(object)ObjectDB.instance) ? ObjectDB.instance.GetItemPrefab(name) : null);
			ItemDrop val2 = (Object.op_Implicit((Object)(object)val) ? val.GetComponent<ItemDrop>() : null);
			if (!Object.op_Implicit((Object)(object)val2) || !Object.op_Implicit((Object)(object)val.GetComponent<ZNetView>()) || val2.m_itemData?.m_shared == null)
			{
				throw new InvalidOperationException("Reward prefab '" + name + "' is unavailable or is not a networked item.");
			}
			return Math.Max(1, val2.m_itemData.m_shared.m_maxStackSize);
		}

		internal static List<CharacterDropPatch.GrantEntry> RollLoot(IReadOnlyList<LootEntryDto> table, Func<string, int> stackSize, Func<double> sample, Func<int, int, int> amount)
		{
			if (table == null || table.Count > 256)
			{
				throw new InvalidOperationException("Invalid pouch table.");
			}
			List<CharacterDropPatch.GrantEntry> list = new List<CharacterDropPatch.GrantEntry>();
			foreach (LootEntryDto item in table)
			{
				if (item == null || float.IsNaN(item.Chance) || float.IsInfinity(item.Chance) || item.Chance < 0f || item.Chance > 1f || item.MinAmount < 1 || item.MaxAmount < item.MinAmount)
				{
					throw new InvalidOperationException("Invalid pouch loot entry.");
				}
				list.Add(new CharacterDropPatch.GrantEntry
				{
					Prefab = item.ItemPrefab,
					Amount = item.MaxAmount
				});
			}
			ValidateGrant(list, stackSize);
			List<CharacterDropPatch.GrantEntry> list2 = new List<CharacterDropPatch.GrantEntry>();
			foreach (LootEntryDto item2 in table)
			{
				if (CharacterDropPatch.SuccessfulRoll(item2.Chance, sample()))
				{
					int num = amount(item2.MinAmount, item2.MaxAmount);
					if (num < item2.MinAmount || num > item2.MaxAmount)
					{
						throw new InvalidOperationException("Invalid random amount.");
					}
					list2.Add(new CharacterDropPatch.GrantEntry
					{
						Prefab = item2.ItemPrefab,
						Amount = num
					});
				}
			}
			return list2;
		}

		internal static void ValidateGrant(IReadOnlyList<CharacterDropPatch.GrantEntry> entries, Func<string, int> stackSize = null)
		{
			if (entries == null || entries.Count > 256)
			{
				throw new InvalidOperationException("Too many reward entries.");
			}
			long num = 0L;
			long num2 = 0L;
			foreach (CharacterDropPatch.GrantEntry entry in entries)
			{
				if (entry == null || !ValidPrefab(entry.Prefab) || entry.Amount < 1)
				{
					throw new InvalidOperationException("Invalid reward entry.");
				}
				num += entry.Amount;
				if (num > 10000)
				{
					throw new InvalidOperationException("Pouch exceeds 10,000 items.");
				}
				if (stackSize != null)
				{
					int num3 = stackSize(entry.Prefab);
					if (num3 < 1)
					{
						throw new InvalidOperationException("Invalid stack size.");
					}
					num2 += ((long)entry.Amount + (long)num3 - 1) / num3;
					if (num2 > 256)
					{
						throw new InvalidOperationException("Pouch exceeds 256 stacks.");
					}
				}
			}
		}

		internal static string FormatSummary(IReadOnlyList<CharacterDropPatch.DeliveryResult> results, string openMessage = null, bool showDepositNotifications = true)
		{
			string opening = openMessage ?? ((results.Count == 0) ? "Pouch opened." : "Pouch opened!");
			if (results.Count == 0)
			{
				return LimitSummary(opening, "No rewards this time.", (openMessage == null) ? " " : "\n");
			}
			List<string> list = new List<string>();
			foreach (IGrouping<string, CharacterDropPatch.DeliveryResult> item in from result in results
				group result by result.Name)
			{
				int num = item.Sum((CharacterDropPatch.DeliveryResult result) => result.Received);
				int num2 = item.Sum((CharacterDropPatch.DeliveryResult result) => result.Dropped);
				int num3 = item.Sum((CharacterDropPatch.DeliveryResult result) => result.Undelivered);
				List<string> list2 = new List<string>();
				if (showDepositNotifications)
				{
					list2.Add($"{num} gained");
				}
				if (num2 > 0)
				{
					list2.Add($"{num2} at your feet");
				}
				if (num3 > 0)
				{
					list2.Add($"{num3} not collected");
				}
				if (list2.Count > 0)
				{
					list.Add(item.Key + ": " + string.Join(", ", list2));
				}
			}
			return LimitSummary(opening, string.Join("; ", list), (list.Count == 0) ? "" : "\n");
		}

		private static string LimitSummary(string opening, string details, string separator = "\n")
		{
			string text = opening + separator + details;
			if (text.Length <= 400)
			{
				return text;
			}
			Plugin.Instance?.LogPouchSummary(text);
			return string.Concat(text.Substring(0, 340) + "...", "; full reward summary in the mod log.");
		}

		private static bool ValidPrefab(string name)
		{
			if (!string.IsNullOrEmpty(name) && name.Length <= 128)
			{
				return name.All((char c) => (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '.' || c == '-');
			}
			return false;
		}

		private static void WriteEnvelope(ZPackage package, OpenRequest request)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			package.Write(1);
			package.Write(request.Id);
			package.Write(request.Sequence);
			package.Write(request.PlayerId);
			package.Write(request.Pouch);
		}

		private static OpenRequest ReadEnvelope(ZPackage package)
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			if (package == null || package.Size() > 65536 || package.ReadInt() != 1)
			{
				throw new InvalidOperationException("Invalid pouch protocol.");
			}
			OpenRequest openRequest = new OpenRequest
			{
				Id = package.ReadString(),
				Sequence = package.ReadLong(),
				PlayerId = package.ReadZDOID(),
				Pouch = package.ReadString()
			};
			if (!Guid.TryParseExact(openRequest.Id, "N", out var _) || openRequest.Sequence < 1 || openRequest.PlayerId == ZDOID.None || !ValidPrefab(openRequest.Pouch))
			{
				throw new InvalidOperationException("Invalid pouch request identity.");
			}
			return openRequest;
		}

		internal static ZPackage WriteRequest(OpenRequest request)
		{
			//IL_0000: 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_000c: Expected O, but got Unknown
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			WriteEnvelope(val, request);
			val.SetPos(0);
			return val;
		}

		internal static OpenRequest ReadRequest(ZPackage package)
		{
			OpenRequest result = ReadEnvelope(package);
			if (package.GetPos() != package.Size())
			{
				throw new InvalidOperationException("Trailing pouch request data.");
			}
			return result;
		}

		internal static ZPackage WriteResponse(OpenResponse response)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			WriteEnvelope(val, response.Request);
			val.Write((int)response.Status);
			val.Write(response.Fallback);
			val.Write(response.Entries.Count);
			foreach (CharacterDropPatch.GrantEntry entry in response.Entries)
			{
				val.Write(entry.Prefab);
				val.Write(entry.Amount);
			}
			val.SetPos(0);
			return val;
		}

		internal static OpenResponse ReadResponse(ZPackage package)
		{
			OpenResponse openResponse = new OpenResponse
			{
				Request = ReadEnvelope(package),
				Status = (OpenStatus)package.ReadInt(),
				Fallback = package.ReadBool()
			};
			int num = package.ReadInt();
			if (!Enum.IsDefined(typeof(OpenStatus), openResponse.Status) || num < 0 || num > 256 || (openResponse.Status != OpenStatus.Approved && (num != 0 || openResponse.Fallback)))
			{
				throw new InvalidOperationException("Invalid pouch response status or entry count.");
			}
			for (int i = 0; i < num; i++)
			{
				openResponse.Entries.Add(new CharacterDropPatch.GrantEntry
				{
					Prefab = package.ReadString(),
					Amount = package.ReadInt()
				});
			}
			ValidateGrant(openResponse.Entries);
			if (package.GetPos() != package.Size())
			{
				throw new InvalidOperationException("Trailing pouch response data.");
			}
			return openResponse;
		}

		private static void Show(Player player, string text)
		{
			((Character)player).Message((MessageType)2, text, 0, (Sprite)null, false);
		}

		private static IEnumerator Ignore(long sender, ZPackage package)
		{
			yield break;
		}

		internal static void Update()
		{
			if (!active)
			{
				return;
			}
			PendingOpen pendingOpen = pending;
			if (pendingOpen != null && (Time.realtimeSinceStartup >= pendingOpen.Deadline || !Object.op_Implicit((Object)(object)pendingOpen.Player) || (Object)(object)pendingOpen.Player != (Object)(object)Player.m_localPlayer || ((Character)pendingOpen.Player).IsDead() || !Object.op_Implicit((Object)(object)ZNet.instance)))
			{
				pending = null;
				if (Object.op_Implicit((Object)(object)pendingOpen.Player) && (Object)(object)pendingOpen.Player == (Object)(object)Player.m_localPlayer)
				{
					Show(pendingOpen.Player, "Pouch opening canceled or timed out. Your pouch was kept.");
				}
			}
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
			{
				long[] array = Requests.Keys.Where((long sender) => sender != ZNet.GetUID() && ZNet.instance.GetPeer(sender) == null).ToArray();
				foreach (long key in array)
				{
					Requests.Remove(key);
				}
			}
		}

		internal static void Clear()
		{
			pending = null;
			Requests.Clear();
		}

		internal static void Shutdown()
		{
			active = false;
			Clear();
			requestRpc = null;
			responseRpc = null;
		}
	}
}
namespace HarrisonCustomLootTable.Models
{
	[JsonObject(/*Could not decode attribute arguments.*/)]
	public sealed class LootEntryDto
	{
		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public string ItemPrefab { get; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public float Chance { get; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public int MinAmount { get; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public int MaxAmount { get; }

		[JsonConstructor]
		public LootEntryDto(string itemPrefab, float chance, int minAmount, int maxAmount)
		{
			ItemPrefab = itemPrefab;
			Chance = chance;
			MinAmount = minAmount;
			MaxAmount = maxAmount;
		}
	}
	[JsonObject(/*Could not decode attribute arguments.*/)]
	public sealed class MonsterDropTableDto
	{
		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public IReadOnlyList<LootEntryDto> Drops { get; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		[JsonConverter(typeof(StringEnumConverter))]
		public DistributionMode? DistributionMode { get; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public bool? KeepVanillaDropsOnGround { get; }

		[JsonConstructor]
		public MonsterDropTableDto(List<LootEntryDto> drops, DistributionMode? distributionMode = null, bool? keepVanillaDropsOnGround = null)
		{
			Drops = ((drops == null) ? null : new List<LootEntryDto>(drops).AsReadOnly());
			DistributionMode = distributionMode;
			KeepVanillaDropsOnGround = keepVanillaDropsOnGround;
		}
	}
	[JsonObject(/*Could not decode attribute arguments.*/)]
	public sealed class LootConfigurationDto
	{
		public const int CurrentSchemaVersion = 1;

		[JsonProperty]
		public int SchemaVersion => 1;

		[JsonProperty]
		public IReadOnlyDictionary<string, MonsterDropTableDto> MonsterDrops { get; }

		[JsonProperty]
		public IReadOnlyDictionary<string, IReadOnlyList<LootEntryDto>> PouchLoot { get; }

		internal LootConfigurationDto(Dictionary<string, MonsterDropTableDto> monsters, Dictionary<string, List<LootEntryDto>> pouches)
		{
			MonsterDrops = new ReadOnlyDictionary<string, MonsterDropTableDto>(new Dictionary<string, MonsterDropTableDto>(monsters, StringComparer.Ordinal));
			Dictionary<string, IReadOnlyList<LootEntryDto>> dictionary = new Dictionary<string, IReadOnlyList<LootEntryDto>>(StringComparer.Ordinal);
			foreach (KeyValuePair<string, List<LootEntryDto>> pouch in pouches)
			{
				dictionary.Add(pouch.Key, new List<LootEntryDto>(pouch.Value).AsReadOnly());
			}
			PouchLoot = new ReadOnlyDictionary<string, IReadOnlyList<LootEntryDto>>(dictionary);
		}
	}
	public enum DistributionMode
	{
		LastHit,
		MostDamage,
		EveryPlayer
	}
	[JsonObject(/*Could not decode attribute arguments.*/)]
	public sealed class PouchDefinition
	{
		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public string PrefabName { get; set; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public string DisplayName { get; set; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public string Description { get; set; }

		[JsonProperty]
		public string OpenMessage { get; set; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public string BasePrefabToClone { get; set; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public int MaxStackSize { get; set; }

		[JsonProperty(/*Could not decode attribute arguments.*/)]
		public float Weight { get; set; }
	}
}
namespace HarrisonCustomLootTable.Managers
{
	public sealed class LootConfigManager : IDisposable
	{
		public const string MonsterDropsFileName = "HarrisonCustomLootTable_MonsterDrops.json";

		public const string PouchLootFileName = "HarrisonCustomLootTable_PouchLoot.json";

		private const int MaxJsonBytes = 1048576;

		private const int MaxSyncBytes = 3145728;

		private const int MaxTables = 4096;

		private const int MaxEntriesPerTable = 256;

		private static readonly Regex PrefabPattern = new Regex("^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$", RegexOptions.CultureInvariant);

		private readonly object gate = new object();

		private readonly string directory;

		private readonly ManualLogSource logger;

		private LootConfigurationDto current;

		private LootConfigurationDto local;

		private string payload;

		private bool initialized;

		private bool disposed;

		private bool sessionActive;

		private bool remoteSession;

		private CustomRPC syncRpc;

		public const string DefaultMonsterDropsJson = "{\n  \"Greydwarf\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"Resin\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 2 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 0.1, \"MinAmount\": 1, \"MaxAmount\": 1 }\n    ],\n    \"KeepVanillaDropsOnGround\": true\n  },\n  \"Troll\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"Coins\", \"Chance\": 1.0, \"MinAmount\": 20, \"MaxAmount\": 40 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 0.5, \"MinAmount\": 1, \"MaxAmount\": 2 }\n    ],\n    \"DistributionMode\": \"MostDamage\",\n    \"KeepVanillaDropsOnGround\": false\n  },\n  \"Eikthyr\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"HardAntler\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 3 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 1 }\n    ],\n    \"DistributionMode\": \"EveryPlayer\",\n    \"KeepVanillaDropsOnGround\": true\n  }\n}";

		public const string DefaultPouchLootJson = "{\n  \"HarrisonRewardPouch\": [\n    { \"ItemPrefab\": \"Coins\", \"Chance\": 1.0, \"MinAmount\": 10, \"MaxAmount\": 30 },\n    { \"ItemPrefab\": \"Amber\", \"Chance\": 0.25, \"MinAmount\": 1, \"MaxAmount\": 2 },\n    { \"ItemPrefab\": \"Ruby\", \"Chance\": 0.05, \"MinAmount\": 1, \"MaxAmount\": 1 }\n  ]\n}";

		public string MonsterDropsPath => Path.Combine(directory, "HarrisonCustomLootTable_MonsterDrops.json");

		public string PouchLootPath => Path.Combine(directory, "HarrisonCustomLootTable_PouchLoot.json");

		public bool IsReady
		{
			get
			{
				lock (gate)
				{
					return current != null;
				}
			}
		}

		public bool IsServerSynchronized
		{
			get
			{
				lock (gate)
				{
					return remoteSession && current != null;
				}
			}
		}

		public LootConfigurationDto Snapshot
		{
			get
			{
				lock (gate)
				{
					return current ?? throw new InvalidOperationException("Loot configuration is not ready.");
				}
			}
		}

		public bool TryGetSnapshot(out LootConfigurationDto snapshot)
		{
			lock (gate)
			{
				snapshot = current;
				return snapshot != null;
			}
		}

		public LootConfigManager(string directory, ManualLogSource logger)
		{
			this.directory = directory ?? throw new ArgumentNullException("directory");
			this.logger = logger ?? throw new ArgumentNullException("logger");
		}

		public void Initialize()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			//IL_0055: Expected O, but got Unknown
			if (disposed)
			{
				throw new ObjectDisposedException("LootConfigManager");
			}
			if (!initialized)
			{
				EnsureDefaultFiles(directory);
				LoadLocalFiles();
				syncRpc = NetworkManager.Instance.AddRPC("LootConfigurationV1", new CoroutineHandler(RejectClientUpload), new CoroutineHandler(ReceiveServerSnapshot));
				SynchronizationManager.Instance.AddInitialSynchronization(syncRpc, (Func<ZNetPeer, ZPackage>)CreateInitialPackage);
				initialized = true;
			}
		}

		internal void BeginSession(bool isServer)
		{
			lock (gate)
			{
				sessionActive = true;
				remoteSession = !isServer;
				current = null;
			}
			if (isServer)
			{
				LoadLocalFiles();
			}
		}

		internal void EndSession()
		{
			lock (gate)
			{
				sessionActive = false;
				remoteSession = false;
				current = local;
			}
		}

		private void LoadLocalFiles()
		{
			try
			{
				LootConfigurationDto lootConfigurationDto = LoadDirectory(directory);
				string text = SerializeSnapshot(lootConfigurationDto);
				lock (gate)
				{
					local = lootConfigurationDto;
					current = lootConfigurationDto;
					payload = text;
				}
				logger.LogInfo((object)$"Loaded loot tables: {lootConfigurationDto.MonsterDrops.Count} monsters, {lootConfigurationDto.PouchLoot.Count} pouches.");
			}
			catch (Exception arg)
			{
				logger.LogError((object)$"Loot JSON loading failed; loot data is unavailable. Existing files were preserved. {arg}");
				lock (gate)
				{
					current = null;
					local = null;
					payload = null;
				}
			}
		}

		private ZPackage CreateInitialPackage(ZNetPeer peer)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			if (!disposed && Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsServer())
			{
				string text;
				lock (gate)
				{
					text = payload;
				}
				if (text == null)
				{
					logger.LogError((object)"Refusing client connection: server loot configuration is invalid.");
					ZNet.instance.Disconnect(peer);
					return new ZPackage();
				}
				ZPackage val = new ZPackage();
				val.Write(text);
				val.SetPos(0);
				return val;
			}
			return new ZPackage();
		}

		private IEnumerator RejectClientUpload(long sender, ZPackage package)
		{
			logger.LogWarning((object)$"Ignored client loot configuration upload from peer {sender}.");
			yield break;
		}

		private IEnumerator ReceiveServerSnapshot(long sender, ZPackage package)
		{
			if (disposed || !Object.op_Implicit((Object)(object)ZNet.instance) || ZNet.instance.IsServer() || !IsAuthorizedSender(sender, ZNet.instance.GetServerPeer()?.m_uid ?? 0))
			{
				yield break;
			}
			try
			{
				LootConfigurationDto lootConfigurationDto = ReadSyncPackage(package);
				lock (gate)
				{
					if (!sessionActive || !remoteSession)
					{
						yield break;
					}
					current = lootConfigurationDto;
				}
				logger.LogInfo((object)$"Received server loot tables: {lootConfigurationDto.MonsterDrops.Count} monsters, {lootConfigurationDto.PouchLoot.Count} pouches.");
			}
			catch (Exception arg)
			{
				logger.LogError((object)$"Invalid server loot configuration; disconnecting. {arg}");
				lock (gate)
				{
					current = null;
				}
				ZNetPeer peer = ZNet.instance.GetPeer(sender);
				if (peer != null)
				{
					ZNet.instance.Disconnect(peer);
				}
			}
		}

		internal static bool IsAuthorizedSender(long sender, long server)
		{
			if (server != 0L)
			{
				return sender == server;
			}
			return false;
		}

		internal static LootConfigurationDto ReadSyncPackage(ZPackage package)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0030: Expected O, but got Unknown
			if (package == null || package.Size() <= 0 || package.Size() > 3145728)
			{
				throw new InvalidDataException("Invalid loot synchronization package size.");
			}
			ZPackage val = new ZPackage(package.GetArray());
			string json = val.ReadString();
			if (val.GetPos() != val.Size())
			{
				throw new InvalidDataException("Trailing loot synchronization data.");
			}
			return ParseSnapshot(json);
		}

		public static void EnsureDefaultFiles(string directory)
		{
			Directory.CreateDirectory(directory);
			CreateIfMissing(Path.Combine(directory, "HarrisonCustomLootTable_MonsterDrops.json"), "{\n  \"Greydwarf\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"Resin\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 2 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 0.1, \"MinAmount\": 1, \"MaxAmount\": 1 }\n    ],\n    \"KeepVanillaDropsOnGround\": true\n  },\n  \"Troll\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"Coins\", \"Chance\": 1.0, \"MinAmount\": 20, \"MaxAmount\": 40 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 0.5, \"MinAmount\": 1, \"MaxAmount\": 2 }\n    ],\n    \"DistributionMode\": \"MostDamage\",\n    \"KeepVanillaDropsOnGround\": false\n  },\n  \"Eikthyr\": {\n    \"Drops\": [\n      { \"ItemPrefab\": \"HardAntler\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 3 },\n      { \"ItemPrefab\": \"HarrisonRewardPouch\", \"Chance\": 1.0, \"MinAmount\": 1, \"MaxAmount\": 1 }\n    ],\n    \"DistributionMode\": \"EveryPlayer\",\n    \"KeepVanillaDropsOnGround\": true\n  }\n}");
			CreateIfMissing(Path.Combine(directory, "HarrisonCustomLootTable_PouchLoot.json"), "{\n  \"HarrisonRewardPouch\": [\n    { \"ItemPrefab\": \"Coins\", \"Chance\": 1.0, \"MinAmount\": 10, \"MaxAmount\": 30 },\n    { \"ItemPrefab\": \"Amber\", \"Chance\": 0.25, \"MinAmount\": 1, \"MaxAmount\": 2 },\n    { \"ItemPrefab\": \"Ruby\", \"Chance\": 0.05, \"MinAmount\": 1, \"MaxAmount\": 1 }\n  ]\n}");
		}

		private static void CreateIfMissing(string path, string json)
		{
			if (File.Exists(path))
			{
				return;
			}
			FileStream fileStream;
			try
			{
				fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None);
			}
			catch (IOException) when (File.Exists(path))
			{
				return;
			}
			using (fileStream)
			{
				using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
				streamWriter.Write(json);
			}
		}

		public static LootConfigurationDto LoadDirectory(string directory)
		{
			string monsterJson = ReadBoundedFile(Path.Combine(directory, "HarrisonCustomLootTable_MonsterDrops.json"));
			string pouchJson = ReadBoundedFile(Path.Combine(directory, "HarrisonCustomLootTable_PouchLoot.json"));
			return ParseTables(monsterJson, pouchJson);
		}

		private static string ReadBoundedFile(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
			if (fileStream.Length > 1048576)
			{
				throw new InvalidDataException(path + ": file exceeds 1 MiB.");
			}
			using StreamReader streamReader = new StreamReader(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), detectEncodingFromByteOrderMarks: true);
			return streamReader.ReadToEnd();
		}

		public static LootConfigurationDto ParseTables(string monsterJson, string pouchJson)
		{
			return ParseTables(ParseObject(monsterJson, 1048576), ParseObject(pouchJson, 1048576));
		}

		private static LootConfigurationDto ParseTables(JObject monstersRoot, JObject pouchesRoot)
		{
			//IL_007b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Invalid comparison between Unknown and I4
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Invalid comparison between Unknown and I4
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0140: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Unknown result type (might be due to invalid IL or missing references)
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			//IL_016a: Expected O, but got Unknown
			ValidateKeys(monstersRoot, "MonsterDrops");
			ValidateKeys(pouchesRoot, "PouchLoot");
			foreach (JProperty item in monstersRoot.Properties())
			{
				JToken value = item.Value;
				JToken obj = ((value is JObject) ? value : null) ?? throw new InvalidDataException(item.Name + ": expected an object.");
				ValidateEntries(((JObject)obj)["Drops"], item.Name);
				JToken val2 = ((JObject)obj)["DistributionMode"];
				if (val2 != null && ((int)val2.Type != 8 || !Enum.GetNames(typeof(DistributionMode)).Contains((string)val2)))
				{
					throw new InvalidDataException(item.Name + ": invalid DistributionMode.");
				}
				JToken val3 = ((JObject)obj)["KeepVanillaDropsOnGround"];
				if (val3 != null && (int)val3.Type != 9)
				{
					throw new InvalidDataException(item.Name + ": KeepVanillaDropsOnGround must be a boolean.");
				}
			}
			foreach (JProperty item2 in pouchesRoot.Properties())
			{
				ValidateEntries(item2.Value, item2.Name);
			}
			JsonSerializer val5 = JsonSerializer.Create(new JsonSerializerSettings
			{
				TypeNameHandling = (TypeNameHandling)0,
				MissingMemberHandling = (MissingMemberHandling)1,
				Converters = { (JsonConverter)new StringEnumConverter
				{
					AllowIntegerValues = false
				} }
			});
			Dictionary<string, MonsterDropTableDto> monsters = ((JToken)monstersRoot).ToObject<Dictionary<string, MonsterDropTableDto>>(val5);
			Dictionary<string, List<LootEntryDto>> pouches = ((JToken)pouchesRoot).ToObject<Dictionary<string, List<LootEntryDto>>>(val5);
			return new LootConfigurationDto(monsters, pouches);
		}

		private static JObject ParseObject(string json, int limit)
		{
			//IL_0024: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Expected O, but got Unknown
			if (json == null || Encoding.UTF8.GetByteCount(json) > limit)
			{
				throw new InvalidDataException("Loot JSON exceeds its size limit.");
			}
			using StringReader stringReader = new StringReader(json);
			JsonTextReader val = new JsonTextReader((TextReader)stringReader)
			{
				MaxDepth = 16,
				DateParseHandling = (DateParseHandling)0
			};
			try
			{
				JObject result = JObject.Load((JsonReader)(object)val, new JsonLoadSettings
				{
					DuplicatePropertyNameHandling = (DuplicatePropertyNameHandling)2
				});
				if (((JsonReader)val).Read())
				{
					throw new InvalidDataException("Trailing JSON content.");
				}
				return result;
			}
			finally
			{
				((IDisposable)val)?.Dispose();
			}
		}

		private static void ValidateKeys(JObject root, string context)
		{
			if (((JContainer)root).Count > 4096)
			{
				throw new InvalidDataException(context + ": too many tables.");
			}
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			HashSet<int> hashSet2 = new HashSet<int>();
			foreach (JProperty item in root.Properties())
			{
				if (!ValidPrefab(item.Name) || !hashSet.Add(item.Name) || !hashSet2.Add(StringExtensionMethods.GetStableHashCode(item.Name)))
				{
					throw new InvalidDataException(context + ": invalid, duplicate or colliding prefab name '" + item.Name + "'.");
				}
			}
		}

		private static bool ValidPrefab(string name)
		{
			if (name != null)
			{
				return PrefabPattern.IsMatch(name);
			}
			return false;
		}

		private static void ValidateEntries(JToken token, string context)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Invalid comparison between Unknown and I4
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bb: Invalid comparison between Unknown and I4
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Invalid comparison between Unknown and I4
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Invalid comparison between Unknown and I4
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0154: Invalid comparison between Unknown and I4
			JArray val = (JArray)(object)((token is JArray) ? token : null);
			if (val == null || ((JContainer)val).Count > 256)
			{
				throw new InvalidDataException($"{context}: expected an array with at most {256} drops.");
			}
			foreach (JToken item in val)
			{
				JObject val2 = (JObject)(object)((item is JObject) ? item : null);
				if (val2 == null)
				{
					throw new InvalidDataException(context + ": null or invalid drop entry.");
				}
				JToken obj2 = val2["ItemPrefab"];
				if (obj2 == null || (int)obj2.Type != 8 || !ValidPrefab((string)val2["ItemPrefab"]))
				{
					throw new InvalidDataException(context + ": invalid ItemPrefab.");
				}
				JToken val3 = val2