using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using Splatform; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ZariRules")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.3.2.0")] [assembly: AssemblyInformationalVersion("1.3.2")] [assembly: AssemblyProduct("ZariRules")] [assembly: AssemblyTitle("ZariRules")] [assembly: AssemblyVersion("1.3.2.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ZariRules { internal static class CompatibilityProbe { internal const string EaqsGuid = "randyknapp.mods.equipmentandquickslots"; internal const string EpicLootGuid = "randyknapp.mods.epicloot"; internal static bool VerifyGameBuild(out string reason) { Guid moduleVersionId = typeof(Player).Assembly.ManifestModule.ModuleVersionId; if (!GameBuildContracts.IsSupported(moduleVersionId)) { reason = $"unsupported assembly_valheim MVID {moduleVersionId}; expected verified Valheim 1.0.12 or 1.0.7 Windows/Linux build"; return false; } reason = string.Empty; return true; } internal static bool VerifyMethod(Type type, string name, BindingFlags flags, Type returnType, Type[] parameters, string expectedIlSha256, out MethodInfo? method, out string reason) { method = type.GetMethod(name, flags, null, parameters, null); if (method == null || method.ReturnType != returnType) { reason = "missing or changed " + type.FullName + "." + name + "(" + string.Join(",", parameters.Select((Type x) => x.FullName)) + ")"; return false; } byte[] array = method.GetMethodBody()?.GetILAsByteArray(); if (array == null) { reason = "no IL body for " + type.FullName + "." + name; return false; } using SHA256 sHA = SHA256.Create(); string text = BitConverter.ToString(sHA.ComputeHash(array)).Replace("-", string.Empty).ToLowerInvariant(); string b = GameBuildContracts.ExpectedIlHash(type.Assembly.ManifestModule.ModuleVersionId, expectedIlSha256); if (!string.Equals(text, b, StringComparison.Ordinal)) { reason = "IL contract mismatch for " + type.FullName + "." + name + ": " + text; return false; } reason = string.Empty; return true; } internal static bool VerifyEaqs(out bool installed, out string reason) { installed = Chainloader.PluginInfos.TryGetValue("randyknapp.mods.equipmentandquickslots", out var value); if (!installed) { reason = "EAQS absent; base-inventory mode"; return true; } string text = value.Metadata.Version.ToString(); reason = "EAQS " + text + "; game death contracts and patch owners checked separately"; return true; } internal static string InstalledPluginVersion(string guid) { if (!Chainloader.PluginInfos.TryGetValue(guid, out var value)) { return "absent"; } return value.Metadata.Version.ToString(); } internal static bool HasOnlyAllowedPatchOwners(MethodBase method, IReadOnlyCollection allowed, out string reason) { Patches patchInfo = Harmony.GetPatchInfo(method); if (patchInfo == null) { reason = string.Empty; return true; } string[] array = patchInfo.Owners.Where((string owner) => !allowed.Contains(owner)).Distinct(StringComparer.Ordinal).OrderBy((string owner) => owner, StringComparer.Ordinal) .ToArray(); if (array.Length == 0) { reason = string.Empty; return true; } reason = "unexpected Harmony owner(s) on " + method.DeclaringType?.FullName + "." + method.Name + ": " + string.Join(",", array); return false; } } internal static class DeathContext { internal const string HarmonyId = "zari.rules.death-context"; private static readonly DeathScopeTracker Scopes = new DeathScopeTracker(); private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (!CompatibilityProbe.VerifyMethod(typeof(Player), "OnDeath", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21", out MethodInfo method, out reason)) { return false; } try { harmony = new Harmony("zari.rules.death-context"); HarmonyMethod val = new HarmonyMethod(typeof(DeathContext), "Prefix", (Type[])null) { priority = 800 }; HarmonyMethod val2 = new HarmonyMethod(typeof(DeathContext), "Finalizer", (Type[])null); val2.priority = 0; val2.after = new string[1] { "randyknapp.mods.equipmentandquickslots" }; HarmonyMethod val3 = val2; harmony.Patch((MethodBase)method, val, (HarmonyMethod)null, (HarmonyMethod)null, val3, (HarmonyMethod)null); Ready = true; reason = "Player.OnDeath scope"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static bool TryGet(Player player, out DeathScopeTracker.Scope? scope) { return Scopes.TryGetCurrent(player, out scope); } internal static void Reset() { Scopes.Clear(); } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Ready = false; Reset(); } private static void Prefix(Player __instance, out DeathScopeTracker.Scope? __state) { __state = null; if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || !ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady) { return; } __state = Scopes.Open(__instance, ZariRulesPlugin.Instance.Rules.KeepInventoryOnDeath.Value, ZariRulesPlugin.Instance.Rules.KeepSkillsOnDeath.Value); try { Diagnostics.OnDeathStarted(__instance); } catch (Exception ex) { ZariRulesPlugin.LogError("ZR901", "feature=Diagnostics phase=death-start reason=" + ex.GetType().Name); } } private static Exception? Finalizer(Player __instance, Exception? __exception, DeathScopeTracker.Scope? __state) { if (__state != null) { try { Diagnostics.OnDeathFinished(__instance, __state, __exception == null); } catch (Exception ex) { ZariRulesPlugin.LogError("ZR901", "feature=Diagnostics phase=death-finish reason=" + ex.GetType().Name); } finally { try { Scopes.Close(__instance, __state); } catch (Exception ex2) { ZariRulesPlugin.LogError("ZR901", "feature=DeathContext phase=close reason=" + ex2.GetType().Name); } } } return __exception; } } internal static class DeathInventoryFeature { internal const string HarmonyId = "zari.rules.keep-inventory"; private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Expected O, but got Unknown //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Expected O, but got Unknown if (!DeathContext.Ready) { reason = "death context unavailable"; return false; } if (!CompatibilityProbe.VerifyEaqs(out bool installed, out string reason2)) { reason = reason2; return false; } if (!CompatibilityProbe.VerifyMethod(typeof(Player), "CreateTombStone", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "00f13b0d81783d11221910d3b2090f27918f53957ea98b114af49d151dacbd98", out MethodInfo method, out reason) || !CompatibilityProbe.VerifyMethod(typeof(Player), "OnDeath", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21", out MethodInfo method2, out reason) || !CompatibilityProbe.VerifyMethod(typeof(Character), "CheckDeath", BindingFlags.Instance | BindingFlags.NonPublic, typeof(void), Type.EmptyTypes, "fef54e1a3e6de1ea05ab81ffd5346eaea2c503b00ccefcc342c4ae862caaa095", out MethodInfo method3, out reason)) { return false; } HashSet allowed = new HashSet(StringComparer.Ordinal) { "randyknapp.mods.equipmentandquickslots" }; HashSet allowed2 = new HashSet(StringComparer.Ordinal) { "randyknapp.mods.equipmentandquickslots", "zari.rules.death-context" }; if (!CompatibilityProbe.HasOnlyAllowedPatchOwners(method, allowed, out reason) || !CompatibilityProbe.HasOnlyAllowedPatchOwners(method2, allowed2, out reason) || !CompatibilityProbe.HasOnlyAllowedPatchOwners(method3, allowed, out reason)) { return false; } try { harmony = new Harmony("zari.rules.keep-inventory"); harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(DeathInventoryFeature), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = $"K1 Player.CreateTombStone; {reason2}; eaqsInstalled={installed}"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Ready = false; } internal static bool VerifyPatchOwners(out string reason) { if (CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Player), "CreateTombStone", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[2] { "zari.rules.keep-inventory", "randyknapp.mods.equipmentandquickslots" }, out reason) && CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[3] { "zari.rules.death-context", "zari.rules.keep-skills", "randyknapp.mods.equipmentandquickslots" }, out reason)) { return CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Character), "CheckDeath", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[2] { "zari.rules.diagnostics", "randyknapp.mods.equipmentandquickslots" }, out reason); } return false; } private static bool Prefix(Player __instance) { if (!Ready || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || !DeathContext.TryGet(__instance, out DeathScopeTracker.Scope scope) || scope == null || !scope.KeepInventory) { return true; } scope.TombstoneSuppressed = true; Diagnostics.Info("ZR202", "tombstoneBranch=suppressed transferredByZariRules=0"); return false; } } internal sealed class DeathScopeTracker where TKey : class { internal sealed class Scope { internal bool KeepInventory { get; } internal bool KeepSkills { get; } internal bool TombstoneSuppressed { get; set; } internal Scope(bool keepInventory, bool keepSkills) { KeepInventory = keepInventory; KeepSkills = keepSkills; } } private readonly Dictionary> scopes = new Dictionary>(); internal Scope Open(TKey key, bool keepInventory, bool keepSkills) { if (!scopes.TryGetValue(key, out Stack value)) { value = new Stack(); scopes.Add(key, value); } Scope scope = new Scope(keepInventory, keepSkills); value.Push(scope); return scope; } internal bool TryGetCurrent(TKey key, out Scope? scope) { if (scopes.TryGetValue(key, out Stack value) && value.Count > 0) { scope = value.Peek(); return true; } scope = null; return false; } internal void Close(TKey key, Scope scope) { if (!scopes.TryGetValue(key, out Stack value) || value.Count == 0 || value.Peek() != scope) { throw new InvalidOperationException("Попытка закрыть неактивный death scope."); } value.Pop(); if (value.Count == 0) { scopes.Remove(key); } } internal void Clear() { scopes.Clear(); } } internal static class Diagnostics { private sealed class Snapshot { internal long Sequence { get; set; } internal int Stacks { get; private set; } internal int Units { get; private set; } internal string ItemDigest { get; private set; } = string.Empty; internal string SkillDigest { get; private set; } = string.Empty; internal HashSet References { get; } = new HashSet(ReferenceComparer.Instance); private Snapshot() { } internal static Snapshot Capture(Player player) { Snapshot snapshot = new Snapshot(); List list = new List(); foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems()) { snapshot.References.Add(allItem); snapshot.Stacks++; snapshot.Units += allItem.m_stack; string text = string.Join(";", from pair in allItem.m_customData.OrderBy, string>((KeyValuePair pair) => pair.Key, StringComparer.Ordinal) select pair.Key + "=" + pair.Value); string text2 = (((Object)(object)allItem.m_dropPrefab == (Object)null) ? "" : Utils.GetPrefabName(allItem.m_dropPrefab)); list.Add(string.Join("|", text2, allItem.m_stack, allItem.m_quality, allItem.m_variant, allItem.m_durability.ToString("R"), allItem.m_gridPos.x, allItem.m_gridPos.y, allItem.m_equipped, text)); } list.Sort(StringComparer.Ordinal); snapshot.ItemDigest = Hash(string.Join("\n", list)); IEnumerable values = from skill in ((Character)player).GetSkills().GetSkillList() orderby (int)skill.m_info.m_skill select $"{(int)skill.m_info.m_skill}:{FloatBits(skill.m_level):x8}:{FloatBits(skill.m_accumulator):x8}"; snapshot.SkillDigest = Hash(string.Join(";", values)); return snapshot; } private static int FloatBits(float value) { return BitConverter.ToInt32(BitConverter.GetBytes(value), 0); } private static string Hash(string value) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(value))).Replace("-", string.Empty).ToLowerInvariant(); } } private sealed class ReferenceComparer : IEqualityComparer where T : class { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T x, T y) { return x == y; } public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); } } internal const string HarmonyId = "zari.rules.diagnostics"; private static readonly Dictionary BeforeDeath = new Dictionary(); private static Harmony? harmony; private static long sequence; internal static bool Install(out string reason) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown if (!CompatibilityProbe.VerifyMethod(typeof(Character), "CheckDeath", BindingFlags.Instance | BindingFlags.NonPublic, typeof(void), Type.EmptyTypes, "fef54e1a3e6de1ea05ab81ffd5346eaea2c503b00ccefcc342c4ae862caaa095", out MethodInfo method, out reason)) { return false; } try { harmony = new Harmony("zari.rules.diagnostics"); HarmonyMethod val = new HarmonyMethod(typeof(Diagnostics), "CheckDeathPrefix", (Type[])null); val.priority = 800; val.before = new string[1] { "randyknapp.mods.equipmentandquickslots" }; HarmonyMethod val2 = val; harmony.Patch((MethodBase)method, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); reason = "read-only Character.CheckDeath observer"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Info(string code, string message) { try { ZariRulesPlugin.Instance.Logger.LogInfo((object)(code + " " + message)); } catch { } } internal static void OnDeathStarted(Player player) { if (ZariRulesPlugin.Instance.Rules.DebugLogging.Value) { Snapshot snapshot = Snapshot.Capture(player); if (!BeforeDeath.TryGetValue(player, out Snapshot value)) { value = snapshot; } long num = ++sequence; long num2 = (value.Sequence = num); BeforeDeath[player] = value; Info("ZR200", $"death={num2} started preEaqsStacks={value.Stacks} preEaqsUnits={value.Units} visibleStacks={snapshot.Stacks} visibleUnits={snapshot.Units}"); Info("ZR201", string.Format("death={0} eaqsVersion={1} pendingCount=not-read", num2, CompatibilityProbe.InstalledPluginVersion("randyknapp.mods.equipmentandquickslots"))); } } internal static void OnDeathFinished(Player player, DeathScopeTracker.Scope scope, bool successful) { if (ZariRulesPlugin.Instance.Rules.DebugLogging.Value && BeforeDeath.TryGetValue(player, out Snapshot value)) { Snapshot snapshot = Snapshot.Capture(player); bool flag = value.ItemDigest == snapshot.ItemDigest && value.References.SetEquals(snapshot.References); bool flag2 = value.SkillDigest == snapshot.SkillDigest; Info("ZR204", $"death={value.Sequence} finished={successful.ToString().ToLowerInvariant()} stacks={snapshot.Stacks} units={snapshot.Units} itemDigestEqual={flag.ToString().ToLowerInvariant()} skillDigestEqual={flag2.ToString().ToLowerInvariant()} tombstoneSuppressed={scope.TombstoneSuppressed.ToString().ToLowerInvariant()}"); BeforeDeath.Remove(player); } } internal static void ResetSession() { BeforeDeath.Clear(); sequence = 0L; } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } ResetSession(); } private static void CheckDeathPrefix(Character __instance) { //IL_0040: 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) //IL_0050: Expected O, but got Unknown //IL_0055: Expected O, but got Unknown try { if (ZariRulesPlugin.Instance.Rules.DebugLogging.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && !__instance.IsDead() && !(__instance.GetHealth() > 0f)) { BeforeDeath[(Player)__instance] = Snapshot.Capture((Player)__instance); } } catch (Exception ex) { ZariRulesPlugin.LogError("ZR901", "feature=Diagnostics phase=pre-death reason=" + ex.GetType().Name); } } } internal static class GameBuildContracts { internal static readonly Guid WindowsClient = new Guid("b8a6fd30-3061-43b3-99f2-11c2e315bc54"); internal static readonly Guid LinuxServer = new Guid("e8e9a680-25ea-4b87-bdd4-07412665a2ad"); internal static readonly Guid LegacyWindowsClient = new Guid("db11c804-ebef-403d-8e77-ac219f14bf46"); internal static readonly Guid LegacyLinuxServer = new Guid("0ee5b4bc-8894-42ff-b5dc-b038511e2bfc"); internal static bool IsSupported(Guid mvid) { if (!(mvid == WindowsClient) && !(mvid == LinuxServer) && !(mvid == LegacyWindowsClient)) { return mvid == LegacyLinuxServer; } return true; } internal static string ExpectedIlHash(Guid mvid, string clientHash) { if (mvid == WindowsClient) { return clientHash switch { "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21" => "94a716985990cdfbef2dab44cde6a20c0ddc85ffe65be3b779670aa468339ba7", "00f13b0d81783d11221910d3b2090f27918f53957ea98b114af49d151dacbd98" => "c4771274beb528d85938ca69e5cadb3d9a906a709a67c732c63d8a2ff02f1059", "807a60cdbe58ac91d5620c9f1bbeb6d866a7b0085851c37edc19c08933c48591" => "e3196a9480de19a817132384a4947f5cff2a78d18c73779758bef4ff8e341e8d", "146dc8c182d22edbcc9f2192f3edb621ec03f219bf327c7315ed6f910563a798" => "3715e25250ae63a5a476a287186fded73554c1da919f7d95c794bdb96561fb19", "bf1b5c65eee0066748cbd0b77e326d2e5e3cd2786f3fa80c2d063dccd4522b7a" => "44735573fcec61a54b9e5d998e5ca5704899d4cda93e7246eb4427bce862b8ea", "0d778f0dea28e91896454793f46dc19c0a456883b218ca2113747744a0dd6ba6" => "ade188b9421fb218374901295da958023f47db81d64729e302f0c43042e261af", "0a477dbd4078ac2966eba61c40b1382a8b572b01e39bafeef72681da3a3522fa" => "6fbfa5d44d14c504ba016c163a807933de874e52621e28dde6b672f5a38b2151", "3acb4e2da64e100830c9912ba3dceb86211cd08eccd7b4739fe1e2cf24e95c68" => "d580ea92edcb6c7597328c6762634f54f6977b12abb253bef693a85f7307d64e", "1febeff1fc10e2e78fe1fa15b5ef3ac6dea400e0795c7e26969a1ebd25d64bfb" => "d07178720ccb367e61dee4cb30149ebb1ab37b20bd3a71df15bb42c82c24fc7e", _ => clientHash, }; } if (mvid == LegacyLinuxServer) { return clientHash switch { "0d778f0dea28e91896454793f46dc19c0a456883b218ca2113747744a0dd6ba6" => "2d5538172782de67019d39e75b0e7ef3de1138b0714c3611822c256c38525076", "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21" => "d7a0608475b68e73155b6b013d2fb120054a0b546f58a14365c7f00ccbbdf23f", "00f13b0d81783d11221910d3b2090f27918f53957ea98b114af49d151dacbd98" => "f5c48166fceba41b88101c905968f2c262500a96c99a7b13bcb71fce5db07db8", "807a60cdbe58ac91d5620c9f1bbeb6d866a7b0085851c37edc19c08933c48591" => "af82486e3d28756396e11d0fc467fb5dc76abd9abb1c8b25665d67599247dc06", "146dc8c182d22edbcc9f2192f3edb621ec03f219bf327c7315ed6f910563a798" => "f50af2280c1f078c07be5b9c2a7e7ea3e0155d072a9d7e03f56c4236e50bd095", "bf1b5c65eee0066748cbd0b77e326d2e5e3cd2786f3fa80c2d063dccd4522b7a" => "4fa96ee109e0f3c7cc47e51e716707f5092db65df236c4232e6789098dff9e82", "0a477dbd4078ac2966eba61c40b1382a8b572b01e39bafeef72681da3a3522fa" => "98b819c45679502cd6cdf3ed277e82ce4ef406ec512cb3c2ea4c69f3f673d2c8", "3acb4e2da64e100830c9912ba3dceb86211cd08eccd7b4739fe1e2cf24e95c68" => "249b687a4a7c042248fd71eaf8603f7689de523e9391fa00b75ba632bc985f6e", "1febeff1fc10e2e78fe1fa15b5ef3ac6dea400e0795c7e26969a1ebd25d64bfb" => "2d858424cc340899fcd60b5e2a6cae6e5ecdf83740d46b06524bdaf7e5ec8018", _ => clientHash, }; } if (mvid == LinuxServer) { return clientHash switch { "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21" => "67755886372660c97917c425b7044392277d616b08d0e4b650204d822d12a57c", "00f13b0d81783d11221910d3b2090f27918f53957ea98b114af49d151dacbd98" => "879a784c42f298551ce6bc35b7df42021f51ea647080db4b83d947cee7a793d5", "146dc8c182d22edbcc9f2192f3edb621ec03f219bf327c7315ed6f910563a798" => "cbd7faf66c4a1f1dd70790e57adddc157aa5a90e35ab9a32627b901e0d2e561d", "bf1b5c65eee0066748cbd0b77e326d2e5e3cd2786f3fa80c2d063dccd4522b7a" => "e377de4c79ca316e14e6c0dccfbd264f0d4d8db421b8af7d37b4604aafef033d", "0d778f0dea28e91896454793f46dc19c0a456883b218ca2113747744a0dd6ba6" => "374ff6598caba1aa57a9269740c94d88e1847f124e8393a20e0ace4e04f6c857", "0a477dbd4078ac2966eba61c40b1382a8b572b01e39bafeef72681da3a3522fa" => "7ef5ab570cb12616af2f282a0912cc94a2981ad3efec04e68ddbca4f3991b34d", "3acb4e2da64e100830c9912ba3dceb86211cd08eccd7b4739fe1e2cf24e95c68" => "cff6ba19bd1e26fcd3aa27fcba87be6dcb894f67caa0c1e17273a40313729ada", "1febeff1fc10e2e78fe1fa15b5ef3ac6dea400e0795c7e26969a1ebd25d64bfb" => "c1812adc1cab5ae250f487c6dfa9ee9d41d41a38dc205ec842018b4beab79ee1", _ => clientHash, }; } return clientHash; } } internal static class HammerDurabilityFeature { internal const string HarmonyId = "zari.rules.hammer-durability"; private static readonly HammerDurabilityTracker Tracker = new HammerDurabilityTracker(); private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Expected O, but got Unknown //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown if (Ready) { reason = "unbreakable building hammer and tools active"; return true; } MethodInfo methodInfo = AccessTools.Method(typeof(Player), "GetPlaceDurability", new Type[1] { typeof(ItemData) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Humanoid), "DrainEquipedItemDurability", new Type[2] { typeof(ItemData), typeof(float) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.DeclaredMethod(typeof(ItemDrop), "Awake", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo4 = AccessTools.DeclaredMethod(typeof(ObjectDB), "Awake", Type.EmptyTypes, (Type[])null); MethodInfo methodInfo5 = AccessTools.DeclaredMethod(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null); if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null || methodInfo5 == null) { reason = "missing or changed Player/Humanoid/ObjectDB durability contracts"; return false; } try { harmony = new Harmony("zari.rules.hammer-durability"); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(HammerDurabilityFeature), "GetPlaceDurabilityPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(HammerDurabilityFeature), "DrainEquipedItemDurabilityPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(HammerDurabilityFeature), "ItemAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); HarmonyMethod val = new HarmonyMethod(typeof(HammerDurabilityFeature), "DatabasePostfix", (Type[])null) { priority = 0 }; harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "Player.GetPlaceDurability + Humanoid.DrainEquipedItemDurability + ItemDrop/ObjectDB postfixes"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Tracker.Restore(SetUseDurability); reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Apply(string source) { if (!Ready) { return; } RegisterDatabase(ObjectDB.instance); ItemDrop[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { TrackInstance(array[i]); } bool flag = IsActive(); int num = Tracker.Apply(flag, SetUseDurability); if (flag && (Object)(object)Player.m_localPlayer != (Object)null && ((Humanoid)Player.m_localPlayer).GetInventory() != null) { foreach (ItemData allItem in ((Humanoid)Player.m_localPlayer).GetInventory().GetAllItems()) { if (IsEligible(allItem)) { float maxDurability = allItem.GetMaxDurability(); if (allItem.m_durability < maxDurability) { allItem.m_durability = maxDurability; } } } } Diagnostics.Info("ZR330", $"hammerDurability=applied active={flag} tracked={num} source={source}"); } internal static void ResetSession() { Tracker.Restore(SetUseDurability); } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Ready = false; Tracker.Restore(SetUseDurability); } internal static bool IsEligible(ItemData? item) { if (item?.m_shared == null) { return false; } string prefabName = (((Object)(object)item.m_dropPrefab != (Object)null) ? ((Object)item.m_dropPrefab).name : null); string name = item.m_shared.m_name; string pieceTableName = (((Object)(object)item.m_shared.m_buildPieces != (Object)null) ? ((Object)item.m_shared.m_buildPieces).name : null); bool hasBuildPieces = (Object)(object)item.m_shared.m_buildPieces != (Object)null; return HammerDurabilityRules.IsEligible(prefabName, name, pieceTableName, hasBuildPieces, ZariRulesPlugin.Instance.Rules.UnbreakableHammer.Value, ZariRulesPlugin.Instance.Rules.UnbreakableBuildingTools.Value); } private static bool IsActive() { if (ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady) { if (!ZariRulesPlugin.Instance.Rules.UnbreakableHammer.Value) { return ZariRulesPlugin.Instance.Rules.UnbreakableBuildingTools.Value; } return true; } return false; } private static void SetUseDurability(SharedData shared, bool useDurability) { shared.m_useDurability = useDurability; } private static bool GetPlaceDurabilityPrefix(ItemData __0, ref float __result) { if (IsActive() && IsEligible(__0)) { __result = 0f; return false; } return true; } private static bool DrainEquipedItemDurabilityPrefix(ItemData __0) { if (IsActive() && IsEligible(__0)) { return false; } return true; } private static void ItemAwakePostfix(ItemDrop __instance) { SharedData val = TrackInstance(__instance); if (val != null && IsActive() && IsEligible(__instance.m_itemData)) { val.m_useDurability = false; } } private static void DatabasePostfix(ObjectDB __instance) { RegisterDatabase(__instance); Tracker.Apply(IsActive(), SetUseDurability); } private static void RegisterDatabase(ObjectDB? database) { if (database?.m_items == null) { return; } foreach (GameObject item in database.m_items) { ItemDrop val = (((Object)(object)item != (Object)null) ? item.GetComponent() : null); SharedData val2 = val?.m_itemData?.m_shared; if (val2 != null) { bool isEligible = IsEligible(val?.m_itemData); Tracker.TrackCanonical(val2, val2.m_useDurability, isEligible); } } } private static SharedData? TrackInstance(ItemDrop? itemDrop) { SharedData val = itemDrop?.m_itemData?.m_shared; object obj; if (itemDrop == null) { obj = null; } else { ItemData itemData = itemDrop.m_itemData; if (itemData == null) { obj = null; } else { GameObject dropPrefab = itemData.m_dropPrefab; obj = ((dropPrefab != null) ? dropPrefab.GetComponent() : null); } } SharedData val2 = ((ItemDrop)(obj?)).m_itemData?.m_shared; if (val != null && val2 != null && Tracker.TrackInstance(val, val2)) { return val; } return null; } } internal static class HammerDurabilityRules { internal static bool IsHammer(string? prefabName, string? itemName, string? pieceTableName) { if (!string.IsNullOrEmpty(prefabName) && (string.Equals(prefabName, "Hammer", StringComparison.OrdinalIgnoreCase) || prefabName.IndexOf("Hammer", StringComparison.OrdinalIgnoreCase) >= 0)) { return true; } if (!string.IsNullOrEmpty(itemName) && (string.Equals(itemName, "$item_hammer", StringComparison.OrdinalIgnoreCase) || itemName.IndexOf("hammer", StringComparison.OrdinalIgnoreCase) >= 0)) { return true; } if (!string.IsNullOrEmpty(pieceTableName) && (string.Equals(pieceTableName, "_PieceTableHammer", StringComparison.OrdinalIgnoreCase) || pieceTableName.IndexOf("Hammer", StringComparison.OrdinalIgnoreCase) >= 0)) { return true; } return false; } internal static bool IsEligible(string? prefabName, string? itemName, string? pieceTableName, bool hasBuildPieces, bool unbreakableHammer, bool unbreakableBuildingTools) { if (!hasBuildPieces) { return false; } if (unbreakableBuildingTools) { return true; } if (unbreakableHammer && IsHammer(prefabName, itemName, pieceTableName)) { return true; } return false; } } internal sealed class HammerDurabilityTracker where T : class { private sealed class CanonicalEntry { internal T Canonical { get; } internal CanonicalEntry(T canonical) { Canonical = canonical; } } private sealed class ReferenceComparer : IEqualityComparer { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T? x, T? y) { return x == y; } public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); } } private readonly Dictionary baselines = new Dictionary(ReferenceComparer.Instance); private readonly Dictionary eligibility = new Dictionary(ReferenceComparer.Instance); private readonly List> instances = new List>(); private ConditionalWeakTable entriesByInstance = new ConditionalWeakTable(); internal void TrackCanonical(T canonical, bool baselineUseDurability, bool isEligible) { eligibility[canonical] = isEligible; if (!baselines.ContainsKey(canonical)) { baselines.Add(canonical, baselineUseDurability); } TrackInstance(canonical, canonical); } internal bool TrackInstance(T instance, T canonical) { if (!baselines.ContainsKey(canonical)) { return false; } if (entriesByInstance.TryGetValue(instance, out CanonicalEntry _)) { return true; } entriesByInstance.Add(instance, new CanonicalEntry(canonical)); instances.Add(new WeakReference(instance)); return true; } internal int Apply(bool active, Action setUseDurability) { int num = 0; bool value3 = default(bool); for (int num2 = instances.Count - 1; num2 >= 0; num2--) { CanonicalEntry value; bool value2; if (!instances[num2].TryGetTarget(out var target)) { instances.RemoveAt(num2); } else if (entriesByInstance.TryGetValue(target, out value) && baselines.TryGetValue(value.Canonical, out value2)) { if (active && eligibility.TryGetValue(value.Canonical, out value3) && value3) { setUseDurability(target, arg2: false); num++; } else { setUseDurability(target, value2); } } } return num; } internal void Restore(Action setUseDurability) { Apply(active: false, setUseDurability); baselines.Clear(); eligibility.Clear(); instances.Clear(); entriesByInstance = new ConditionalWeakTable(); } } internal static class HealthHudFeature { private static readonly Color NormalColor = new Color(0.16f, 0.88f, 0.67f, 1f); private static readonly Color WoundedColor = new Color(0.92f, 0.68f, 0.2f, 1f); private static readonly Color CriticalColor = new Color(1f, 0.18f, 0.16f, 1f); private static GameObject? canvasObject; private static GameObject? panelObject; private static RectTransform? panelRect; private static CanvasGroup? panelGroup; private static Image? fillImage; private static TMP_Text? valueText; private static TMP_Text? statusText; private static Image? edgeVignetteImage; private static Sprite? edgeVignetteSprite; private static Image[] edgeImages = Array.Empty(); private static Hud? ownerHud; private static HealthHudSnapshot lastSnapshot; private static string lastLanguage = string.Empty; private static float lastPositionX = float.NaN; private static float lastPositionY = float.NaN; private static float lastScale = float.NaN; internal static bool Install(out string reason) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 ResetSession(); if ((int)SystemInfo.graphicsDeviceType == 4) { reason = "headless graphics device; UI disabled"; return true; } reason = "independent overlay; no Hud or BetterUI patches"; return true; } internal static void Tick() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)SystemInfo.graphicsDeviceType == 4) { return; } Hud instance = Hud.instance; Player localPlayer = Player.m_localPlayer; if (!ZariRulesPlugin.Instance.Rules.HealthIndicatorEnabled.Value || !((Object)(object)instance != (Object)null) || !((Object)(object)localPlayer != (Object)null) || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting() || !instance.IsVisible() || Hud.IsUserHidden() || InventoryGui.IsVisible() || Menu.IsVisible()) { SetVisible(visible: false); return; } if ((Object)(object)canvasObject == (Object)null || (Object)(object)ownerHud != (Object)(object)instance) { DestroyUi(); if (!CreateUi(instance)) { return; } } HealthHudSnapshot healthHudSnapshot = HealthHudRules.Evaluate(((Character)localPlayer).GetHealth(), ((Character)localPlayer).GetMaxHealth()); if (!healthHudSnapshot.IsValid) { SetVisible(visible: false); return; } UpdateLayout(); Localization instance2 = Localization.instance; string text = ((instance2 != null) ? instance2.GetSelectedLanguage() : null) ?? "English"; if (!SameHealth(healthHudSnapshot, lastSnapshot) || !string.Equals(text, lastLanguage, StringComparison.Ordinal)) { UpdateContent(healthHudSnapshot, text); lastSnapshot = healthHudSnapshot; lastLanguage = text; } bool flag = healthHudSnapshot.State == HealthHudState.Critical && ZariRulesPlugin.Instance.Rules.HealthIndicatorPulse.Value; float num = (flag ? (0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * (float)Math.PI * 2.5f)) : 1f); panelGroup.alpha = (flag ? (0.78f + 0.22f * num) : 1f); SetEdges(healthHudSnapshot.State, num); SetVisible(visible: true); } internal static void ResetSession() { DestroyUi(); lastSnapshot = default(HealthHudSnapshot); lastLanguage = string.Empty; } internal static void Uninstall() { ResetSession(); } private static bool SameHealth(HealthHudSnapshot left, HealthHudSnapshot right) { if (left.IsValid == right.IsValid && left.Current == right.Current && left.Maximum == right.Maximum && left.Percent == right.Percent) { return left.State == right.State; } return false; } private static bool CreateUi(Hud hud) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_00b0: 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_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01be: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0286: Unknown result type (might be due to invalid IL or missing references) //IL_0295: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_033e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud.m_healthText == (Object)null || (Object)(object)hud.m_healthText.font == (Object)null) { return false; } ownerHud = hud; canvasObject = new GameObject("ZariRules.HealthHud", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(CanvasGroup) }); Object.DontDestroyOnLoad((Object)(object)canvasObject); Canvas component = canvasObject.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 50; CanvasScaler component2 = canvasObject.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.matchWidthOrHeight = 0.5f; CanvasGroup component3 = canvasObject.GetComponent(); component3.interactable = false; component3.blocksRaycasts = false; panelObject = ((Component)CreateImage("Panel", canvasObject.transform, new Color(0.025f, 0.035f, 0.045f, 0.92f), out Image image)).gameObject; ((Graphic)image).raycastTarget = false; panelRect = panelObject.GetComponent(); panelRect.sizeDelta = new Vector2(280f, 44f); panelGroup = panelObject.AddComponent(); panelGroup.interactable = false; panelGroup.blocksRaycasts = false; Image image2; RectTransform val = CreateImage("Track", (Transform)(object)panelRect, new Color(0.12f, 0.14f, 0.16f, 1f), out image2); val.anchorMin = Vector2.zero; val.anchorMax = new Vector2(1f, 0f); val.offsetMin = new Vector2(10f, 6f); val.offsetMax = new Vector2(-10f, 12f); Image image3; RectTransform obj = CreateImage("Fill", (Transform)(object)val, NormalColor, out image3); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; fillImage = image3; valueText = CreateText("Value", (Transform)(object)panelRect, hud.m_healthText.font, 17f, (TextAlignmentOptions)513); valueText.enableAutoSizing = true; valueText.fontSizeMin = 13f; valueText.fontSizeMax = 17f; SetRect(valueText.rectTransform, new Vector2(10f, 14f), new Vector2(-95f, -3f)); statusText = CreateText("Status", (Transform)(object)panelRect, hud.m_healthText.font, 12f, (TextAlignmentOptions)516); SetRect(statusText.rectTransform, new Vector2(175f, 14f), new Vector2(-10f, -4f)); edgeVignetteSprite = CreateVignetteSprite(); RectTransform obj2 = CreateImage("EdgeVignette", canvasObject.transform, Color.clear, out edgeVignetteImage); obj2.anchorMin = Vector2.zero; obj2.anchorMax = Vector2.one; obj2.offsetMin = Vector2.zero; obj2.offsetMax = Vector2.zero; edgeVignetteImage.sprite = edgeVignetteSprite; ((Graphic)edgeVignetteImage).raycastTarget = false; edgeImages = CreateEdges(canvasObject.transform); UpdateLayout(); return true; } private static void UpdateContent(HealthHudSnapshot snapshot, string language) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0018: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) Color color = (Color)(snapshot.State switch { HealthHudState.Critical => CriticalColor, HealthHudState.Wounded => WoundedColor, _ => NormalColor, }); bool flag = language.StartsWith("Russian", StringComparison.OrdinalIgnoreCase) || language.StartsWith("Рус", StringComparison.OrdinalIgnoreCase); string text = snapshot.State switch { HealthHudState.Critical => flag ? "КРИТИЧНО" : "CRITICAL", HealthHudState.Wounded => flag ? "РАНЕН" : "WOUNDED", _ => flag ? "НОРМА" : "NORMAL", }; valueText.text = $"{Mathf.CeilToInt(snapshot.Current)} / {Mathf.CeilToInt(snapshot.Maximum)} · {snapshot.Percent}%"; ((Graphic)valueText).color = Color.white; statusText.text = text; ((Graphic)statusText).color = color; ((Graphic)fillImage).color = color; ((Graphic)fillImage).rectTransform.anchorMax = new Vector2(snapshot.Fraction, 1f); } private static void UpdateLayout() { //IL_0084: 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_009f: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Clamp01(ZariRulesPlugin.Instance.Rules.HealthIndicatorPositionX.Value); float num2 = Mathf.Clamp01(ZariRulesPlugin.Instance.Rules.HealthIndicatorPositionY.Value); float num3 = Mathf.Clamp(ZariRulesPlugin.Instance.Rules.HealthIndicatorScale.Value, 0.5f, 2f); if (num != lastPositionX || num2 != lastPositionY || num3 != lastScale) { RectTransform? obj = panelRect; RectTransform? obj2 = panelRect; Vector2 val = default(Vector2); ((Vector2)(ref val))..ctor(num, num2); obj2.anchorMax = val; obj.anchorMin = val; panelRect.pivot = new Vector2(0.5f, 0.5f); panelRect.anchoredPosition = Vector2.zero; ((Transform)panelRect).localScale = Vector3.one * num3; lastPositionX = num; lastPositionY = num2; lastScale = num3; } } private static RectTransform CreateImage(string name, Transform parent, Color color, out Image image) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); image = val.GetComponent(); ((Graphic)image).color = color; ((Graphic)image).raycastTarget = false; return val.GetComponent(); } private static TMP_Text CreateText(string name, Transform parent, TMP_FontAsset font, float size, TextAlignmentOptions alignment) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(CanvasRenderer) }); val.SetActive(false); val.transform.SetParent(parent, false); TextMeshProUGUI val2 = val.AddComponent(); ((TMP_Text)val2).font = font; if ((Object)(object)font != (Object)null && (Object)(object)((TMP_Asset)font).material != (Object)null) { ((TMP_Text)val2).fontSharedMaterial = ((TMP_Asset)font).material; } ((TMP_Text)val2).fontSize = size; ((TMP_Text)val2).alignment = alignment; ((Graphic)val2).raycastTarget = false; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0; val.SetActive(true); return (TMP_Text)(object)val2; } private static Image[] CreateEdges(Transform parent) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: 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) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) Image[] array = (Image[])(object)new Image[4]; for (int i = 0; i < array.Length; i++) { RectTransform val = CreateImage("CriticalEdge" + i, parent, new Color(1f, 0.05f, 0.03f, 0f), out array[i]); RectTransform val2 = val; val2.anchorMin = (Vector2)(i switch { 0 => new Vector2(0f, 1f), 1 => Vector2.zero, 2 => Vector2.zero, _ => new Vector2(1f, 0f), }); val2 = val; val2.anchorMax = (Vector2)(i switch { 0 => Vector2.one, 1 => new Vector2(1f, 0f), 2 => new Vector2(0f, 1f), _ => Vector2.one, }); val.pivot = val.anchorMin; val.anchoredPosition = Vector2.zero; val.sizeDelta = ((i < 2) ? new Vector2(0f, 3f) : new Vector2(3f, 0f)); } return array; } private static Sprite CreateVignetteSprite() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false) { name = "ZariRules_EdgeGlow", wrapMode = (TextureWrapMode)1, filterMode = (FilterMode)1 }; Color32[] array = (Color32[])(object)new Color32[4096]; for (int i = 0; i < 64; i++) { float num = (float)i / 63f; float num2 = Mathf.Min(num, 1f - num) * 2f; for (int j = 0; j < 64; j++) { float num3 = (float)j / 63f; float num4 = Mathf.Min(num3, 1f - num3) * 2f; float num5 = 1f - Mathf.Clamp01(Mathf.Min(num4, num2)); byte b = (byte)(Mathf.Clamp01(Mathf.Pow(Mathf.SmoothStep(0f, 1f, num5), 2f)) * 255f); array[i * 64 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b); } } val.SetPixels32(array); val.Apply(); return Sprite.Create(val, new Rect(0f, 0f, 64f, 64f), new Vector2(0.5f, 0.5f)); } private static void SetEdges(HealthHudState state, float pulseFactor) { //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_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0095: 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_00b1: Unknown result type (might be due to invalid IL or missing references) Color clear = default(Color); float num; float num2; switch (state) { case HealthHudState.Critical: ((Color)(ref clear))..ctor(1f, 0.12f, 0.1f, 1f); num = 0.22f + 0.16f * pulseFactor; num2 = 0.8f; break; case HealthHudState.Wounded: ((Color)(ref clear))..ctor(0.95f, 0.65f, 0.15f, 1f); num = 0.04f; num2 = 0.4f; break; default: clear = Color.clear; num = 0f; num2 = 0f; break; } clear.a = num; if ((Object)(object)edgeVignetteImage != (Object)null) { ((Graphic)edgeVignetteImage).color = clear; } Color color = clear; color.a = num * num2; Image[] array = edgeImages; for (int i = 0; i < array.Length; i++) { ((Graphic)array[i]).color = color; } } private static void SetRect(RectTransform rect, Vector2 offsetMin, Vector2 offsetMax) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = offsetMin; rect.offsetMax = offsetMax; } private static void SetVisible(bool visible) { if ((Object)(object)canvasObject != (Object)null && canvasObject.activeSelf != visible) { canvasObject.SetActive(visible); } } private static void DestroyUi() { if ((Object)(object)edgeVignetteSprite != (Object)null) { if ((Object)(object)edgeVignetteSprite.texture != (Object)null) { Object.Destroy((Object)(object)edgeVignetteSprite.texture); } Object.Destroy((Object)(object)edgeVignetteSprite); edgeVignetteSprite = null; } edgeVignetteImage = null; if ((Object)(object)canvasObject != (Object)null) { Object.Destroy((Object)(object)canvasObject); } canvasObject = null; panelObject = null; panelRect = null; panelGroup = null; fillImage = null; valueText = null; statusText = null; edgeImages = Array.Empty(); ownerHud = null; lastSnapshot = default(HealthHudSnapshot); lastLanguage = string.Empty; lastPositionX = float.NaN; lastPositionY = float.NaN; lastScale = float.NaN; } } internal enum HealthHudState { Normal, Wounded, Critical } internal readonly struct HealthHudSnapshot { internal bool IsValid { get; } internal float Current { get; } internal float Maximum { get; } internal float Fraction { get; } internal int Percent { get; } internal HealthHudState State { get; } internal HealthHudSnapshot(bool isValid, float current, float maximum, float fraction, int percent, HealthHudState state) { IsValid = isValid; Current = current; Maximum = maximum; Fraction = fraction; Percent = percent; State = state; } } internal static class HealthHudRules { internal static HealthHudSnapshot Evaluate(float current, float maximum) { if (float.IsNaN(current) || float.IsInfinity(current) || float.IsNaN(maximum) || float.IsInfinity(maximum) || maximum <= 0f) { return default(HealthHudSnapshot); } float num = Math.Min(Math.Max(current, 0f), maximum); float num2 = num / maximum; HealthHudState state = ((num2 <= 0.25f) ? HealthHudState.Critical : ((num2 <= 0.5f) ? HealthHudState.Wounded : HealthHudState.Normal)); return new HealthHudSnapshot(isValid: true, num, maximum, num2, (int)Math.Round(num2 * 100f, MidpointRounding.AwayFromZero), state); } } internal enum MapPinCategory : byte { Boss, Trader, Dungeon } internal readonly struct MapPinData : IEquatable { internal string Prefab { get; } internal float X { get; } internal float Y { get; } internal float Z { get; } internal MapPinCategory Category { get; } internal MapPinData(string prefab, float x, float y, float z, MapPinCategory category) { Prefab = prefab ?? string.Empty; X = x; Y = y; Z = z; Category = category; } public bool Equals(MapPinData other) { if (Prefab == other.Prefab && X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z)) { return Category == other.Category; } return false; } public override bool Equals(object? obj) { if (obj is MapPinData other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((((Prefab.GetHashCode() * 397) ^ X.GetHashCode()) * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode()) * 397) ^ (int)Category; } } internal static class MapPinRules { internal const int MaxLocations = 2048; internal const int MaxPayloadBytes = 262144; internal static bool TryClassify(string prefab, out MapPinCategory category, out string token, out string nameEn, out string nameRu) { category = MapPinCategory.Dungeon; token = string.Empty; nameEn = string.Empty; nameRu = string.Empty; if (string.IsNullOrEmpty(prefab)) { return false; } if (string.Equals(prefab, "Eikthyrnir", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_eikthyr"; nameEn = "Eikthyr"; nameRu = "Эйктюр"; return true; } if (string.Equals(prefab, "GDKing", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_gdking"; nameEn = "The Elder"; nameRu = "Древний"; return true; } if (string.Equals(prefab, "Bonemass", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_bonemass"; nameEn = "Bonemass"; nameRu = "Масса Костей"; return true; } if (string.Equals(prefab, "Dragonqueen", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_dragonqueen"; nameEn = "Moder"; nameRu = "Моудер"; return true; } if (string.Equals(prefab, "GoblinKing", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_goblinking"; nameEn = "Yagluth"; nameRu = "Яглут"; return true; } if (string.Equals(prefab, "Mistlands_DvergrBossEntrance1", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_seekerqueen"; nameEn = "The Queen"; nameRu = "Королева"; return true; } if (string.Equals(prefab, "FaderLocation", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Boss; token = "$enemy_fader"; nameEn = "Fader"; nameRu = "Фейдер"; return true; } if (string.Equals(prefab, "Vendor_BlackForest", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Trader; token = "$npc_haldor"; nameEn = "Haldor"; nameRu = "Хальдор"; return true; } if (string.Equals(prefab, "Hildir_camp", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Trader; token = "$npc_hildir"; nameEn = "Hildir"; nameRu = "Хильдир"; return true; } if (string.Equals(prefab, "BogWitch_Camp", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Trader; token = "$npc_bogwitch"; nameEn = "Bog Witch"; nameRu = "Болотная ведьма"; return true; } if (prefab.StartsWith("ForestCryptHildir", StringComparison.OrdinalIgnoreCase) || prefab.StartsWith("CryptHildir", StringComparison.OrdinalIgnoreCase) || prefab.StartsWith("Hildir_crypt", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_hildir_crypt"; nameEn = "Smoldering Tomb"; nameRu = "Тлеющая гробница"; return true; } if (prefab.StartsWith("CaveHildir", StringComparison.OrdinalIgnoreCase) || prefab.StartsWith("Hildir_cave", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_hildir_cave"; nameEn = "Howling Cavern"; nameRu = "Воющая пещера"; return true; } if (prefab.StartsWith("PlainsFortHildir", StringComparison.OrdinalIgnoreCase) || prefab.StartsWith("Hildir_plainsfortress", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_hildir_tower"; nameEn = "Sealed Tower"; nameRu = "Запечатанная башня"; return true; } if (prefab.StartsWith("SunkenCrypt", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_sunkencrypt"; nameEn = "Sunken Crypt"; nameRu = "Затонувшие склепы"; return true; } if (prefab.StartsWith("Crypt", StringComparison.OrdinalIgnoreCase) || string.Equals(prefab, "ForestCrypt", StringComparison.OrdinalIgnoreCase) || string.Equals(prefab, "HalfBurried_ForestCrypt", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_forestcrypt"; nameEn = "Burial Chambers"; nameRu = "Погребальные склепы"; return true; } if (prefab.StartsWith("TrollCave", StringComparison.OrdinalIgnoreCase) || string.Equals(prefab, "BearCave", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_trollcave"; nameEn = "Troll Cave"; nameRu = "Пещера тролля"; return true; } if (prefab.StartsWith("MountainCave", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_mountaincave"; nameEn = "Frost Cave"; nameRu = "Морозная пещера"; return true; } if (prefab.StartsWith("Mistlands_DvergrTownEntrance", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_infestedmine"; nameEn = "Infested Mine"; nameRu = "Зараженная шахта"; return true; } if (prefab.StartsWith("CharredFortress", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_charredfortress"; nameEn = "Charred Fortress"; nameRu = "Обугленная крепость"; return true; } if (prefab.StartsWith("MorgenHole", StringComparison.OrdinalIgnoreCase)) { category = MapPinCategory.Dungeon; token = "$location_morgenhole"; nameEn = "Putrid Hole"; nameRu = "Зловонная яма"; return true; } return false; } internal static byte[] Encode(IReadOnlyList locations) { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); int num = Math.Min(locations.Count, 2048); binaryWriter.Write(num); for (int i = 0; i < num; i++) { MapPinData mapPinData = locations[i]; binaryWriter.Write((byte)mapPinData.Category); binaryWriter.Write(mapPinData.Prefab ?? string.Empty); binaryWriter.Write(mapPinData.X); binaryWriter.Write(mapPinData.Y); binaryWriter.Write(mapPinData.Z); } binaryWriter.Flush(); return memoryStream.ToArray(); } internal static List Decode(byte[] data) { if (data == null || data.Length < 4 || data.Length > 262144) { throw new InvalidDataException("Invalid map pins payload size."); } using MemoryStream input = new MemoryStream(data, writable: false); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num < 0 || num > 2048) { throw new InvalidDataException("Invalid map pin count."); } List list = new List(num); for (int i = 0; i < num; i++) { byte category = binaryReader.ReadByte(); string prefab = binaryReader.ReadString(); float x = binaryReader.ReadSingle(); float y = binaryReader.ReadSingle(); float z = binaryReader.ReadSingle(); list.Add(new MapPinData(prefab, x, y, z, (MapPinCategory)category)); } return list; } } internal static class MapPinsFeature { internal const string RpcName = "ZariRules_MapPins_V1"; internal const string RequestRpcName = "ZariRules_MapPins_Request_V1"; private const float RequestIntervalSeconds = 5f; private static readonly HashSet Registered = new HashSet(); private static readonly Dictionary NextResponseAt = new Dictionary(); private static readonly List ServerLocations = new List(); private static readonly List ClientLocations = new List(); private static readonly List ActivePins = new List(); private static FieldInfo? locationInstancesField; private static FieldInfo? locationsGeneratedField; private static FieldInfo? hasGeneratedField; private static FieldInfo? pinsField; private static bool serverLocationsCached; private static bool pinsApplied; private static bool locationsReceived; private static float nextRequestAt; internal static bool Install(out string reason) { locationInstancesField = typeof(ZoneSystem).GetField("m_locationInstances", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); locationsGeneratedField = typeof(ZoneSystem).GetField("m_locationsGenerated", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); hasGeneratedField = typeof(Minimap).GetField("m_hasGenerated", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); pinsField = typeof(Minimap).GetField("m_pins", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (locationInstancesField == null || locationsGeneratedField == null || hasGeneratedField == null || pinsField == null) { reason = "missing or changed ZoneSystem/Minimap location contracts"; return false; } reason = "server-authoritative non-persistent map pins for bosses, traders, and dungeons"; return true; } internal static void Tick() { ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null)) { RegisterPeers(instance); if (instance.IsServer()) { EnsureServerLocations(); } else { RequestLocations(instance); } UpdateClientPins(); } } internal static void ReapplyPins() { pinsApplied = false; UpdateClientPins(); } internal static void ResetSession() { foreach (ZRpc item in Registered) { item.Unregister("ZariRules_MapPins_V1"); item.Unregister("ZariRules_MapPins_Request_V1"); } Registered.Clear(); NextResponseAt.Clear(); ServerLocations.Clear(); ClientLocations.Clear(); serverLocationsCached = false; ClearActivePins(); pinsApplied = false; locationsReceived = false; nextRequestAt = 0f; } internal static void Uninstall() { ResetSession(); locationInstancesField = null; locationsGeneratedField = null; hasGeneratedField = null; pinsField = null; } internal static bool TryClassify(string prefab, out MapPinCategory category, out string token, out string nameEn, out string nameRu) { return MapPinRules.TryClassify(prefab, out category, out token, out nameEn, out nameRu); } internal static byte[] Encode(IReadOnlyList locations) { return MapPinRules.Encode(locations); } internal static List Decode(byte[] data) { return MapPinRules.Decode(data); } private static void EnsureServerLocations() { //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: 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_00da: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010b: 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_0117: 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) //IL_0123: Unknown result type (might be due to invalid IL or missing references) if (serverLocationsCached) { return; } ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null || locationsGeneratedField == null || locationInstancesField == null) { return; } object value = locationsGeneratedField.GetValue(instance); if (!(value is bool) || !(bool)value) { return; } ServerLocations.Clear(); object? value2 = locationInstancesField.GetValue(instance); IEnumerable enumerable = value2 as IEnumerable; if (value2 is IDictionary dictionary) { enumerable = dictionary.Values; } if (enumerable != null) { foreach (object item in enumerable) { if (item is LocationInstance val && val.m_location != null) { string text = val.m_location.m_prefabName ?? string.Empty; if (string.IsNullOrEmpty(text)) { text = val.m_location.m_name ?? string.Empty; } if (TryClassify(text, out MapPinCategory category, out string _, out string _, out string _)) { ServerLocations.Add(new MapPinData(text, val.m_position.x, val.m_position.y, val.m_position.z, category)); } } } } ServerLocations.Sort((MapPinData a, MapPinData b) => a.Category.CompareTo(b.Category)); serverLocationsCached = true; Diagnostics.Info("ZR115", $"mapPins=cached count={ServerLocations.Count}"); } private static void RegisterPeers(ZNet net) { HashSet current = new HashSet(); if (net.IsServer()) { foreach (ZNetPeer peer in net.GetPeers()) { if (peer != null && peer.IsReady()) { current.Add(peer.m_rpc); Register(peer.m_rpc); } } } else { ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady()) { current.Add(serverPeer.m_rpc); Register(serverPeer.m_rpc); } } Registered.RemoveWhere(delegate(ZRpc rpc) { if (current.Contains(rpc) && rpc.IsConnected()) { return false; } rpc.Unregister("ZariRules_MapPins_V1"); rpc.Unregister("ZariRules_MapPins_Request_V1"); NextResponseAt.Remove(rpc); if (!net.IsServer()) { locationsReceived = false; nextRequestAt = 0f; ClientLocations.Clear(); ClearActivePins(); pinsApplied = false; } return true; }); } private static void Register(ZRpc rpc) { if (rpc != null && Registered.Add(rpc)) { rpc.Register("ZariRules_MapPins_V1", (Action)OnMapPins); rpc.Register("ZariRules_MapPins_Request_V1", (Action)OnRequest); } } private static void RequestLocations(ZNet net) { //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Expected O, but got Unknown if (!locationsReceived && !(Time.unscaledTime < nextRequestAt) && ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady && ZariRulesPlugin.Instance.Rules.RevealMapPins.Value) { ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady() && Registered.Contains(serverPeer.m_rpc) && serverPeer.m_rpc.IsConnected()) { nextRequestAt = Time.unscaledTime + 5f; serverPeer.m_rpc.Invoke("ZariRules_MapPins_Request_V1", new object[1] { (object)new ZPackage() }); } } } private static void OnRequest(ZRpc rpc, ZPackage package) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null) && instance.IsServer() && package != null && package.Size() == 0 && serverLocationsCached && IsCurrentReadyPeer(instance, rpc) && ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady && ZariRulesPlugin.Instance.Rules.RevealMapPins.Value) { float unscaledTime = Time.unscaledTime; if (!NextResponseAt.TryGetValue(rpc, out var value) || !(unscaledTime < value)) { NextResponseAt[rpc] = unscaledTime + 5f; rpc.Invoke("ZariRules_MapPins_V1", new object[1] { (object)new ZPackage(Encode(ServerLocations)) }); } } } private static bool IsCurrentReadyPeer(ZNet net, ZRpc rpc) { if (!Registered.Contains(rpc) || !rpc.IsConnected()) { return false; } if (!net.IsServer()) { ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady()) { return serverPeer.m_rpc == rpc; } return false; } foreach (ZNetPeer peer in net.GetPeers()) { if (peer != null && peer.IsReady() && peer.m_rpc == rpc) { return true; } } return false; } private static void OnMapPins(ZRpc rpc, ZPackage package) { try { if (package == null || package.Size() > 262144) { return; } ZNet instance = ZNet.instance; if (!((Object)(object)instance == (Object)null) && !instance.IsServer() && IsCurrentReadyPeer(instance, rpc)) { ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer != null && serverPeer.m_rpc == rpc) { List collection = Decode(package.GetArray()); ClientLocations.Clear(); ClientLocations.AddRange(collection); locationsReceived = true; pinsApplied = false; Diagnostics.Info("ZR116", $"mapPins=received count={ClientLocations.Count}"); } } } catch (Exception ex) { ZariRulesPlugin.LogError("ZR917", "feature=MapPins error=" + ex.GetType().Name + ": " + ex.Message); } } private static void UpdateClientPins() { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || hasGeneratedField == null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } object value = hasGeneratedField.GetValue(instance); bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) == 0 || !ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady || !ZariRulesPlugin.Instance.Rules.RevealMapPins.Value) { if (ActivePins.Count > 0) { ClearActivePins(); } pinsApplied = false; } else { if (pinsApplied) { return; } ZNet instance2 = ZNet.instance; IReadOnlyList readOnlyList = ((instance2 != null && instance2.IsServer()) ? ServerLocations : ClientLocations); if (readOnlyList.Count == 0) { return; } ClearActivePins(); Localization instance3 = Localization.instance; string text = ((instance3 != null) ? instance3.GetSelectedLanguage() : null) ?? "English"; bool russian = text.StartsWith("Russian", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Рус", StringComparison.OrdinalIgnoreCase); Vector3 val = default(Vector3); foreach (MapPinData item in readOnlyList) { ((Vector3)(ref val))..ctor(item.X, item.Y, item.Z); if (HasNearbyPin(instance, val, 25f) || !TryClassify(item.Prefab, out MapPinCategory category, out string token, out string nameEn, out string nameRu)) { continue; } string text2 = LocalizePinName(token, nameEn, nameRu, russian); Sprite val2 = null; if (category == MapPinCategory.Trader && instance.m_locationIcons != null) { for (int i = 0; i < instance.m_locationIcons.Count; i++) { LocationSpriteData val3 = instance.m_locationIcons[i]; if (string.Equals(val3.m_name, item.Prefab, StringComparison.OrdinalIgnoreCase)) { val2 = val3.m_icon; break; } } } PinType val4 = (PinType)(category switch { MapPinCategory.Boss => 9, MapPinCategory.Trader => ((Object)(object)val2 != (Object)null) ? 8 : 3, _ => 3, }); PinData val5 = instance.AddPin(val, val4, text2, false, false, 0L, default(PlatformUserID)); if ((Object)(object)val2 != (Object)null) { val5.m_icon = val2; val5.m_doubleSize = true; } ActivePins.Add(val5); } pinsApplied = true; Diagnostics.Info("ZR117", $"mapPins=applied count={ActivePins.Count}"); } } private static string LocalizePinName(string token, string defaultEn, string defaultRu, bool russian) { if (Localization.instance != null && !string.IsNullOrEmpty(token)) { string text = Localization.instance.Localize(token); if (!string.IsNullOrEmpty(text) && text != token && !text.StartsWith("$", StringComparison.Ordinal)) { return text; } } if (!russian) { return defaultEn; } return defaultRu; } private static bool HasNearbyPin(Minimap minimap, Vector3 position, float radius) { //IL_0018: 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_001e: 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) //IL_007f: Unknown result type (might be due to invalid IL or missing references) float num = radius * radius; foreach (PinData activePin in ActivePins) { if (Vector3.SqrMagnitude(activePin.m_pos - position) < num) { return true; } } try { if (pinsField?.GetValue(minimap) is List list) { for (int i = 0; i < list.Count; i++) { PinData val = list[i]; if (val != null && Vector3.SqrMagnitude(val.m_pos - position) < num) { return true; } } } } catch { } return false; } private static void ClearActivePins() { Minimap instance = Minimap.instance; foreach (PinData activePin in ActivePins) { if ((Object)(object)instance != (Object)null) { instance.RemovePin(activePin); } } ActivePins.Clear(); } } internal static class MapRevealFeature { private static FieldInfo? hasGeneratedField; private static FieldInfo? exploredField; private static FieldInfo? fogField; private static Minimap? revealedMap; internal static bool Install(out string reason) { hasGeneratedField = typeof(Minimap).GetField("m_hasGenerated", BindingFlags.Instance | BindingFlags.NonPublic); exploredField = typeof(Minimap).GetField("m_explored", BindingFlags.Instance | BindingFlags.NonPublic); fogField = typeof(Minimap).GetField("m_fogTexture", BindingFlags.Instance | BindingFlags.NonPublic); MethodInfo method = typeof(Minimap).GetMethod("ExploreAll", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null); if (hasGeneratedField?.FieldType != typeof(bool) || method?.ReturnType != typeof(void) || exploredField?.FieldType != typeof(BitArray) || fogField?.FieldType != typeof(Texture2D)) { reason = "missing or changed Minimap.m_hasGenerated/ExploreAll contract"; hasGeneratedField = null; return false; } reason = "bulk fog reveal after profile map generation, preserving shared-map channels"; return true; } internal static void Tick() { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 if (hasGeneratedField == null || !ZariRulesPlugin.Instance.Rules.RevealFullMap.Value || !ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady || (Object)(object)Player.m_localPlayer == (Object)null || (int)SystemInfo.graphicsDeviceType == 4) { return; } FieldInfo fieldInfo = hasGeneratedField; Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null || instance == revealedMap) { return; } object value = fieldInfo.GetValue(instance); if (!(value is bool) || !(bool)value || !(exploredField.GetValue(instance) is BitArray bitArray)) { return; } object? value2 = fogField.GetValue(instance); Texture2D val = (Texture2D)((value2 is Texture2D) ? value2 : null); if (val == null) { return; } Color32[] pixels = val.GetPixels32(); if (pixels.Length != bitArray.Length) { revealedMap = instance; ZariRulesPlugin.LogError("ZR915", "MapReveal: fog and exploration dimensions differ"); return; } for (int i = 0; i < pixels.Length; i++) { pixels[i].r = 0; } val.SetPixels32(pixels); val.Apply(); bitArray.SetAll(value: true); revealedMap = instance; Diagnostics.Info("ZR110", "mapReveal=completed"); } internal static void ResetSession() { revealedMap = null; } internal static void Uninstall() { ResetSession(); hasGeneratedField = null; exploredField = null; fogField = null; } } internal static class PlantGrowthFeature { internal const string HarmonyId = "zari.rules.plant-growth"; private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Expected O, but got Unknown //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Expected O, but got Unknown if (Ready) { reason = "plant growth multiplier active"; return true; } MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Plant), "GetGrowTime", Type.EmptyTypes, (Type[])null); if (methodInfo == null || methodInfo.IsStatic || methodInfo.ReturnType != typeof(float)) { reason = "missing Plant.GetGrowTime contract"; return false; } try { harmony = new Harmony("zari.rules.plant-growth"); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PlantGrowthFeature), "GetGrowTimePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "Plant.GetGrowTime postfix"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Ready = false; } private static void GetGrowTimePostfix(ref float __result) { if (ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady) { double value = ZariRulesPlugin.Instance.Rules.PlantGrowthMultiplier.Value; if (RuleMath.IsSupportedMultiplier(value)) { __result = (float)((double)__result / value); } } } } internal static class ProductionSpeedController { internal const string HarmonyId = "zari.rules.production-speed"; private static readonly HashSet Prefabs = new HashSet(StringComparer.Ordinal) { "smelter", "blastfurnace", "charcoal_kiln" }; private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown if (!CompatibilityProbe.VerifyMethod(typeof(Smelter), "UpdateSmelter", BindingFlags.Instance | BindingFlags.NonPublic, typeof(void), Type.EmptyTypes, "146dc8c182d22edbcc9f2192f3edb621ec03f219bf327c7315ed6f910563a798", out MethodInfo method, out reason) || !CompatibilityProbe.HasOnlyAllowedPatchOwners(method, (IReadOnlyCollection)(object)Array.Empty(), out reason)) { return false; } try { harmony = new Harmony("zari.rules.production-speed"); harmony.Patch((MethodBase)method, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ProductionSpeedController), "Transpiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "Smelter.UpdateSmelter: whole simulation ticks; real-time ZDO remainder; native duration/fuel/queue unchanged"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void ApplyToExisting(string source) { if (Ready) { double value = ZariRulesPlugin.Instance.Rules.ProductionSpeedMultiplier.Value; if (!RuleMath.TryProductionTick(value, out float tick, out string error)) { ZariRulesPlugin.LogError("ZR900", "feature=ProductionSpeed status=unavailable reason=" + error); } else { Diagnostics.Info("ZR310", $"production prefabs=smelter,blastfurnace,charcoal_kiln multiplier={value:R} tickSeconds={tick:R} source={source}"); } } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Ready = false; } internal static bool VerifyPatchOwners(out string reason) { if (CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Smelter), "Awake", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)Array.Empty(), out reason)) { return CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Smelter), "UpdateSmelter", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[1] { "zari.rules.production-speed" }, out reason); } return false; } private static float GetTickSeconds(Smelter smelter) { if (!Ready || !Prefabs.Contains(Utils.GetPrefabName(((Component)smelter).gameObject)) || !RuleMath.TryProductionTick(ZariRulesPlugin.Instance.Rules.ProductionSpeedMultiplier.Value, out float tick, out string _)) { return 1f; } return tick; } private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator) { return ProductionTimingPatch.Rewrite(instructions, generator, AccessTools.Method(typeof(ProductionSpeedController), "GetTickSeconds", (Type[])null, (Type[])null)); } } internal static class ProductionTimingPatch { internal static IEnumerable Rewrite(IEnumerable instructions, ILGenerator generator, MethodInfo tickGetter) { //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Expected O, but got Unknown //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Expected O, but got Unknown //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_028d: Expected O, but got Unknown //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Expected O, but got Unknown //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Expected O, but got Unknown //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Expected O, but got Unknown //IL_02e1: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Expected O, but got Unknown //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Expected O, but got Unknown //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Expected O, but got Unknown //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Expected O, but got Unknown //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_0329: Expected O, but got Unknown //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Expected O, but got Unknown List list = instructions.ToList(); List list2 = new List(); List list3 = new List(); for (int i = 0; i + 1 < list.Count; i++) { if (!(list[i].opcode != OpCodes.Ldc_R4) && object.Equals(list[i].operand, 1f)) { if (list[i + 1].opcode == OpCodes.Sub) { list2.Add(i); } else if (list[i + 1].opcode == OpCodes.Bge || list[i + 1].opcode == OpCodes.Bge_S) { list3.Add(i); } } } if (list2.Count != 1 || list3.Count != 1) { throw new InvalidOperationException($"Expected one accumulator subtraction and comparison, found {list2.Count}/{list3.Count}."); } int num = list2[0] - 1; int num2 = num - 1; int index = list3[0] - 1; if (num2 < 0 || (list[num2].opcode != OpCodes.Br && list[num2].opcode != OpCodes.Br_S) || !(list[num2].operand is Label item) || !list[index].labels.Contains(item) || !CodeInstructionExtensions.IsLdloc(list[num], (LocalBuilder)null) || !CodeInstructionExtensions.IsStloc(list[list2[0] + 2], (LocalBuilder)null) || list[num].opcode != list[index].opcode || !object.Equals(list[num].operand, list[index].operand)) { throw new InvalidOperationException("Unexpected accumulator loop entry/local contract."); } LocalBuilder localBuilder = generator.DeclareLocal(typeof(float)); CodeInstruction val = new CodeInstruction(list[num].opcode, list[num].operand); CodeInstruction val2 = new CodeInstruction(list[list2[0] + 2].opcode, list[list2[0] + 2].operand); list.InsertRange(list3[0] + 2, (IEnumerable)(object)new CodeInstruction[4] { new CodeInstruction(val.opcode, val.operand), new CodeInstruction(OpCodes.Ldloc, (object)localBuilder), new CodeInstruction(OpCodes.Mul, (object)null), new CodeInstruction(val2.opcode, val2.operand) }); CodeInstruction[] array = (CodeInstruction[])(object)new CodeInstruction[7] { new CodeInstruction(OpCodes.Ldarg_0, (object)null), new CodeInstruction(OpCodes.Call, (object)tickGetter), new CodeInstruction(OpCodes.Stloc, (object)localBuilder), new CodeInstruction(val.opcode, val.operand), new CodeInstruction(OpCodes.Ldloc, (object)localBuilder), new CodeInstruction(OpCodes.Div, (object)null), new CodeInstruction(val2.opcode, val2.operand) }; array[0].labels.AddRange(list[num2].labels); list[num2].labels.Clear(); list.InsertRange(num2, array); return list; } } internal static class RecipeNotificationFeature { private sealed class NotificationItem { internal string Author { get; } internal List Recipes { get; } internal NotificationItem(string author, List recipes) { Author = author; Recipes = recipes; } } private const int MaxQueueSize = 6; private const float FadeInDuration = 0.3f; private const float HoldDuration = 4.4f; private const float FadeOutDuration = 0.3f; private const float TotalDuration = 5.0000005f; private static readonly Queue Queue = new Queue(); private static GameObject? canvasObject; private static GameObject? panelObject; private static CanvasGroup? panelGroup; private static TMP_Text? titleText; private static TMP_Text? bodyText; private static Hud? ownerHud; private static NotificationItem? currentItem; private static float currentTimer; internal static bool Install(out string reason) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Invalid comparison between Unknown and I4 ResetSession(); if ((int)SystemInfo.graphicsDeviceType == 4) { reason = "headless graphics device; UI disabled"; return true; } reason = "independent HUD notification banner below minimap with localization and queue"; return true; } internal static void QueueNotification(string author, IReadOnlyList recipes) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Invalid comparison between Unknown and I4 if (recipes != null && recipes.Count != 0 && (int)SystemInfo.graphicsDeviceType != 4) { if (Queue.Count >= 6) { Queue.Dequeue(); } Queue.Enqueue(new NotificationItem(author, recipes.Distinct(StringComparer.Ordinal).ToList())); } } internal static void Tick() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)SystemInfo.graphicsDeviceType == 4) { return; } Hud instance = Hud.instance; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)instance != (Object)null) || !((Object)(object)localPlayer != (Object)null) || ((Character)localPlayer).IsDead() || ((Character)localPlayer).IsTeleporting() || !instance.IsVisible() || Hud.IsUserHidden() || InventoryGui.IsVisible() || Menu.IsVisible()) { SetVisible(visible: false); return; } if ((Object)(object)canvasObject == (Object)null || (Object)(object)ownerHud != (Object)(object)instance) { DestroyUi(); if (!CreateUi(instance)) { return; } } if (currentItem == null) { if (Queue.Count == 0) { SetVisible(visible: false); return; } currentItem = Queue.Dequeue(); currentTimer = 0f; DisplayCurrent(currentItem); Diagnostics.Info("ZR124", $"recipeNotification=shown count={currentItem.Recipes.Count}"); } currentTimer += Time.unscaledDeltaTime; float num; if (currentTimer < 0.3f) { num = Mathf.Clamp01(currentTimer / 0.3f); } else if (currentTimer < 4.7000003f) { num = 1f; } else if (currentTimer < 5.0000005f) { num = Mathf.Clamp01((5.0000005f - currentTimer) / 0.3f); } else { currentItem = null; num = 0f; } if ((Object)(object)panelGroup != (Object)null) { panelGroup.alpha = num; } SetVisible(num > 0.01f); } internal static void ResetSession() { Queue.Clear(); currentItem = null; currentTimer = 0f; DestroyUi(); } internal static void Uninstall() { ResetSession(); } private static bool CreateUi(Hud hud) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Expected O, but got Unknown //IL_00b0: 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_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0132: 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_015c: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01c9: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02ed: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_031a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)hud.m_healthText == (Object)null || (Object)(object)hud.m_healthText.font == (Object)null) { return false; } ownerHud = hud; canvasObject = new GameObject("ZariRules.RecipeNotifications", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(CanvasGroup) }); Object.DontDestroyOnLoad((Object)(object)canvasObject); Canvas component = canvasObject.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 48; CanvasScaler component2 = canvasObject.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1920f, 1080f); component2.matchWidthOrHeight = 0.5f; CanvasGroup component3 = canvasObject.GetComponent(); component3.interactable = false; component3.blocksRaycasts = false; panelObject = ((Component)CreateImage("Panel", canvasObject.transform, new Color(0.025f, 0.035f, 0.045f, 0.94f), out Image image)).gameObject; ((Graphic)image).raycastTarget = false; RectTransform component4 = panelObject.GetComponent(); component4.anchorMin = Vector2.one; component4.anchorMax = Vector2.one; component4.pivot = new Vector2(1f, 1f); component4.anchoredPosition = new Vector2(-20f, -235f); component4.sizeDelta = new Vector2(380f, 62f); panelGroup = panelObject.AddComponent(); panelGroup.interactable = false; panelGroup.blocksRaycasts = false; panelGroup.alpha = 0f; Image image2; RectTransform obj = CreateImage("Accent", (Transform)(object)component4, new Color(0.16f, 0.88f, 0.67f, 1f), out image2); ((Graphic)image2).raycastTarget = false; obj.anchorMin = new Vector2(0f, 0f); obj.anchorMax = new Vector2(0f, 1f); obj.pivot = new Vector2(0f, 0.5f); obj.anchoredPosition = Vector2.zero; obj.sizeDelta = new Vector2(3f, 0f); titleText = CreateText("Title", (Transform)(object)component4, hud.m_healthText.font, 13f, (TextAlignmentOptions)257); ((Graphic)titleText).color = new Color(0.2f, 0.9f, 0.7f, 1f); SetRect(titleText.rectTransform, new Vector2(12f, 32f), new Vector2(-10f, -6f)); bodyText = CreateText("Body", (Transform)(object)component4, hud.m_healthText.font, 12f, (TextAlignmentOptions)257); ((Graphic)bodyText).color = new Color(0.9f, 0.92f, 0.94f, 1f); SetRect(bodyText.rectTransform, new Vector2(12f, 6f), new Vector2(-10f, -28f)); return true; } private static void DisplayCurrent(NotificationItem item) { if (!((Object)(object)titleText == (Object)null) && !((Object)(object)bodyText == (Object)null)) { Localization instance = Localization.instance; string text = ((instance != null) ? instance.GetSelectedLanguage() : null) ?? "English"; bool flag = text.StartsWith("Russian", StringComparison.OrdinalIgnoreCase) || text.StartsWith("Рус", StringComparison.OrdinalIgnoreCase); int count = item.Recipes.Count; List list = (from s in item.Recipes.Select(ResolveRecipeDisplayName) where !string.IsNullOrEmpty(s) select s).ToList(); if (list.Count == 0) { list.AddRange(item.Recipes); } string text2; if (list.Count <= 3) { text2 = string.Join(", ", list); } else { int num = list.Count - 3; string text3 = (flag ? $"и ещё {num}" : $"and {num} more"); text2 = string.Join(", ", list.Take(3)) + ", " + text3; } if (!string.IsNullOrEmpty(item.Author)) { titleText.text = (flag ? $"{item.Author} поделился рецептами ({count}):" : $"{item.Author} shared recipes ({count}):"); } else { titleText.text = (flag ? $"Получены общие рецепты ({count}):" : $"Received shared recipes ({count}):"); } bodyText.text = text2; } } internal static string ResolveRecipeDisplayName(string recipeName) { if (string.IsNullOrEmpty(recipeName)) { return string.Empty; } try { string text = LocalizeToken(recipeName); if (!string.IsNullOrEmpty(text)) { return text; } if (!recipeName.StartsWith("$", StringComparison.Ordinal)) { string text2 = LocalizeToken("$" + recipeName); if (!string.IsNullOrEmpty(text2)) { return text2; } } string cleanName = (recipeName.StartsWith("$", StringComparison.Ordinal) ? recipeName.Substring(1) : recipeName); if ((Object)(object)ObjectDB.instance != (Object)null) { Recipe val = ObjectDB.instance.m_recipes.Find((Recipe r) => (Object)(object)r != (Object)null && (((Object)r).name == cleanName || ((Object)r).name == recipeName || ((Object)(object)r.m_item != (Object)null && (((Object)r.m_item).name == cleanName || ((Object)r.m_item).name == recipeName)))); if (val?.m_item?.m_itemData?.m_shared != null) { string text3 = LocalizeToken(val.m_item.m_itemData.m_shared.m_name); if (!string.IsNullOrEmpty(text3)) { return text3; } } GameObject val2 = ObjectDB.instance.GetItemPrefab(cleanName) ?? ObjectDB.instance.GetItemPrefab(recipeName); ItemDrop val3 = (((Object)(object)val2 != (Object)null) ? val2.GetComponent() : null); if (val3?.m_itemData?.m_shared != null) { string text4 = LocalizeToken(val3.m_itemData.m_shared.m_name); if (!string.IsNullOrEmpty(text4)) { return text4; } } } if ((Object)(object)ZNetScene.instance != (Object)null) { GameObject val4 = ZNetScene.instance.GetPrefab(cleanName) ?? ZNetScene.instance.GetPrefab(recipeName); Piece val5 = (((Object)(object)val4 != (Object)null) ? val4.GetComponent() : null); if ((Object)(object)val5 != (Object)null && !string.IsNullOrEmpty(val5.m_name)) { string text5 = LocalizeToken(val5.m_name); if (!string.IsNullOrEmpty(text5)) { return text5; } } } } catch { } return (recipeName.StartsWith("$", StringComparison.Ordinal) ? recipeName.Substring(1) : recipeName).Replace('_', ' '); } private static string LocalizeToken(string token) { if (string.IsNullOrEmpty(token)) { return string.Empty; } if (Localization.instance != null) { string text = Localization.instance.Localize(token); if (!string.IsNullOrEmpty(text) && text != token && !text.StartsWith("$", StringComparison.Ordinal)) { return text; } } return string.Empty; } private static RectTransform CreateImage(string name, Transform parent, Color color, out Image image) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[3] { typeof(RectTransform), typeof(CanvasRenderer), typeof(Image) }); val.transform.SetParent(parent, false); image = val.GetComponent(); ((Graphic)image).color = color; ((Graphic)image).raycastTarget = false; return val.GetComponent(); } private static TMP_Text CreateText(string name, Transform parent, TMP_FontAsset font, float size, TextAlignmentOptions alignment) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(CanvasRenderer) }); val.SetActive(false); val.transform.SetParent(parent, false); TextMeshProUGUI val2 = val.AddComponent(); ((TMP_Text)val2).font = font; if ((Object)(object)font != (Object)null && (Object)(object)((TMP_Asset)font).material != (Object)null) { ((TMP_Text)val2).fontSharedMaterial = ((TMP_Asset)font).material; } ((TMP_Text)val2).fontSize = size; ((TMP_Text)val2).alignment = alignment; ((Graphic)val2).raycastTarget = false; ((TMP_Text)val2).textWrappingMode = (TextWrappingModes)0; ((TMP_Text)val2).overflowMode = (TextOverflowModes)1; val.SetActive(true); return (TMP_Text)(object)val2; } private static void SetRect(RectTransform rect, Vector2 offsetMin, Vector2 offsetMax) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) rect.anchorMin = Vector2.zero; rect.anchorMax = Vector2.one; rect.offsetMin = offsetMin; rect.offsetMax = offsetMax; } private static void SetVisible(bool visible) { if ((Object)(object)canvasObject != (Object)null && canvasObject.activeSelf != visible) { canvasObject.SetActive(visible); } } private static void DestroyUi() { if ((Object)(object)canvasObject != (Object)null) { Object.Destroy((Object)(object)canvasObject); } canvasObject = null; panelObject = null; panelGroup = null; titleText = null; bodyText = null; ownerHud = null; } } internal static class ResourceRateController { private const string HarmonyId = "zari.rules.resource-rate"; private static Harmony? harmony; private static bool applying; private static bool worldReady; private static int loadingKeys; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Expected O, but got Unknown //IL_014f: Expected O, but got Unknown //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Expected O, but got Unknown //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Expected O, but got Unknown if (!CompatibilityProbe.VerifyMethod(typeof(ZoneSystem), "SetStartingGlobalKeys", BindingFlags.Instance | BindingFlags.Public, typeof(void), new Type[1] { typeof(bool) }, "0a477dbd4078ac2966eba61c40b1382a8b572b01e39bafeef72681da3a3522fa", out MethodInfo method, out reason) || !CompatibilityProbe.VerifyMethod(typeof(ZoneSystem), "UpdateWorldRates", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "3acb4e2da64e100830c9912ba3dceb86211cd08eccd7b4739fe1e2cf24e95c68", out MethodInfo method2, out reason) || !CompatibilityProbe.VerifyMethod(typeof(ZoneSystem), "RPC_GlobalKeys", BindingFlags.Instance | BindingFlags.NonPublic, typeof(void), new Type[2] { typeof(long), typeof(List) }, "1febeff1fc10e2e78fe1fa15b5ef3ac6dea400e0795c7e26969a1ebd25d64bfb", out MethodInfo method3, out reason) || !CompatibilityProbe.VerifyMethod(typeof(PickableItem), "GetStackSize", BindingFlags.Instance | BindingFlags.NonPublic, typeof(int), Type.EmptyTypes, "0d778f0dea28e91896454793f46dc19c0a456883b218ca2113747744a0dd6ba6", out MethodInfo method4, out reason)) { return false; } try { harmony = new Harmony("zari.rules.resource-rate"); MethodInfo[] array = new MethodInfo[2] { method, method3 }; foreach (MethodInfo methodInfo in array) { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ResourceRateController), "KeysLoadingPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(ResourceRateController), "KeysLoadedFinalizer", (Type[])null), (HarmonyMethod)null); } harmony.Patch((MethodBase)method2, (HarmonyMethod)null, new HarmonyMethod(typeof(ResourceRateController), "RatesUpdatedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)method4, (HarmonyMethod)null, new HarmonyMethod(typeof(ResourceRateController), "FixedPickupPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "native GlobalKeys.ResourceRate; fixed PickableItem stacks scaled once; native exclusions preserved"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Reconcile(string source) { if (!Ready || !worldReady || loadingKeys != 0 || applying || (Object)(object)ZoneSystem.instance == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return; } if (!RuleMath.TryResourcePercent(ZariRulesPlugin.Instance.Rules.ResourceRateMultiplier.Value, out int percent, out string error)) { ZariRulesPlugin.LogError("ZR900", "feature=ResourceRate status=unavailable reason=" + error); return; } float num = 100f; bool globalKey = ZoneSystem.instance.GetGlobalKey((GlobalKeys)4, ref num); if (ZNet.instance.IsServer() && (!globalKey || float.IsNaN(num) || float.IsInfinity(num) || Math.Abs(num - (float)percent) > 0.001f)) { try { applying = true; ZoneSystem.instance.SetGlobalKey((GlobalKeys)4, (float)percent); Diagnostics.Info("ZR300", $"serverResourceRate keyPercent={percent} desired={(double)percent / 100.0:R} source={source}"); } finally { applying = false; } } VerifyReadback(); } internal static void ResetSession() { applying = false; worldReady = false; loadingKeys = 0; } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Ready = false; ResetSession(); } private static void KeysLoadingPrefix(out bool __state) { __state = true; loadingKeys++; worldReady = false; } private static Exception? KeysLoadedFinalizer(Exception? __exception, ref bool __state) { if (!__state) { return __exception; } __state = false; loadingKeys--; worldReady = __exception == null && loadingKeys == 0; if (worldReady) { try { Reconcile("keys-loaded"); } catch (Exception ex) { worldReady = false; ZariRulesPlugin.LogError("ZR900", "feature=ResourceRate status=unavailable reason=" + ex.GetType().Name + ": " + ex.Message); } } return __exception; } private static void RatesUpdatedPostfix() { if (worldReady && !applying) { Reconcile("native-key-update"); } } private static void FixedPickupPostfix(PickableItem __instance, ref int __result) { if (Ready && __instance.m_randomItemPrefabs.Length == 0 && (Object)(object)__instance.m_itemPrefab != (Object)null && (Object)(object)Game.instance != (Object)null) { __result = Game.instance.ScaleDrops(__instance.m_itemPrefab.m_itemData, __result); } } private static void VerifyReadback() { if (!((Object)(object)ZoneSystem.instance == (Object)null) && RuleMath.TryResourcePercent(ZariRulesPlugin.Instance.Rules.ResourceRateMultiplier.Value, out int percent, out string _)) { float num = default(float); bool globalKey = ZoneSystem.instance.GetGlobalKey((GlobalKeys)4, ref num); if (globalKey && Math.Abs(num - (float)percent) < 0.001f && Math.Abs(Game.m_resourceRate - (float)percent / 100f) < 0.0001f) { Diagnostics.Info("ZR301", $"resourceRate received={Game.m_resourceRate:R} expected={(float)percent / 100f:R} ready=true"); } else if ((Object)(object)ZNet.instance != (Object)null && !ZNet.instance.IsServer()) { ZariRulesPlugin.LogError("ZR900", string.Format("feature=ResourceRate status=unavailable reason=readback key={0} derived={1:R} expected={2}", globalKey ? num.ToString("R") : "absent", Game.m_resourceRate, percent)); } } } } internal static class RuleMath { internal const double MinimumMultiplier = 0.01; internal const double MaximumMultiplier = 10.0; internal static bool TryResourcePercent(double multiplier, out int percent, out string error) { percent = 0; if (!IsSupportedMultiplier(multiplier)) { error = $"значение должно быть конечным числом от {0.01} до {10.0}"; return false; } double num = multiplier * 100.0; double num2 = Math.Round(num, MidpointRounding.AwayFromZero); if (Math.Abs(num - num2) > 1E-09 || num2 > 2147483647.0) { error = "значение должно задаваться с точностью не более двух знаков после запятой"; return false; } percent = checked((int)num2); error = string.Empty; return true; } internal static bool TryProductionTick(double multiplier, out float tick, out string error) { tick = 1f; if (double.IsNaN(multiplier) || double.IsInfinity(multiplier) || multiplier < 0.01 || multiplier > 20.0) { error = $"множитель должен быть конечным числом от {0.01} до 20"; return false; } tick = (float)(1.0 / multiplier); error = string.Empty; return true; } internal static float StaminaCost(float amount, float multiplier) { if (!(amount > 0f) || float.IsInfinity(amount)) { return amount; } return amount * multiplier; } internal static bool IsSupportedMultiplier(double value) { if (!double.IsNaN(value) && !double.IsInfinity(value) && value >= 0.01) { return value <= 10.0; } return false; } } internal sealed class RulesConfig { private readonly ZariRulesPlugin plugin; private bool initialSynchronizationReceived; internal ConfigEntry KeepInventoryOnDeath { get; } internal ConfigEntry KeepSkillsOnDeath { get; } internal ConfigEntry ResourceRateMultiplier { get; } internal ConfigEntry ProductionSpeedMultiplier { get; } internal ConfigEntry PlantGrowthMultiplier { get; } internal ConfigEntry StaminaUsageMultiplier { get; } internal ConfigEntry RevealFullMap { get; } internal ConfigEntry RevealMapPins { get; } internal ConfigEntry ShareRecipes { get; } internal ConfigEntry StackSizeMultiplier { get; } internal ConfigEntry UnbreakableHammer { get; } internal ConfigEntry UnbreakableBuildingTools { get; } internal ConfigEntry HealthIndicatorEnabled { get; } internal ConfigEntry HealthIndicatorPositionX { get; } internal ConfigEntry HealthIndicatorPositionY { get; } internal ConfigEntry HealthIndicatorScale { get; } internal ConfigEntry HealthIndicatorPulse { get; } internal bool IsServerConfigurationReady { get { if ((Object)(object)ZNet.instance != (Object)null) { if (!ZNet.instance.IsServer()) { return initialSynchronizationReceived; } return true; } return false; } } internal ConfigEntry DebugLogging { get; } internal RulesConfig(ZariRulesPlugin plugin) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Expected O, but got Unknown //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Expected O, but got Unknown //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Expected O, but got Unknown //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Expected O, but got Unknown //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Expected O, but got Unknown //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Expected O, but got Unknown //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Expected O, but got Unknown //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0346: Expected O, but got Unknown //IL_037a: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Expected O, but got Unknown this.plugin = plugin; ConfigurationManagerAttributes val = new ConfigurationManagerAttributes { IsAdminOnly = true }; KeepInventoryOnDeath = ((BaseUnityPlugin)plugin).Config.Bind("Death", "KeepInventoryOnDeath", true, new ConfigDescription("Сохранять все предметы при смерти.", (AcceptableValueBase)null, new object[1] { val })); KeepSkillsOnDeath = ((BaseUnityPlugin)plugin).Config.Bind("Death", "KeepSkillsOnDeath", true, new ConfigDescription("Сохранять уровни навыков и незавершённый прогресс при смерти.", (AcceptableValueBase)null, new object[1] { val })); ResourceRateMultiplier = ((BaseUnityPlugin)plugin).Config.Bind("Resources", "ResourceRateMultiplier", 4.0, new ConfigDescription("Штатный множитель Resource Rate (0.01–10.00, шаг 0.01).", (AcceptableValueBase)null, new object[1] { val })); ProductionSpeedMultiplier = ((BaseUnityPlugin)plugin).Config.Bind("Production", "ProductionSpeedMultiplier", 20.0, new ConfigDescription("Множитель скорости smelter, blastfurnace и charcoal_kiln (0.01–20.00).", (AcceptableValueBase)null, new object[1] { val })); PlantGrowthMultiplier = ((BaseUnityPlugin)plugin).Config.Bind("Production", "PlantGrowthMultiplier", 10.0, new ConfigDescription("Множитель скорости роста растений (0.01–10.00).", (AcceptableValueBase)(object)new AcceptableValueRange(0.01, 10.0), new object[1] { val })); StaminaUsageMultiplier = ((BaseUnityPlugin)plugin).Config.Bind("Player", "StaminaUsageMultiplier", 0.5f, new ConfigDescription("Расход выносливости: 0.5 — вдвое меньше, 1 — обычный.", (AcceptableValueBase)(object)new AcceptableValueRange(0.1f, 1f), new object[1] { val })); RevealFullMap = ((BaseUnityPlugin)plugin).Config.Bind("World", "RevealFullMap", true, new ConfigDescription("Открывать всю карту после загрузки персонажа.", (AcceptableValueBase)null, new object[1] { val })); RevealMapPins = ((BaseUnityPlugin)plugin).Config.Bind("World", "RevealMapPins", true, new ConfigDescription("Показывать реальные позиции боссов, торговцев и подземелий на открытой карте.", (AcceptableValueBase)null, new object[1] { val })); ShareRecipes = ((BaseUnityPlugin)plugin).Config.Bind("Crafting", "ShareRecipes", true, new ConfigDescription("Объединять уже известные игрокам рецепты в пределах мира.", (AcceptableValueBase)null, new object[1] { val })); StackSizeMultiplier = ((BaseUnityPlugin)plugin).Config.Bind("Items", "StackSizeMultiplier", 20, new ConfigDescription("Множитель максимального размера стопок (1–20).", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), new object[1] { val })); UnbreakableHammer = ((BaseUnityPlugin)plugin).Config.Bind("Building", "UnbreakableHammer", true, new ConfigDescription("Молот для строительства не теряет прочность и не ломается.", (AcceptableValueBase)null, new object[1] { val })); UnbreakableBuildingTools = ((BaseUnityPlugin)plugin).Config.Bind("Building", "UnbreakableBuildingTools", false, new ConfigDescription("Все строительные инструменты (молот, мотыга, культиватор) не теряют прочность.", (AcceptableValueBase)null, new object[1] { val })); HealthIndicatorEnabled = ((BaseUnityPlugin)plugin).Config.Bind("Health Indicator", "Enabled", true, "Показывать локальный индикатор здоровья."); HealthIndicatorPositionX = ((BaseUnityPlugin)plugin).Config.Bind("Health Indicator", "PositionX", 0.5f, new ConfigDescription("Положение индикатора по горизонтали (0–1).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); HealthIndicatorPositionY = ((BaseUnityPlugin)plugin).Config.Bind("Health Indicator", "PositionY", 0.1f, new ConfigDescription("Положение индикатора по вертикали (0–1).", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); HealthIndicatorScale = ((BaseUnityPlugin)plugin).Config.Bind("Health Indicator", "Scale", 1f, new ConfigDescription("Масштаб индикатора (0.5–2).", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2f), Array.Empty())); HealthIndicatorPulse = ((BaseUnityPlugin)plugin).Config.Bind("Health Indicator", "Pulse", true, "Пульсация индикатора при критическом здоровье."); DebugLogging = ((BaseUnityPlugin)plugin).Config.Bind("General", "DebugLogging", false, "Локальные диагностические snapshots без изменения игрового состояния."); KeepInventoryOnDeath.SettingChanged += Changed; KeepSkillsOnDeath.SettingChanged += Changed; ResourceRateMultiplier.SettingChanged += Changed; ProductionSpeedMultiplier.SettingChanged += Changed; PlantGrowthMultiplier.SettingChanged += Changed; StaminaUsageMultiplier.SettingChanged += Changed; RevealFullMap.SettingChanged += Changed; RevealMapPins.SettingChanged += Changed; ShareRecipes.SettingChanged += Changed; StackSizeMultiplier.SettingChanged += Changed; UnbreakableHammer.SettingChanged += Changed; UnbreakableBuildingTools.SettingChanged += Changed; SynchronizationManager.OnConfigurationSynchronized += Synchronized; } internal void Dispose() { KeepInventoryOnDeath.SettingChanged -= Changed; KeepSkillsOnDeath.SettingChanged -= Changed; ResourceRateMultiplier.SettingChanged -= Changed; ProductionSpeedMultiplier.SettingChanged -= Changed; PlantGrowthMultiplier.SettingChanged -= Changed; StaminaUsageMultiplier.SettingChanged -= Changed; RevealFullMap.SettingChanged -= Changed; RevealMapPins.SettingChanged -= Changed; ShareRecipes.SettingChanged -= Changed; StackSizeMultiplier.SettingChanged -= Changed; UnbreakableHammer.SettingChanged -= Changed; UnbreakableBuildingTools.SettingChanged -= Changed; SynchronizationManager.OnConfigurationSynchronized -= Synchronized; } private void Changed(object sender, EventArgs args) { plugin.ApplyEffectiveRules("config-change"); } internal void ResetSession() { initialSynchronizationReceived = false; } private void Synchronized(object sender, ConfigurationSynchronizationEventArgs args) { if (args.UpdatedPluginGUIDs.Contains("zari.rules")) { if (args.InitialSynchronization) { initialSynchronizationReceived = true; } plugin.ApplyEffectiveRules(args.InitialSynchronization ? "initial-sync" : "server-config-update"); } } } internal static class SessionLifecycleFeature { private const string HarmonyId = "zari.rules.session"; private static Harmony? harmony; internal static bool Install(out string reason) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (!CompatibilityProbe.VerifyMethod(typeof(ZNet), "OnDestroy", BindingFlags.Instance | BindingFlags.NonPublic, typeof(void), Type.EmptyTypes, "bf1b5c65eee0066748cbd0b77e326d2e5e3cd2786f3fa80c2d063dccd4522b7a", out MethodInfo method, out reason)) { return false; } try { harmony = new Harmony("zari.rules.session"); Harmony? obj = harmony; MethodInfo methodInfo = method; HarmonyMethod val = new HarmonyMethod(typeof(SessionLifecycleFeature), "Prefix", (Type[])null); val.priority = 800; val.before = new string[1] { "com.jotunn.jotunn" }; obj.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); reason = "ZNet.OnDestroy reset before Jotunn restores local config"; return true; } catch (Exception ex) { Harmony? obj2 = harmony; if (obj2 != null) { obj2.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } } private static void Prefix() { ZariRulesPlugin.Instance.Rules.ResetSession(); DeathContext.Reset(); ResourceRateController.ResetSession(); StackSizeFeature.ResetSession(); MapRevealFeature.ResetSession(); MapPinsFeature.ResetSession(); SharedRecipesFeature.ResetSession(); RecipeNotificationFeature.ResetSession(); HealthHudFeature.ResetSession(); HammerDurabilityFeature.ResetSession(); Diagnostics.ResetSession(); } } internal static class SharedRecipeCodec { internal const int FormatVersionV1 = 1; internal const int FormatVersionV2 = 2; internal const int MaxRecipes = 8192; internal const int MaxRecipeBytes = 512; internal const int MaxAuthorBytes = 128; internal const int MaxPayloadBytes = 4194304; private const int MagicV1 = 827478618; private const int MagicV2 = 844255834; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); internal static byte[] Encode(IEnumerable recipes) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (string recipe in recipes) { if (!string.IsNullOrEmpty(recipe)) { dictionary[recipe] = string.Empty; } } return EncodeWithAuthors(dictionary); } internal static byte[] EncodeWithAuthors(IReadOnlyDictionary recipesWithAuthors) { SortedDictionary sortedDictionary = new SortedDictionary(StringComparer.Ordinal); foreach (KeyValuePair recipesWithAuthor in recipesWithAuthors) { if (!string.IsNullOrEmpty(recipesWithAuthor.Key)) { if (Utf8.GetByteCount(recipesWithAuthor.Key) > 512 || (recipesWithAuthor.Value != null && Utf8.GetByteCount(recipesWithAuthor.Value) > 128) || (sortedDictionary.Count >= 8192 && !sortedDictionary.ContainsKey(recipesWithAuthor.Key))) { throw new InvalidDataException("Shared recipe limits exceeded."); } sortedDictionary[recipesWithAuthor.Key] = recipesWithAuthor.Value ?? string.Empty; } } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Utf8, leaveOpen: true); binaryWriter.Write(844255834); binaryWriter.Write(2); long position = memoryStream.Position; binaryWriter.Write(0); int num = 0; foreach (KeyValuePair item in sortedDictionary) { byte[] bytes = Utf8.GetBytes(item.Key); byte[] bytes2 = Utf8.GetBytes(item.Value); num++; binaryWriter.Write(bytes.Length); binaryWriter.Write(bytes); binaryWriter.Write(bytes2.Length); binaryWriter.Write(bytes2); } binaryWriter.Flush(); memoryStream.Position = position; binaryWriter.Write(num); binaryWriter.Flush(); if (memoryStream.Length > 4194304) { throw new InvalidDataException("Shared recipe payload is too large."); } return memoryStream.ToArray(); } internal static Dictionary DecodeWithAuthors(byte[] data) { if (data == null || data.Length < 12 || data.Length > 4194304) { throw new InvalidDataException("Invalid shared recipe payload size."); } using MemoryStream memoryStream = new MemoryStream(data, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Utf8, leaveOpen: false); int num = binaryReader.ReadInt32(); int num2 = binaryReader.ReadInt32(); if (num == 827478618 && num2 == 1) { int num3 = binaryReader.ReadInt32(); if (num3 < 0 || num3 > 8192) { throw new InvalidDataException("Invalid shared recipe count."); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); for (int i = 0; i < num3; i++) { if (memoryStream.Length - memoryStream.Position < 4) { throw new InvalidDataException("Truncated shared recipe payload."); } int num4 = binaryReader.ReadInt32(); if (num4 <= 0 || num4 > 512 || memoryStream.Length - memoryStream.Position < num4) { throw new InvalidDataException("Invalid shared recipe name length."); } string key = Utf8.GetString(binaryReader.ReadBytes(num4)); dictionary[key] = string.Empty; } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing shared recipe data."); } return dictionary; } if (num == 844255834 && num2 == 2) { int num5 = binaryReader.ReadInt32(); if (num5 < 0 || num5 > 8192) { throw new InvalidDataException("Invalid shared recipe count."); } Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); for (int j = 0; j < num5; j++) { if (memoryStream.Length - memoryStream.Position < 4) { throw new InvalidDataException("Truncated shared recipe payload."); } int num6 = binaryReader.ReadInt32(); if (num6 <= 0 || num6 > 512 || memoryStream.Length - memoryStream.Position < num6) { throw new InvalidDataException("Invalid shared recipe name length."); } string key2 = Utf8.GetString(binaryReader.ReadBytes(num6)); if (memoryStream.Length - memoryStream.Position < 4) { throw new InvalidDataException("Truncated shared recipe payload."); } int num7 = binaryReader.ReadInt32(); if (num7 < 0 || num7 > 128 || memoryStream.Length - memoryStream.Position < num7) { throw new InvalidDataException("Invalid shared recipe author length."); } string value = ((num7 > 0) ? Utf8.GetString(binaryReader.ReadBytes(num7)) : string.Empty); dictionary2[key2] = value; } if (memoryStream.Position != memoryStream.Length) { throw new InvalidDataException("Trailing shared recipe data."); } return dictionary2; } throw new InvalidDataException("Unsupported shared recipe file format."); } internal static HashSet Decode(byte[] data) { return new HashSet(DecodeWithAuthors(data).Keys, StringComparer.Ordinal); } } internal static class SharedRecipesFeature { private const string RpcName = "ZariRules_SharedRecipes_V1"; private const double RequestIntervalSeconds = 5.0; private const double ReconcileIntervalSeconds = 30.0; private static readonly HashSet Registered = new HashSet(); private static readonly HashSet AcknowledgedPeers = new HashSet(); private static readonly Dictionary Union = new Dictionary(StringComparer.Ordinal); private static readonly SharedRecipeClientState ClientState = new SharedRecipeClientState(); private static FieldInfo? knownRecipesField; private static MethodInfo? updatePiecesMethod; private static MethodInfo? updateCraftingMethod; private static Player? trackedPlayer; private static long worldUid; private static bool worldLoaded; private static bool persistenceBlocked; private static double nextRequestAt; internal static bool Install(out string reason) { knownRecipesField = typeof(Player).GetField("m_knownRecipes", BindingFlags.Instance | BindingFlags.NonPublic); updatePiecesMethod = typeof(Player).GetMethod("UpdateAvailablePiecesList", BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null); updateCraftingMethod = typeof(InventoryGui).GetMethod("UpdateCraftingPanel", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { typeof(bool) }, null); if (knownRecipesField?.FieldType != typeof(HashSet) || updatePiecesMethod?.ReturnType != typeof(void) || updateCraftingMethod?.ReturnType != typeof(void)) { knownRecipesField = null; updatePiecesMethod = null; reason = "missing or changed Player.m_knownRecipes/UpdateAvailablePiecesList contract"; return false; } reason = "authenticated peer ZRpc recipe union with author tracking and versioned sidecar"; return true; } internal static void Tick() { if (persistenceBlocked) { return; } try { TickCore(); } catch (Exception error) { BlockPersistence("ZR915", "tick", error); } } private static void TickCore() { if (knownRecipesField == null || updatePiecesMethod == null || (Object)(object)ZNet.instance == (Object)null) { return; } ZNet instance = ZNet.instance; if (instance.GetWorld() == null || !EnsureWorld(instance) || persistenceBlocked) { return; } RegisterPeers(instance); if (!ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady || !ZariRulesPlugin.Instance.Rules.ShareRecipes.Value) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } HashSet hashSet = Known(localPlayer); if (localPlayer != trackedPlayer) { trackedPlayer = localPlayer; ClientState.Clear(); nextRequestAt = 0.0; } if (instance.IsServer()) { Game instance2 = Game.instance; object obj; if (instance2 == null) { obj = null; } else { PlayerProfile playerProfile = instance2.GetPlayerProfile(); obj = ((playerProfile != null) ? playerProfile.GetName() : null); } if (obj == null) { obj = localPlayer.GetPlayerName() ?? string.Empty; } string text = (string)obj; if (SharedRecipeUnion.TryMergeWithAuthors(Union, hashSet, text, out string reason)) { if (SaveUnion()) { Broadcast(); } } else if (!string.IsNullOrEmpty(reason)) { BlockPersistence("ZR914", "merge", new InvalidDataException(reason)); return; } if (hashSet.Count >= Union.Count) { return; } Dictionary> dictionary = new Dictionary>(StringComparer.Ordinal); foreach (KeyValuePair item in Union) { if (hashSet.Contains(item.Key)) { continue; } string text2 = item.Value ?? string.Empty; if (string.IsNullOrEmpty(text) || !string.Equals(text2, text, StringComparison.Ordinal)) { if (!dictionary.TryGetValue(text2, out var value)) { value = (dictionary[text2] = new List()); } value.Add(item.Key); } } if (dictionary.Count > 0) { foreach (KeyValuePair> item2 in dictionary) { RecipeNotificationFeature.QueueNotification(item2.Key, item2.Value); } } ApplyUnion(localPlayer); return; } ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer == null || !Registered.Contains(serverPeer.m_rpc)) { return; } double num = (double)DateTime.UtcNow.Ticks / 10000000.0; if (!ClientState.NeedsSubmission(hashSet) && !(num >= nextRequestAt)) { return; } try { Send(serverPeer.m_rpc, hashSet); ClientState.Submitted(hashSet); nextRequestAt = num + 5.0; } catch (Exception error) { BlockPersistence("ZR914", "submit", error); } } internal static void ResetSession() { foreach (ZRpc item in Registered) { item.Unregister("ZariRules_SharedRecipes_V1"); } Registered.Clear(); AcknowledgedPeers.Clear(); Union.Clear(); trackedPlayer = null; ClientState.Clear(); worldUid = 0L; worldLoaded = false; persistenceBlocked = false; nextRequestAt = 0.0; } internal static void Uninstall() { ResetSession(); knownRecipesField = null; updatePiecesMethod = null; updateCraftingMethod = null; } private static bool EnsureWorld(ZNet net) { if (net.GetWorld() == null) { return false; } long worldUID = net.GetWorldUID(); if (worldLoaded && worldUID == worldUid) { return true; } ResetSession(); worldLoaded = true; worldUid = worldUID; if (!net.IsServer() || worldUID == 0L) { return worldUID != 0; } try { foreach (KeyValuePair item in SharedRecipeStore.LoadWithAuthors(Paths.ConfigPath, worldUID)) { Union[item.Key] = item.Value; } } catch (Exception error) { BlockPersistence("ZR912", "load", error); } return !persistenceBlocked; } private static void RegisterPeers(ZNet net) { HashSet current = new HashSet(); if (net.IsServer()) { foreach (ZNetPeer peer in net.GetPeers()) { if (peer != null && peer.IsReady()) { current.Add(peer.m_rpc); Register(peer.m_rpc); } } } else { ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady()) { current.Add(serverPeer.m_rpc); Register(serverPeer.m_rpc); } } Registered.RemoveWhere(delegate(ZRpc rpc) { if (current.Contains(rpc) && rpc.IsConnected()) { return false; } rpc.Unregister("ZariRules_SharedRecipes_V1"); AcknowledgedPeers.Remove(rpc); return true; }); } private static void Register(ZRpc rpc) { if (rpc != null && Registered.Add(rpc)) { rpc.Register("ZariRules_SharedRecipes_V1", (Action)OnRecipes); } } private static void OnRecipes(ZRpc rpc, ZPackage package) { try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || persistenceBlocked || !IsCurrentReadyPeer(instance, rpc) || !ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady || !ZariRulesPlugin.Instance.Rules.ShareRecipes.Value) { return; } if (package == null || package.Size() > 4194304) { throw new InvalidDataException("Shared recipe RPC payload is too large."); } if (instance.IsServer()) { string author = ((IEnumerable)instance.GetPeers()).FirstOrDefault((Func)((ZNetPeer p) => p != null && p.m_rpc == rpc))?.m_playerName ?? string.Empty; HashSet incoming = SharedRecipeCodec.Decode(package.GetArray()); AcknowledgedPeers.Add(rpc); if (SharedRecipeUnion.TryMergeWithAuthors(Union, incoming, author, out string reason)) { if (SaveUnion()) { Diagnostics.Info("ZR122", $"recipes=merged total={Union.Count}"); Broadcast(); } } else { if (!string.IsNullOrEmpty(reason)) { throw new InvalidDataException(reason); } Send(rpc, Union); } } else { if (instance.GetServerPeer()?.m_rpc != rpc) { return; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Dictionary dictionary = SharedRecipeCodec.DecodeWithAuthors(package.GetArray()); HashSet hashSet = Known(localPlayer); Dictionary> dictionary2 = new Dictionary>(StringComparer.Ordinal); foreach (KeyValuePair item in dictionary) { if (!hashSet.Contains(item.Key)) { string key = item.Value ?? string.Empty; if (!dictionary2.TryGetValue(key, out var value)) { value = (dictionary2[key] = new List()); } value.Add(item.Key); } } if (dictionary2.Count > 0) { foreach (KeyValuePair> item2 in dictionary2) { RecipeNotificationFeature.QueueNotification(item2.Key, item2.Value); } } Apply(localPlayer, dictionary.Keys); ClientState.Received(dictionary.Keys); int num = dictionary2.Values.Sum((List recipes) => recipes.Count); bool flag = AcknowledgedPeers.Add(rpc); if (flag || num > 0) { Diagnostics.Info("ZR123", $"recipes=received total={dictionary.Count} new={num} initial={flag}"); } nextRequestAt = (double)DateTime.UtcNow.Ticks / 10000000.0 + 30.0; } } catch (Exception ex) { ZariRulesPlugin.LogError("ZR911", "feature=SharedRecipes phase=rpc reason=" + ex.GetType().Name + ": " + ex.Message); } } private static bool IsCurrentReadyPeer(ZNet net, ZRpc rpc) { if (!Registered.Contains(rpc) || !rpc.IsConnected()) { return false; } if (!net.IsServer()) { ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer != null && serverPeer.IsReady()) { return serverPeer.m_rpc == rpc; } return false; } foreach (ZNetPeer peer in net.GetPeers()) { if (peer != null && peer.IsReady() && peer.m_rpc == rpc) { return true; } } return false; } private static void ApplyUnion(Player player) { Apply(player, Union.Keys); } private static void Apply(Player player, IEnumerable recipes) { HashSet hashSet = Known(player); int count = hashSet.Count; hashSet.UnionWith(recipes); if (hashSet.Count != count) { updatePiecesMethod.Invoke(player, null); if ((Object)(object)InventoryGui.instance != (Object)null && InventoryGui.IsVisible()) { updateCraftingMethod.Invoke(InventoryGui.instance, new object[1] { false }); } } } private static HashSet Known(Player player) { return (HashSet)knownRecipesField.GetValue(player); } private static void Broadcast() { foreach (ZRpc acknowledgedPeer in AcknowledgedPeers) { if (Registered.Contains(acknowledgedPeer) && acknowledgedPeer.IsConnected()) { Send(acknowledgedPeer, Union); } } } private static void Send(ZRpc rpc, IReadOnlyDictionary recipes) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown rpc.Invoke("ZariRules_SharedRecipes_V1", new object[1] { (object)new ZPackage(SharedRecipeCodec.EncodeWithAuthors(recipes)) }); } private static void Send(ZRpc rpc, IEnumerable recipes) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown rpc.Invoke("ZariRules_SharedRecipes_V1", new object[1] { (object)new ZPackage(SharedRecipeCodec.Encode(recipes)) }); } private static bool SaveUnion() { if (persistenceBlocked || worldUid == 0L) { return false; } try { SharedRecipeStore.Save(Paths.ConfigPath, worldUid, Union); return true; } catch (Exception error) { BlockPersistence("ZR913", "save", error); return false; } } private static void BlockPersistence(string code, string phase, Exception error) { if (!persistenceBlocked) { persistenceBlocked = true; ZariRulesPlugin.LogError(code, $"feature=SharedRecipes phase={phase} world={worldUid} reason={error.GetType().Name}: {error.Message}; synchronization stopped"); } } } internal sealed class SharedRecipeClientState { private readonly HashSet acknowledged = new HashSet(StringComparer.Ordinal); internal bool NeedsSubmission(ISet local) { return !acknowledged.SetEquals(local); } internal void Submitted(IEnumerable recipes) { acknowledged.Clear(); acknowledged.UnionWith(recipes); } internal void Received(IEnumerable recipes) { acknowledged.UnionWith(recipes); } internal void Clear() { acknowledged.Clear(); } } internal static class SharedRecipeUnion { internal static bool TryMerge(HashSet target, IEnumerable incoming, out string reason) { HashSet hashSet = new HashSet(target, StringComparer.Ordinal); hashSet.UnionWith(incoming); if (hashSet.SetEquals(target)) { reason = string.Empty; return false; } try { SharedRecipeCodec.Encode(hashSet); } catch (Exception ex) when (ex is InvalidDataException || ex is EncoderFallbackException) { reason = ex.Message; return false; } target.Clear(); target.UnionWith(hashSet); reason = string.Empty; return true; } internal static bool TryMergeWithAuthors(Dictionary target, IEnumerable incoming, string author, out string reason) { bool flag = false; Dictionary dictionary = new Dictionary(target, StringComparer.Ordinal); foreach (string item in incoming) { if (!string.IsNullOrEmpty(item)) { if (!dictionary.TryGetValue(item, out var value)) { dictionary[item] = author ?? string.Empty; flag = true; } else if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(author)) { dictionary[item] = author; flag = true; } } } if (!flag) { reason = string.Empty; return false; } try { SharedRecipeCodec.EncodeWithAuthors(dictionary); } catch (Exception ex) when (ex is InvalidDataException || ex is EncoderFallbackException) { reason = ex.Message; return false; } target.Clear(); foreach (KeyValuePair item2 in dictionary) { target[item2.Key] = item2.Value; } reason = string.Empty; return true; } } internal static class SharedRecipeStore { internal static string GetPath(string configPath, long worldUid) { if (worldUid == 0L) { throw new ArgumentOutOfRangeException("worldUid", "A loaded world UID is required."); } return Path.Combine(configPath, "ZariRules", "SharedRecipes", worldUid + ".bin"); } internal static Dictionary LoadWithAuthors(string configPath, long worldUid) { string path = GetPath(configPath, worldUid); if (!File.Exists(path)) { return new Dictionary(StringComparer.Ordinal); } long length = new FileInfo(path).Length; if (length < 12 || length > 4194304) { throw new InvalidDataException("Invalid shared recipe sidecar size."); } return SharedRecipeCodec.DecodeWithAuthors(File.ReadAllBytes(path)); } internal static HashSet Load(string configPath, long worldUid) { return new HashSet(LoadWithAuthors(configPath, worldUid).Keys, StringComparer.Ordinal); } internal static void Save(string configPath, long worldUid, IReadOnlyDictionary recipesWithAuthors) { string path = GetPath(configPath, worldUid); string? directoryName = Path.GetDirectoryName(path); string text = path + ".tmp"; Directory.CreateDirectory(directoryName); try { File.WriteAllBytes(text, SharedRecipeCodec.EncodeWithAuthors(recipesWithAuthors)); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } finally { if (File.Exists(text)) { File.Delete(text); } } } internal static void Save(string configPath, long worldUid, IEnumerable recipes) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (string recipe in recipes) { if (!string.IsNullOrEmpty(recipe)) { dictionary[recipe] = string.Empty; } } Save(configPath, worldUid, dictionary); } } internal static class SkillPreservationFeature { internal const string HarmonyId = "zari.rules.keep-skills"; private static Harmony? harmony; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Expected O, but got Unknown if (!DeathContext.Ready) { reason = "death context unavailable"; return false; } if (!CompatibilityProbe.VerifyMethod(typeof(Skills), "OnDeath", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "807a60cdbe58ac91d5620c9f1bbeb6d866a7b0085851c37edc19c08933c48591", out MethodInfo method, out reason) || !CompatibilityProbe.VerifyMethod(typeof(Player), "OnDeath", BindingFlags.Instance | BindingFlags.Public, typeof(void), Type.EmptyTypes, "76b17b9f1894281bde973d01f97a8f330b955e275d4556ceea1ead4ed56f2c21", out MethodInfo method2, out reason)) { return false; } try { harmony = new Harmony("zari.rules.keep-skills"); harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(SkillPreservationFeature), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)method2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(SkillPreservationFeature), "GuardDeathSkillsReset", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "Skills.OnDeath Prefix + guarded Player.OnDeath Skills.Clear call-site"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } Ready = false; } internal static bool VerifyPatchOwners(out string reason) { if (CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Skills), "OnDeath", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[1] { "zari.rules.keep-skills" }, out reason)) { return CompatibilityProbe.HasOnlyAllowedPatchOwners(AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null), (IReadOnlyCollection)(object)new string[3] { "zari.rules.keep-skills", "zari.rules.death-context", "randyknapp.mods.equipmentandquickslots" }, out reason); } return false; } private static bool Prefix(Skills __instance) { Player localPlayer = Player.m_localPlayer; if (!Ready || (Object)(object)localPlayer == (Object)null || (Object)(object)((Character)localPlayer).GetSkills() != (Object)(object)__instance || !DeathContext.TryGet(localPlayer, out DeathScopeTracker.Scope scope) || scope == null || !scope.KeepSkills) { return true; } Diagnostics.Info("ZR203", "skillDeathSuppression=executed path=Skills.OnDeath"); return false; } private static void ClearForDeath(Skills skills) { Player localPlayer = Player.m_localPlayer; if (Ready && (Object)(object)localPlayer != (Object)null && (Object)(object)((Character)localPlayer).GetSkills() == (Object)(object)skills && DeathContext.TryGet(localPlayer, out DeathScopeTracker.Scope scope) && scope != null && scope.KeepSkills) { Diagnostics.Info("ZR203", "skillDeathSuppression=executed path=DeathSkillsReset"); } else { skills.Clear(); } } private static IEnumerable GuardDeathSkillsReset(IEnumerable instructions) { List list = instructions.ToList(); MethodInfo objB = AccessTools.Method(typeof(Skills), "Clear", Type.EmptyTypes, (Type[])null); MethodInfo operand = AccessTools.Method(typeof(SkillPreservationFeature), "ClearForDeath", (Type[])null, (Type[])null); int num = 0; foreach (CodeInstruction item in list) { if ((item.opcode == OpCodes.Call || item.opcode == OpCodes.Callvirt) && object.Equals(item.operand, objB)) { item.opcode = OpCodes.Call; item.operand = operand; num++; } } if (num != 1) { throw new InvalidOperationException($"Expected one Player.OnDeath Skills.Clear call-site, found {num}."); } return list; } } internal static class StackLoadPatch { internal static IEnumerable Rewrite(IEnumerable instructions, MethodInfo original, MethodInfo replacement) { List list = instructions.ToList(); List list2 = new List(); for (int i = 1; i + 1 < list.Count; i++) { if (list[i].opcode == OpCodes.Call && object.Equals(list[i].operand, original) && list[i - 1].opcode == OpCodes.Ldfld && list[i - 1].operand is FieldInfo { Name: "m_maxStackSize" } && list[i + 1].opcode == OpCodes.Stfld && list[i + 1].operand is FieldInfo { Name: "m_stack" }) { list2.Add(i); } } if (list2.Count != 1) { throw new InvalidOperationException($"Expected one loaded stack clamp, found {list2.Count}."); } list[list2[0]].operand = replacement; return list; } } internal static class StackSizeFeature { internal const string HarmonyId = "zari.rules.stack-size"; private static readonly StackSizeRules Rules = new StackSizeRules(); private static Harmony? harmony; [ThreadStatic] private static int inventoryLoadDepth; internal static bool Ready { get; private set; } internal static bool Install(out string reason) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown if (Ready) { reason = "ItemDrop.Awake + ObjectDB.Awake/CopyOtherDB postfixes"; return true; } if (!TryGetTargets(out MethodInfo itemAwake, out MethodInfo databaseAwake, out MethodInfo copyOtherDatabase, out MethodInfo load, out MethodInfo legacyLoad, out MethodInfo loadedItemAdd, out reason)) { return false; } try { harmony = new Harmony("zari.rules.stack-size"); harmony.Patch((MethodBase)itemAwake, (HarmonyMethod)null, new HarmonyMethod(typeof(StackSizeFeature), "ItemAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); HarmonyMethod val = new HarmonyMethod(typeof(StackSizeFeature), "DatabasePostfix", (Type[])null) { priority = 0 }; harmony.Patch((MethodBase)databaseAwake, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)copyOtherDatabase, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); HarmonyMethod val2 = new HarmonyMethod(typeof(StackSizeFeature), "InventoryLoadPrefix", (Type[])null); HarmonyMethod val3 = new HarmonyMethod(typeof(StackSizeFeature), "InventoryLoadFinalizer", (Type[])null); harmony.Patch((MethodBase)load, val2, (HarmonyMethod)null, (HarmonyMethod)null, val3, (HarmonyMethod)null); harmony.Patch((MethodBase)legacyLoad, val2, (HarmonyMethod)null, (HarmonyMethod)null, val3, (HarmonyMethod)null); harmony.Patch((MethodBase)loadedItemAdd, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(StackSizeFeature), "LoadedItemTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); Ready = true; reason = "ItemDrop.Awake + ObjectDB.Awake/CopyOtherDB postfixes"; return true; } catch (Exception ex) { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Rules.Restore(SetMaximum); reason = ex.GetType().Name + ": " + ex.Message; return false; } } internal static void Apply(string source) { if (Ready) { RegisterDatabase(ObjectDB.instance); ItemDrop[] array = Resources.FindObjectsOfTypeAll(); for (int i = 0; i < array.Length; i++) { TrackInstance(array[i]); } int num = EffectiveMultiplier(); int num2 = Rules.Apply(num, SetMaximum); Diagnostics.Info("ZR320", $"stackSize=applied multiplier={num} tracked={num2} source={source}"); } } internal static void ResetSession() { Rules.Restore(SetMaximum); } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; Ready = false; Rules.Restore(SetMaximum); } private static void ItemAwakePostfix(ItemDrop __instance) { SharedData val = TrackInstance(__instance); if (val != null) { Rules.ApplyOne(val, EffectiveMultiplier(), SetMaximum); } } private static void DatabasePostfix(ObjectDB __instance) { RegisterDatabase(__instance); Rules.Apply(EffectiveMultiplier(), SetMaximum); } private static void RegisterDatabase(ObjectDB? database) { if (database?.m_items == null) { return; } foreach (GameObject item in database.m_items) { SharedData val = (((Object)(object)item != (Object)null) ? item.GetComponent() : null)?.m_itemData?.m_shared; if (val != null) { Rules.TrackCanonical(val, val.m_maxStackSize); } } } private static SharedData? TrackInstance(ItemDrop? itemDrop) { SharedData val = itemDrop?.m_itemData?.m_shared; object obj; if (itemDrop == null) { obj = null; } else { ItemData itemData = itemDrop.m_itemData; if (itemData == null) { obj = null; } else { GameObject dropPrefab = itemData.m_dropPrefab; obj = ((dropPrefab == null) ? null : dropPrefab.GetComponent()?.m_itemData?.m_shared); } } SharedData val2 = (SharedData)obj; if (val == null || val2 == null || !Rules.TrackInstance(val, val2)) { return null; } return val; } private static int EffectiveMultiplier() { if (!ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady) { return 1; } return ZariRulesPlugin.Instance.Rules.StackSizeMultiplier.Value; } private static void SetMaximum(SharedData shared, int maximum) { shared.m_maxStackSize = maximum; } private static void InventoryLoadPrefix(out bool __state) { inventoryLoadDepth++; __state = true; } private static Exception? InventoryLoadFinalizer(Exception? __exception, bool __state) { if (__state) { inventoryLoadDepth = Math.Max(0, inventoryLoadDepth - 1); } return __exception; } private static int ClampLoadedStack(int requested, int maximum) { return StackSizeLoadRules.Clamp(requested, maximum, inventoryLoadDepth > 0); } private static IEnumerable LoadedItemTranspiler(IEnumerable instructions) { MethodInfo original = AccessTools.Method(typeof(Mathf), "Min", new Type[2] { typeof(int), typeof(int) }, (Type[])null); MethodInfo replacement = AccessTools.Method(typeof(StackSizeFeature), "ClampLoadedStack", (Type[])null, (Type[])null); return StackLoadPatch.Rewrite(instructions, original, replacement); } private static bool TryGetTargets(out MethodInfo? itemAwake, out MethodInfo? databaseAwake, out MethodInfo? copyOtherDatabase, out MethodInfo? load, out MethodInfo? legacyLoad, out MethodInfo? loadedItemAdd, out string reason) { itemAwake = AccessTools.DeclaredMethod(typeof(ItemDrop), "Awake", Type.EmptyTypes, (Type[])null); databaseAwake = AccessTools.DeclaredMethod(typeof(ObjectDB), "Awake", Type.EmptyTypes, (Type[])null); copyOtherDatabase = AccessTools.DeclaredMethod(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null); load = AccessTools.DeclaredMethod(typeof(Inventory), "Load", new Type[1] { typeof(ZPackage) }, (Type[])null); legacyLoad = AccessTools.DeclaredMethod(typeof(Inventory), "Load", new Type[2] { typeof(ZPackage), typeof(bool) }, (Type[])null); loadedItemAdd = typeof(Inventory).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).SingleOrDefault((MethodInfo method) => method.Name == "AddItem" && method.ReturnType == typeof(bool) && (from parameter in method.GetParameters() select parameter.ParameterType).SequenceEqual(new Type[14] { typeof(int), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary), typeof(int), typeof(bool), typeof(bool), typeof(bool) })); FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ItemDrop), "m_itemData"); FieldInfo fieldInfo2 = AccessTools.DeclaredField(typeof(ItemData), "m_shared"); FieldInfo fieldInfo3 = AccessTools.DeclaredField(typeof(ItemData), "m_dropPrefab"); FieldInfo fieldInfo4 = AccessTools.DeclaredField(typeof(SharedData), "m_maxStackSize"); FieldInfo fieldInfo5 = AccessTools.DeclaredField(typeof(ObjectDB), "m_items"); if (!IsVoidInstanceMethod(itemAwake, isPublic: false, Type.EmptyTypes) || !IsVoidInstanceMethod(databaseAwake, isPublic: false, Type.EmptyTypes) || !IsVoidInstanceMethod(copyOtherDatabase, isPublic: true, new Type[1] { typeof(ObjectDB) }) || fieldInfo?.FieldType != typeof(ItemData) || !IsVoidInstanceMethod(load, isPublic: true, new Type[1] { typeof(ZPackage) }) || !IsVoidInstanceMethod(legacyLoad, isPublic: true, new Type[2] { typeof(ZPackage), typeof(bool) }) || loadedItemAdd == null || fieldInfo2?.FieldType != typeof(SharedData) || fieldInfo3?.FieldType != typeof(GameObject) || fieldInfo4?.FieldType != typeof(int) || fieldInfo5?.FieldType != typeof(List)) { reason = "missing or changed ItemDrop/ObjectDB stack-size contract"; return false; } reason = string.Empty; return true; } private static bool IsVoidInstanceMethod(MethodInfo? method, bool isPublic, Type[] parameters) { if (method == null || method.IsStatic || method.IsPublic != isPublic || method.ReturnType != typeof(void)) { return false; } ParameterInfo[] parameters2 = method.GetParameters(); if (parameters2.Length != parameters.Length) { return false; } for (int i = 0; i < parameters2.Length; i++) { if (parameters2[i].ParameterType != parameters[i]) { return false; } } return true; } } internal static class StackSizeLoadRules { internal static int Clamp(int requested, int maximum, bool loading) { if (!loading || maximum <= 1 || requested <= maximum) { return Math.Min(requested, maximum); } return requested; } } internal sealed class StackSizeRules where T : class { private sealed class CanonicalReference { internal T Canonical { get; } internal CanonicalReference(T canonical) { Canonical = canonical; } } private sealed class ReferenceComparer : IEqualityComparer { internal static readonly ReferenceComparer Instance = new ReferenceComparer(); public bool Equals(T? x, T? y) { return x == y; } public int GetHashCode(T obj) { return RuntimeHelpers.GetHashCode(obj); } } private readonly Dictionary baselines = new Dictionary(ReferenceComparer.Instance); private readonly List> instances = new List>(); private ConditionalWeakTable canonicalByInstance = new ConditionalWeakTable(); internal void TrackCanonical(T canonical, int baseline) { if (!baselines.ContainsKey(canonical)) { baselines.Add(canonical, baseline); } TrackInstance(canonical, canonical); } internal bool TrackInstance(T instance, T canonical) { if (!baselines.ContainsKey(canonical)) { return false; } if (canonicalByInstance.TryGetValue(instance, out CanonicalReference _)) { return true; } canonicalByInstance.Add(instance, new CanonicalReference(canonical)); instances.Add(new WeakReference(instance)); return true; } internal bool ApplyOne(T instance, int multiplier, Action setMaximum) { if (!TryGetTarget(instance, multiplier, out var maximum)) { return false; } setMaximum(instance, maximum); return true; } internal int Apply(int multiplier, Action setMaximum) { int num = 0; for (int num2 = instances.Count - 1; num2 >= 0; num2--) { if (!instances[num2].TryGetTarget(out var target)) { instances.RemoveAt(num2); } else if (ApplyOne(target, multiplier, setMaximum)) { num++; } } return num; } internal void Restore(Action setMaximum) { Apply(1, setMaximum); baselines.Clear(); instances.Clear(); canonicalByInstance = new ConditionalWeakTable(); } private bool TryGetTarget(T instance, int multiplier, out int maximum) { maximum = 0; if (!canonicalByInstance.TryGetValue(instance, out CanonicalReference value) || !baselines.TryGetValue(value.Canonical, out var value2)) { return false; } maximum = value2; if (value2 <= 1 || multiplier < 1 || multiplier > 20) { return true; } long num = (long)value2 * (long)multiplier; maximum = (int)((num > int.MaxValue) ? int.MaxValue : num); return true; } } internal static class StaminaFeature { private static Harmony? harmony; internal static bool Install(out string reason) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Expected O, but got Unknown //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Player), "UseStamina", new Type[1] { typeof(float) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.DeclaredMethod(typeof(Player), "HaveStamina", new Type[1] { typeof(float) }, (Type[])null); if (methodInfo == null || methodInfo2 == null || !methodInfo.IsPublic || !methodInfo2.IsPublic || methodInfo.IsStatic || methodInfo2.IsStatic || methodInfo.ReturnType != typeof(void) || methodInfo2.ReturnType != typeof(bool)) { reason = "Player stamina contracts changed"; return false; } try { harmony = new Harmony("zari.rules.stamina"); HarmonyMethod val = new HarmonyMethod(typeof(StaminaFeature), "Prefix", (Type[])null); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); reason = "local Player.UseStamina + HaveStamina matching cost multiplier"; return true; } catch (Exception ex) { Uninstall(); reason = ex.GetType().Name + ": " + ex.Message; return false; } } private static void Prefix(Player __instance, ref float __0) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer && ZariRulesPlugin.Instance.Rules.IsServerConfigurationReady) { __0 = RuleMath.StaminaCost(__0, ZariRulesPlugin.Instance.Rules.StaminaUsageMultiplier.Value); } } internal static void Uninstall() { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } harmony = null; } } [BepInPlugin("zari.rules", "Zari Rules", "1.3.2")] [BepInDependency("com.jotunn.jotunn", "2.30.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [NetworkCompatibility(/*Could not decode attribute arguments.*/)] public sealed class ZariRulesPlugin : BaseUnityPlugin { public const string PluginGuid = "zari.rules"; public const string PluginName = "Zari Rules"; public const string PluginVersion = "1.3.2"; private float nextWorldFeatureTick; private bool gameBuildReady; private ZNet? appliedWorld; internal static ZariRulesPlugin Instance { get; private set; } internal ManualLogSource Logger => ((BaseUnityPlugin)this).Logger; internal RulesConfig Rules { get; private set; } private void Awake() { Instance = this; Rules = new RulesConfig(this); Logger.LogInfo((object)(string.Format("ZR001 plugin={0} version={1} game={2} network={3} unity={4} ", "zari.rules", "1.3.2", Version.GetVersionString(false), 40u, Application.unityVersion) + string.Format("bepinex={0} harmony={1} jotunn={2} serverAuthoritative=true", typeof(BaseUnityPlugin).Assembly.GetName().Version, typeof(Harmony).Assembly.GetName().Version, "2.30.0"))); Logger.LogInfo((object)string.Format("ZR010 dependency=EAQS version={0} detected={1}", CompatibilityProbe.InstalledPluginVersion("randyknapp.mods.equipmentandquickslots"), Chainloader.PluginInfos.ContainsKey("randyknapp.mods.equipmentandquickslots"))); Logger.LogInfo((object)string.Format("ZR011 dependency=EpicLoot version={0} detected={1}", CompatibilityProbe.InstalledPluginVersion("randyknapp.mods.epicloot"), Chainloader.PluginInfos.ContainsKey("randyknapp.mods.epicloot"))); if (!CompatibilityProbe.VerifyGameBuild(out string reason)) { LogError("ZR900", "allFeatures=unavailable reason=" + reason); return; } gameBuildReady = true; Report("Stamina", StaminaFeature.Install(out string reason2), reason2, "ZR108"); Report("DeathContext", DeathContext.Install(out string reason3), reason3); Report("KeepInventory", DeathInventoryFeature.Install(out string reason4), reason4, "ZR100"); Report("KeepSkills", SkillPreservationFeature.Install(out string reason5), reason5, "ZR101"); Report("ResourceRate", ResourceRateController.Install(out string reason6), reason6, "ZR102"); Report("ProductionSpeed", ProductionSpeedController.Install(out string reason7), reason7, "ZR103"); Report("PlantGrowth", PlantGrowthFeature.Install(out string reason8), reason8, "ZR121"); Report("StackSize", StackSizeFeature.Install(out string reason9), reason9, "ZR104"); Report("HammerDurability", HammerDurabilityFeature.Install(out string reason10), reason10, "ZR120"); Report("MapReveal", MapRevealFeature.Install(out string reason11), reason11, "ZR105"); Report("MapPins", MapPinsFeature.Install(out string reason12), reason12, "ZR118"); Report("SharedRecipes", SharedRecipesFeature.Install(out string reason13), reason13, "ZR106"); Report("RecipeNotifications", RecipeNotificationFeature.Install(out string reason14), reason14, "ZR119"); Report("HealthHud", HealthHudFeature.Install(out string reason15), reason15, "ZR107"); Report("Diagnostics", Diagnostics.Install(out string reason16), reason16); Report("SessionReset", SessionLifecycleFeature.Install(out string reason17), reason17); ApplyEffectiveRules("startup"); } internal void ApplyEffectiveRules(string source) { ResourceRateController.Reconcile(source); ProductionSpeedController.ApplyToExisting(source); StackSizeFeature.Apply(source); HammerDurabilityFeature.Apply(source); MapPinsFeature.ReapplyPins(); } private void Update() { if (!gameBuildReady) { return; } HealthHudFeature.Tick(); RecipeNotificationFeature.Tick(); if (!(Time.unscaledTime < nextWorldFeatureTick)) { nextWorldFeatureTick = Time.unscaledTime + 1f; if ((Object)(object)ZNet.instance == (Object)null) { appliedWorld = null; } else if (Rules.IsServerConfigurationReady && ZNet.instance.GetWorld() != null && (Object)(object)appliedWorld != (Object)(object)ZNet.instance) { appliedWorld = ZNet.instance; ApplyEffectiveRules("world-ready"); } MapRevealFeature.Tick(); MapPinsFeature.Tick(); SharedRecipesFeature.Tick(); } } private void Start() { if (DeathInventoryFeature.Ready && !DeathInventoryFeature.VerifyPatchOwners(out string reason)) { DeathInventoryFeature.Uninstall(); Report("KeepInventory", ready: false, reason); } if (SkillPreservationFeature.Ready && !SkillPreservationFeature.VerifyPatchOwners(out string reason2)) { SkillPreservationFeature.Uninstall(); Report("KeepSkills", ready: false, reason2); } if (ProductionSpeedController.Ready && !ProductionSpeedController.VerifyPatchOwners(out string reason3)) { ProductionSpeedController.Uninstall(); Report("ProductionSpeed", ready: false, reason3); } } internal static void LogError(string code, string message) { try { if ((Object)(object)Instance != (Object)null) { Instance.Logger.LogError((object)(code + " " + message)); } } catch { } } private void OnDestroy() { if (Rules != null) { Rules.Dispose(); } RecipeNotificationFeature.Uninstall(); SessionLifecycleFeature.Uninstall(); StaminaFeature.Uninstall(); HealthHudFeature.Uninstall(); SharedRecipesFeature.Uninstall(); MapPinsFeature.Uninstall(); MapRevealFeature.Uninstall(); StackSizeFeature.Uninstall(); HammerDurabilityFeature.Uninstall(); Diagnostics.Uninstall(); ProductionSpeedController.Uninstall(); PlantGrowthFeature.Uninstall(); ResourceRateController.Uninstall(); SkillPreservationFeature.Uninstall(); DeathInventoryFeature.Uninstall(); DeathContext.Uninstall(); } private void Report(string feature, bool ready, string reason, string code = "ZR900") { if (ready) { Logger.LogInfo((object)(code + " feature=" + feature + " status=ready contract=" + reason)); } else { Logger.LogError((object)("ZR900 feature=" + feature + " status=unavailable reason=" + reason)); } } } }