Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of HarrisonCustomLootTable v0.0.3
BepInEx/plugins/HarrisonCustomLootTable/HarrisonCustomLootTable.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
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