using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; 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: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: IgnoresAccessChecksTo("com.rlabrecque.steamworks.net")] [assembly: AssemblyCompany("NoVikingLeftBehind")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.2.0")] [assembly: AssemblyInformationalVersion("0.4.2+f81126f3ee7a29f200ea6bb89f303985b2e82935")] [assembly: AssemblyProduct("NoVikingLeftBehind")] [assembly: AssemblyTitle("NoVikingLeftBehind")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.4.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] 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; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServerSync { [PublicAPI] public abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] public class SyncedConfigEntry(ConfigEntry sourceConfig) : OwnConfigEntryBase() { public readonly ConfigEntry SourceConfig = sourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } public abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] public sealed class CustomSyncedValue : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] public class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register(configSync2.Name + " ConfigSync", (Action)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List CurrentList = new List(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List(adminList.GetList()); List list = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId != null) ? ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })) : adminList.Contains(hostName); }).ToList(); SendAdmin(ZNet.instance.GetPeers().Except(list).ToList(), isAdmin: false); SendAdmin(list, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register(configSync.Name + " ConfigSync", (Action)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary configValues = new Dictionary(); public readonly Dictionary customValues = new Dictionary(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished; public volatile int versionMatchQueued = -1; public readonly List Package = new List(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary? __state, ZNet __instance, ZRpc rpc) { //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend != 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary __state, ZNet __instance, ZRpc rpc) { ZNetPeer peer; if (__instance.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List list = new List(); if (configSync.CurrentVersion != null) { list.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); list.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)methodInfo == null) ? ((object)val.Contains(rpc.GetSocket().GetHostName())) : methodInfo.Invoke(ZNet.instance, new object[2] { val, rpc.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, list, partial: false); yield return ((MonoBehaviour)__instance).StartCoroutine(configSync.sendZPackage(new List { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section; public string key; public Type type; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected; public string received; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet configSyncs; private readonly HashSet allConfigs = new HashSet(); private HashSet allCustomValues = new HashSet(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary> configValueCache = new Dictionary>(); private readonly List> cacheExpirations = new List>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0051; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag == true; } if (num) { return !lockExempt; } goto IL_0051; IL_0051: return false; } set { forceConfigLocking = value; } } public bool IsAdmin { get { if (!lockExempt) { return isSourceOfTruth; } return true; } } public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } public event Action? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry AddConfigEntry(ConfigEntry configEntry) { OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry); SyncedConfigEntry syncedEntry = ownConfigEntryBase as SyncedConfigEntry; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry(configEntry); AccessTools.DeclaredField(typeof(ConfigDescription), "k__BackingField").SetValue(((ConfigEntryBase)configEntry).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry).Description.Tags ?? Array.Empty()).Concat(new SyncedConfigEntry[1] { syncedEntry }).ToArray()); configEntry.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry AddLockingConfigEntry(ConfigEntry lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue); allCustomValues = new HashSet(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Expected O, but got Unknown //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Expected O, but got Unknown //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary value)) { value = new SortedDictionary(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4) != 0) { MemoryStream stream = new MemoryStream(package.ReadByteArray()); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int num2 = 0; num2 < num; num2++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config)); if (configSync == null) { return true; } if (!configSync.IsSourceOfTruth && config.SynchronizedConfig && config.LocalBaseValue != null) { if (!configSync.IsLocked) { if (config == configSync.lockedConfig) { return lockExempt; } return true; } return false; } return true; } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer.m_socket.IsConnected()) { ZPackage val = new ZPackage(); val.Write((byte)2); val.Write(packageIdentifier); val.Write(fragment); val.Write(fragments); val.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(val); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer.m_server ? 0 : peer.m_uid, text, new object[1] { pkg }); } } IEnumerable waitForQueue() { float timeout = Time.time + 30f; while (peer.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer.m_uid} after 30 seconds config sending timeout"); peer.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty().GetEnumerator(); } List list = (List)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List peers, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] array = package.GetArray(); if (array != null && array.LongLength > 10000) { ZPackage val = new ZPackage(); val.Write((byte)4); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal)) { deflateStream.Write(array, 0, array.Length); } val.Write(memoryStream.ToArray()); package = val; } List> writers = (from p in peers where p.IsReady() select distributeConfigToPeers(p, package)).ToList(); writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); while (writers.Count > 0) { yield return null; writers.RemoveAll((IEnumerator writer) => !writer.MoveNext()); } } private void Broadcast(long target, params ConfigEntryBase[] configs) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(configs); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private void Broadcast(long target, params CustomSyncedValueBase[] customValues) { if (!IsLocked || isServer) { ZPackage package = ConfigsToPackage(null, customValues); ZNet instance = ZNet.instance; if (instance != null) { ((MonoBehaviour)instance).StartCoroutine(sendZPackage(target, package)); } } } private static OwnConfigEntryBase? configData(ConfigEntryBase config) { return config.Description.Tags?.OfType().SingleOrDefault(); } public static SyncedConfigEntry? ConfigData(ConfigEntry config) { return ((ConfigEntryBase)config).Description.Tags?.OfType>().SingleOrDefault(); } private static T configAttribute(ConfigEntryBase config) { return config.Description.Tags.OfType().First(); } private static Type configType(ConfigEntryBase config) { return configType(config.SettingType); } private static Type configType(Type type) { if (!type.IsEnum) { return type; } return Enum.GetUnderlyingType(type); } private static ZPackage ConfigsToPackage(IEnumerable? configs = null, IEnumerable? customValues = null, IEnumerable? packageEntries = null, bool partial = true) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown List list = configs?.Where((ConfigEntryBase config) => configData(config).SynchronizedConfig).ToList() ?? new List(); List list2 = customValues?.ToList() ?? new List(); ZPackage val = new ZPackage(); val.Write(partial ? ((byte)1) : ((byte)0)); val.Write(list.Count + list2.Count + (packageEntries?.Count() ?? 0)); foreach (PackageEntry item in packageEntries ?? Array.Empty()) { AddEntryToPackage(val, item); } foreach (CustomSyncedValueBase item2 in list2) { AddEntryToPackage(val, new PackageEntry { section = "Internal", key = item2.Identifier, type = item2.Type, value = item2.BoxedValue }); } foreach (ConfigEntryBase item3 in list) { AddEntryToPackage(val, new PackageEntry { section = item3.Definition.Section, key = item3.Definition.Key, type = configType(item3), value = item3.BoxedValue }); } return val; } private static void AddEntryToPackage(ZPackage package, PackageEntry entry) { package.Write(entry.section); package.Write(entry.key); package.Write((entry.value == null) ? "" : GetZPackageTypeString(entry.type)); AddValueToZPackage(package, entry.value); } private static string GetZPackageTypeString(Type type) { return type.AssemblyQualifiedName; } private static void AddValueToZPackage(ZPackage package, object? value) { Type type = value?.GetType(); if (value is Enum) { value = ((IConvertible)value).ToType(Enum.GetUnderlyingType(value.GetType()), CultureInfo.InvariantCulture); } else { if (value is ICollection collection) { package.Write(collection.Count); { foreach (object item in collection) { AddValueToZPackage(package, item); } return; } } if ((object)type != null && type.IsValueType && !type.IsPrimitive) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); package.Write(fields.Length); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { package.Write(GetZPackageTypeString(fieldInfo.FieldType)); AddValueToZPackage(package, fieldInfo.GetValue(value)); } return; } } ZRpc.Serialize(new object[1] { value }, ref package); } private static object ReadValueWithTypeFromZPackage(ZPackage package, Type type) { if ((object)type != null && type.IsValueType && !type.IsPrimitive && !type.IsEnum) { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); int num = package.ReadInt(); if (num != fields.Length) { throw new InvalidDeserializationTypeException { received = $"(field count: {num})", expected = $"(field count: {fields.Length})" }; } object uninitializedObject = FormatterServices.GetUninitializedObject(type); FieldInfo[] array = fields; foreach (FieldInfo fieldInfo in array) { string text = package.ReadString(); if (text != GetZPackageTypeString(fieldInfo.FieldType)) { throw new InvalidDeserializationTypeException { received = text, expected = GetZPackageTypeString(fieldInfo.FieldType), field = fieldInfo.Name }; } fieldInfo.SetValue(uninitializedObject, ReadValueWithTypeFromZPackage(package, fieldInfo.FieldType)); } return uninitializedObject; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { int num2 = package.ReadInt(); IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); Type type2 = typeof(KeyValuePair<, >).MakeGenericType(type.GenericTypeArguments); FieldInfo field = type2.GetField("key", BindingFlags.Instance | BindingFlags.NonPublic); FieldInfo field2 = type2.GetField("value", BindingFlags.Instance | BindingFlags.NonPublic); for (int j = 0; j < num2; j++) { object obj = ReadValueWithTypeFromZPackage(package, type2); dictionary.Add(field.GetValue(obj), field2.GetValue(obj)); } return dictionary; } if (type != typeof(List) && type.IsGenericType) { Type type3 = typeof(ICollection<>).MakeGenericType(type.GenericTypeArguments[0]); if ((object)type3 != null && type3.IsAssignableFrom(type)) { int num3 = package.ReadInt(); object obj2 = Activator.CreateInstance(type); MethodInfo method = type3.GetMethod("Add"); for (int k = 0; k < num3; k++) { method.Invoke(obj2, new object[1] { ReadValueWithTypeFromZPackage(package, type.GenericTypeArguments[0]) }); } return obj2; } } ParameterInfo parameterInfo = (ParameterInfo)FormatterServices.GetUninitializedObject(typeof(ParameterInfo)); AccessTools.DeclaredField(typeof(ParameterInfo), "ClassImpl").SetValue(parameterInfo, type); List source = new List(); ZRpc.Deserialize(new ParameterInfo[2] { null, parameterInfo }, package, ref source); return source.First(); } } [PublicAPI] [HarmonyPatch] public class VersionCheck { private static readonly HashSet versionChecks; private static readonly Dictionary notProcessedNames; public string Name; private string? displayName; private string? currentVersion; private string? minimumRequiredVersion; public bool ModRequired = true; private string? ReceivedCurrentVersion; private string? ReceivedMinimumRequiredVersion; private readonly List ValidatedClients = new List(); private ConfigSync? ConfigSync; public string DisplayName { get { return displayName ?? Name; } set { displayName = value; } } public string CurrentVersion { get { return currentVersion ?? "0.0.0"; } set { currentVersion = value; } } public string MinimumRequiredVersion { get { string text = minimumRequiredVersion; if (text == null) { if (!ModRequired) { return "0.0.0"; } text = CurrentVersion; } return text; } set { minimumRequiredVersion = value; } } private static void PatchServerSync() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Patches patchInfo = PatchProcessor.GetPatchInfo((MethodBase)AccessTools.DeclaredMethod(typeof(ZNet), "Awake", (Type[])null, (Type[])null)); if (patchInfo != null && patchInfo.Postfixes.Count((Patch p) => p.PatchMethod.DeclaringType == typeof(ConfigSync.RegisterRPCPatch)) > 0) { return; } Harmony val = new Harmony("org.bepinex.helpers.ServerSync"); foreach (Type item in from t in typeof(ConfigSync).GetNestedTypes(BindingFlags.NonPublic).Concat(new Type[1] { typeof(VersionCheck) }) where t.IsClass select t) { val.PatchAll(item); } } static VersionCheck() { versionChecks = new HashSet(); notProcessedNames = new Dictionary(); typeof(ThreadingHelper).GetMethod("StartSyncInvoke").Invoke(ThreadingHelper.Instance, new object[1] { new Action(PatchServerSync) }); } public VersionCheck(string name) { Name = name; ModRequired = true; versionChecks.Add(this); } public VersionCheck(ConfigSync configSync) { ConfigSync = configSync; Name = ConfigSync.Name; versionChecks.Add(this); } public void Initialize() { ReceivedCurrentVersion = null; ReceivedMinimumRequiredVersion = null; if (ConfigSync != null) { Name = ConfigSync.Name; DisplayName = ConfigSync.DisplayName; CurrentVersion = ConfigSync.CurrentVersion; MinimumRequiredVersion = ConfigSync.MinimumRequiredVersion; ModRequired = ConfigSync.ModRequired; } } private bool IsVersionOk() { if (ReceivedMinimumRequiredVersion == null || ReceivedCurrentVersion == null) { return !ModRequired; } bool num = new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion); bool flag = new Version(ReceivedCurrentVersion) >= new Version(MinimumRequiredVersion); return num && flag; } private string ErrorClient() { if (ReceivedMinimumRequiredVersion == null) { return DisplayName + " is not installed on the server."; } if (!(new Version(CurrentVersion) >= new Version(ReceivedMinimumRequiredVersion))) { return DisplayName + " needs to be at least version " + ReceivedMinimumRequiredVersion + ". You have version " + CurrentVersion + "."; } return DisplayName + " may not be higher than version " + ReceivedCurrentVersion + ". You have version " + CurrentVersion + "."; } private string ErrorServer(ZRpc rpc) { return "Disconnect: The client (" + rpc.GetSocket().GetHostName() + ") doesn't have the correct " + DisplayName + " version " + MinimumRequiredVersion; } private string Error(ZRpc? rpc = null) { if (rpc != null) { return ErrorServer(rpc); } return ErrorClient(); } private static VersionCheck[] GetFailedClient() { return versionChecks.Where((VersionCheck check) => !check.IsVersionOk()).ToArray(); } private static VersionCheck[] GetFailedServer(ZRpc rpc) { return versionChecks.Where((VersionCheck check) => check.ModRequired && !check.ValidatedClients.Contains(rpc)).ToArray(); } private static void Logout() { Game.instance.Logout(true, true); AccessTools.DeclaredField(typeof(ZNet), "m_connectionStatus").SetValue(null, (object)(ConnectionStatus)3); } private static void DisconnectClient(ZRpc rpc) { rpc.Invoke("Error", new object[1] { 3 }); } private static void CheckVersion(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, null); } private static void CheckVersion(ZRpc rpc, ZPackage pkg, Action? original) { string text = pkg.ReadString(); string text2 = pkg.ReadString(); string text3 = pkg.ReadString(); bool flag = false; foreach (VersionCheck versionCheck in versionChecks) { if (!(text != versionCheck.Name)) { Debug.Log((object)("Received " + versionCheck.DisplayName + " version " + text3 + " and minimum version " + text2 + " from the " + (ZNet.instance.IsServer() ? "client" : "server") + ".")); versionCheck.ReceivedMinimumRequiredVersion = text2; versionCheck.ReceivedCurrentVersion = text3; if (ZNet.instance.IsServer() && versionCheck.IsVersionOk()) { versionCheck.ValidatedClients.Add(rpc); } flag = true; } } if (flag) { return; } pkg.SetPos(0); if (original != null) { original(rpc, pkg); if (pkg.GetPos() == 0) { notProcessedNames.Add(text, text3); } } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] [HarmonyPrefix] private static bool RPC_PeerInfo(ZRpc rpc, ZNet __instance) { VersionCheck[] array = (__instance.IsServer() ? GetFailedServer(rpc) : GetFailedClient()); if (array.Length == 0) { return true; } VersionCheck[] array2 = array; for (int i = 0; i < array2.Length; i++) { Debug.LogWarning((object)array2[i].Error(rpc)); } if (__instance.IsServer()) { DisconnectClient(rpc); } else { Logout(); } return false; } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] [HarmonyPrefix] private static void RegisterAndCheckVersion(ZNetPeer peer, ZNet __instance) { //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Expected O, but got Unknown notProcessedNames.Clear(); IDictionary dictionary = (IDictionary)typeof(ZRpc).GetField("m_functions", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(peer.m_rpc); if (dictionary.Contains(StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck"))) { object obj = dictionary[StringExtensionMethods.GetStableHashCode("ServerSync VersionCheck")]; Action action = (Action)obj.GetType().GetField("m_action", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj); peer.m_rpc.Register("ServerSync VersionCheck", (Action)delegate(ZRpc rpc, ZPackage pkg) { CheckVersion(rpc, pkg, action); }); } else { peer.m_rpc.Register("ServerSync VersionCheck", (Action)CheckVersion); } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.Initialize(); if (versionCheck.ModRequired || __instance.IsServer()) { Debug.Log((object)("Sending " + versionCheck.DisplayName + " version " + versionCheck.CurrentVersion + " and minimum version " + versionCheck.MinimumRequiredVersion + " to the " + (__instance.IsServer() ? "client" : "server") + ".")); ZPackage val = new ZPackage(); val.Write(versionCheck.Name); val.Write(versionCheck.MinimumRequiredVersion); val.Write(versionCheck.CurrentVersion); peer.m_rpc.Invoke("ServerSync VersionCheck", new object[1] { val }); } } } [HarmonyPatch(typeof(ZNet), "Disconnect")] [HarmonyPrefix] private static void RemoveDisconnected(ZNetPeer peer, ZNet __instance) { if (!__instance.IsServer()) { return; } foreach (VersionCheck versionCheck in versionChecks) { versionCheck.ValidatedClients.Remove(peer.m_rpc); } } [HarmonyPatch(typeof(FejdStartup), "ShowConnectError")] [HarmonyPostfix] private static void ShowConnectionError(FejdStartup __instance) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: 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_01d0: Unknown result type (might be due to invalid IL or missing references) //IL_01de: 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_01ee: Unknown result type (might be due to invalid IL or missing references) if (!__instance.m_connectionFailedPanel.activeSelf || (int)ZNet.GetConnectionStatus() != 3) { return; } bool flag = false; VersionCheck[] failedClient = GetFailedClient(); if (failedClient.Length != 0) { string text = string.Join("\n", failedClient.Select((VersionCheck check) => check.Error())); TMP_Text connectionFailedError = __instance.m_connectionFailedError; connectionFailedError.text = connectionFailedError.text + "\n" + text; flag = true; } foreach (KeyValuePair item in notProcessedNames.OrderBy, string>((KeyValuePair kv) => kv.Key)) { if (!__instance.m_connectionFailedError.text.Contains(item.Key)) { TMP_Text connectionFailedError2 = __instance.m_connectionFailedError; connectionFailedError2.text = connectionFailedError2.text + "\nServer expects you to have " + item.Key + " (Version: " + item.Value + ") installed."; flag = true; } } if (flag) { RectTransform component = ((Component)__instance.m_connectionFailedPanel.transform.Find("Image")).GetComponent(); Vector2 sizeDelta = component.sizeDelta; sizeDelta.x = 675f; component.sizeDelta = sizeDelta; __instance.m_connectionFailedError.ForceMeshUpdate(false, false); float num = __instance.m_connectionFailedError.renderedHeight + 105f; RectTransform component2 = ((Component)((Component)component).transform.Find("ButtonOk")).GetComponent(); component2.anchoredPosition = new Vector2(component2.anchoredPosition.x, component2.anchoredPosition.y - (num - component.sizeDelta.y) / 2f); sizeDelta = component.sizeDelta; sizeDelta.y = num; component.sizeDelta = sizeDelta; } } } } namespace NoVikingLeftBehind { internal sealed class ConfigWatcher : IDisposable { private const double DebounceSeconds = 0.5; private const double PollIntervalSeconds = 0.5; private const double IgnoreAfterReloadSeconds = 1.0; private readonly ConfigFile _cfg; private readonly ManualLogSource _log; private readonly string _fileName; private readonly string _logPrefix; private FileSystemWatcher _fsw; private volatile bool _pending; private DateTime _lastEventUtc; private DateTime _lastReloadUtc = DateTime.MinValue; private DateTime _lastPollUtc = DateTime.MinValue; private DateTime _lastKnownWriteUtc = DateTime.MinValue; public ConfigWatcher(ConfigFile cfg, ManualLogSource log, string logPrefix = "[Config]") { _cfg = cfg; _log = log; _logPrefix = logPrefix; _fileName = Path.GetFileName(cfg.ConfigFilePath); _lastKnownWriteUtc = SafeGetLastWriteUtc(); string directoryName = Path.GetDirectoryName(cfg.ConfigFilePath); if (string.IsNullOrEmpty(directoryName)) { _log.LogWarning((object)(_logPrefix + " watcher: could not resolve a directory for " + cfg.ConfigFilePath)); return; } try { _fsw = new FileSystemWatcher(directoryName) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite), IncludeSubdirectories = false }; _fsw.Changed += OnFsEvent; _fsw.Created += OnFsEvent; _fsw.Renamed += OnFsRenamed; _fsw.Error += OnFsError; _fsw.EnableRaisingEvents = true; } catch (Exception ex) { _log.LogWarning((object)(_logPrefix + " could not start FileSystemWatcher, relying on polling: " + ex.Message)); _fsw = null; } _log.LogInfo((object)(_logPrefix + " watching " + cfg.ConfigFilePath + " for live edits")); } private void OnFsEvent(object sender, FileSystemEventArgs e) { if (string.Equals(e.Name, _fileName, StringComparison.OrdinalIgnoreCase)) { Schedule(); } } private void OnFsRenamed(object sender, RenamedEventArgs e) { if (string.Equals(e.Name, _fileName, StringComparison.OrdinalIgnoreCase)) { Schedule(); } } private void OnFsError(object sender, ErrorEventArgs e) { _log.LogWarning((object)(_logPrefix + " watcher error (falling back to polling): " + e.GetException())); } private void Schedule() { if (!((DateTime.UtcNow - _lastReloadUtc).TotalSeconds < 1.0)) { _pending = true; _lastEventUtc = DateTime.UtcNow; } } private DateTime SafeGetLastWriteUtc() { try { return File.GetLastWriteTimeUtc(_cfg.ConfigFilePath); } catch { return DateTime.MinValue; } } public void Pump() { if ((DateTime.UtcNow - _lastPollUtc).TotalSeconds >= 0.5) { _lastPollUtc = DateTime.UtcNow; DateTime dateTime = SafeGetLastWriteUtc(); if (dateTime != DateTime.MinValue && dateTime != _lastKnownWriteUtc) { _lastKnownWriteUtc = dateTime; Schedule(); } } if (!_pending || (DateTime.UtcNow - _lastEventUtc).TotalSeconds < 0.5) { return; } _pending = false; try { DoReload(); } catch (Exception ex) { _log.LogError((object)(_logPrefix + " reload failed: " + ex)); } } private void DoReload() { Dictionary before = Snapshot(); bool saveOnConfigSet = _cfg.SaveOnConfigSet; _cfg.SaveOnConfigSet = false; try { _cfg.Reload(); } finally { _cfg.SaveOnConfigSet = saveOnConfigSet; } _lastReloadUtc = DateTime.UtcNow; _lastKnownWriteUtc = SafeGetLastWriteUtc(); List list = Diff(before); _log.LogInfo((object)((list.Count == 0) ? (_logPrefix + " reloaded: no changes") : (_logPrefix + " reloaded: " + string.Join(", ", list.ToArray())))); } private Dictionary Snapshot() { Dictionary dictionary = new Dictionary(); foreach (KeyValuePair item in _cfg) { try { dictionary[item.Key] = item.Value.GetSerializedValue(); } catch { } } return dictionary; } private List Diff(Dictionary before) { List list = new List(); foreach (KeyValuePair item in _cfg) { string serializedValue; try { serializedValue = item.Value.GetSerializedValue(); } catch { continue; } if (!before.TryGetValue(item.Key, out string value)) { value = "?"; } if (value != serializedValue) { list.Add("[" + item.Key.Section + "] " + item.Key.Key + " " + value + " -> " + serializedValue); } } return list; } public void Dispose() { if (_fsw != null) { try { _fsw.EnableRaisingEvents = false; _fsw.Changed -= OnFsEvent; _fsw.Created -= OnFsEvent; _fsw.Renamed -= OnFsRenamed; _fsw.Error -= OnFsError; _fsw.Dispose(); } catch { } _fsw = null; } } } internal enum ModuleSide { Server, Client, Both } internal abstract class FeatureModule { public ConfigEntry EnabledCfg; protected Harmony Harmony; protected ConfigFile Cfg; public string Status = "not-run"; public bool Applied; public abstract string Name { get; } public virtual ModuleSide Side => ModuleSide.Both; public virtual string Section => Name; public virtual bool DefaultEnabled => true; protected virtual string EnabledDescription => "Enable the " + Name + " module."; public bool Enabled { get { if (EnabledCfg != null) { return EnabledCfg.Value; } return false; } } public bool Active { get { if (Applied) { return Enabled; } return false; } } protected static ManualLogSource Log => NoVikingLeftBehindPlugin.Log; public void Configure(ConfigFile cfg) { Cfg = cfg; EnabledCfg = BindSynced(Section, "Enabled", DefaultEnabled, EnabledDescription); Bind(); } public void TryEnable(string guidPrefix, ModuleSide runningSide) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown if (Side != ModuleSide.Both && Side != runningSide) { Applied = false; Status = "disabled(side)"; return; } if (!Enabled) { Applied = false; Status = "disabled"; return; } try { Harmony = new Harmony(guidPrefix + "." + Name); ApplyPatches(); Applied = true; Status = "applied"; Log.LogInfo((object)("[" + Name + "] applied")); } catch (Exception ex) { Applied = false; string text = ((ex.InnerException != null) ? ex.InnerException.Message : ex.Message); Status = "FAILED(" + text + ")"; Log.LogError((object)("[" + Name + "] FAILED to patch: " + ex)); Disable(); } } public virtual void Disable() { Applied = false; try { if (Harmony != null) { Harmony.UnpatchSelf(); } } catch (Exception ex) { Log.LogWarning((object)("[" + Name + "] unpatch failed: " + ex.Message)); } } protected abstract void Bind(); protected abstract void ApplyPatches(); public virtual void OnConfigChanged(ConfigEntryBase entry) { } public virtual string StatusDetail() { return null; } protected ConfigEntry BindSynced(string section, string key, T defaultValue, string description) { return NoVikingLeftBehindPlugin.BindSynced(section, key, defaultValue, description, this); } protected ConfigEntry BindSynced(string key, T defaultValue, string description) { return NoVikingLeftBehindPlugin.BindSynced(Section, key, defaultValue, description, this); } protected ConfigEntry BindLocal(string section, string key, T defaultValue, string description) { return NoVikingLeftBehindPlugin.BindLocal(section, key, defaultValue, description, this); } protected ConfigEntry BindLocal(string key, T defaultValue, string description) { return NoVikingLeftBehindPlugin.BindLocal(Section, key, defaultValue, description, this); } protected internal static bool ServerActive() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } protected internal static bool ClientActive() { if ((Object)(object)ZNet.instance != (Object)null) { return !ZNet.instance.IsDedicated(); } return false; } } internal static class Frontier { public const string Section = "Frontier"; public static readonly string[] BossKeys = new string[7] { "defeated_eikthyr", "defeated_gdking", "defeated_bonemass", "defeated_dragon", "defeated_goblinking", "defeated_queen", "defeated_fader" }; public const int MaxTier = 7; public static ConfigEntry TierOverride; public static ConfigEntry TiersBehind; private static int _autoTier; private static bool _known; public static int WorldTier { get { if (IsOverridden) { return Math.Min(7, TierOverride.Value); } return _autoTier; } } public static bool IsOverridden { get { if (TierOverride != null) { return TierOverride.Value >= 0; } return false; } } public static int AutoTier => _autoTier; public static void BindConfig() { TierOverride = NoVikingLeftBehindPlugin.BindSynced("Frontier", "TierOverride", -1, "Force the world tier instead of reading the boss keys. -1 = auto. 0 = no boss killed, 1 Eikthyr, 2 The Elder, 3 Bonemass, 4 Moder, 5 Yagluth, 6 The Queen, 7 Fader. For testing.", null); TiersBehind = NoVikingLeftBehindPlugin.BindSynced("Frontier", "TiersBehind", 1, "How many tiers below the world tier a material has to be before the catch-up rules apply to it. 1 = everything up to WorldTier-1 is 'behind the frontier' (the group killed the Elder -> WorldTier 2 -> bronze, tier 1, qualifies).", null); NoVikingLeftBehindPlugin.ConfigChanged += OnConfigChanged; } private static void OnConfigChanged(ConfigEntryBase entry) { if ((object)entry == TierOverride || (object)entry == TiersBehind) { NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Frontier] config changed: WorldTier=" + Describe() + " TiersBehind=" + TiersBehind.Value)); } } public static bool Recompute() { ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } int num = 0; for (int i = 0; i < BossKeys.Length; i++) { if (instance.GetGlobalKey(BossKeys[i])) { num = i + 1; } } bool flag = !_known || num != _autoTier; int autoTier = _autoTier; _autoTier = num; _known = true; if (flag) { NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Frontier] WorldTier=" + Describe() + (IsOverridden ? (" [auto would be " + num + "]") : (" (was " + autoTier + ", key " + KeyFor(num) + ")")) + " TiersBehind=" + ((TiersBehind == null) ? 1 : TiersBehind.Value))); } return flag; } public static string KeyFor(int tier) { if (tier <= 0) { return "none"; } if (tier > BossKeys.Length) { return "?"; } return BossKeys[tier - 1]; } public static string Describe() { if (!IsOverridden) { return WorldTier + " (auto)"; } return WorldTier + " (override)"; } public static string BehindRangeText() { int num = WorldTier - ((TiersBehind == null) ? 1 : TiersBehind.Value); if (num >= 1) { return "1.." + num; } return "none"; } } internal sealed class FrontierModule : FeatureModule { public override string Name => "Frontier"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "Frontier"; protected override void Bind() { } protected override void ApplyPatches() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "GlobalKeyAdd", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); if (methodInfo == null) { throw new Exception("ZoneSystem.GlobalKeyAdd(string,bool) not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(FrontierModule), "KeysChanged", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZoneSystem), "GlobalKeyRemove", new Type[2] { typeof(string), typeof(bool) }, (Type[])null); if (methodInfo2 != null) { Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(FrontierModule), "KeysChanged", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(typeof(ZoneSystem), "ClearGlobalKeys", (Type[])null, (Type[])null); if (methodInfo3 != null) { Harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(FrontierModule), "KeysChanged", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(typeof(ZoneSystem), "Start", (Type[])null, (Type[])null); if (methodInfo4 != null) { Harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(FrontierModule), "KeysChanged", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void KeysChanged() { try { Frontier.Recompute(); Tiers.ValidateOnce(); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Frontier] recompute failed: " + ex)); } } public override string StatusDetail() { return "WorldTier=" + Frontier.Describe() + " autoKey=" + Frontier.KeyFor(Frontier.AutoTier) + " TiersBehind=" + Frontier.TiersBehind.Value + " materials=" + Tiers.Count + " unknown=" + Tiers.UnknownCount; } } internal static class CatchupUtil { internal static ConfigEntry SelfTestCfg; internal static bool SelfTest { get { if (SelfTestCfg != null) { return SelfTestCfg.Value; } return false; } } internal static string DataDir { get { try { return Path.Combine(Paths.ConfigPath, "nvlb"); } catch { return "nvlb"; } } } internal static string DataFile(string name) { return Path.Combine(DataDir, name); } internal static void EnsureDataDir() { string dataDir = DataDir; if (!Directory.Exists(dataDir)) { Directory.CreateDirectory(dataDir); } } internal static long NowUnix() { return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; } internal static string PeerKey(ZNetPeer peer) { if (peer == null) { return null; } string text = null; try { text = ((peer.m_socket != null) ? peer.m_socket.GetHostName() : null); } catch { text = null; } if (string.IsNullOrEmpty(text)) { text = "unknown"; } string playerName = peer.m_playerName; if (string.IsNullOrEmpty(playerName)) { return null; } return text + "|" + playerName; } internal static string PeerHost(ZNetPeer peer) { try { return (peer != null && peer.m_socket != null) ? peer.m_socket.GetHostName() : "unknown"; } catch { return "unknown"; } } internal static int ProbRound(float value) { if (value <= 0f) { return 0; } int num = (int)value; float num2 = value - (float)num; if (num2 > 0f && Random.value < num2) { num++; } return num; } internal static string F(float v) { return v.ToString("0.###", CultureInfo.InvariantCulture); } internal static string F(double v) { return v.ToString("0.###", CultureInfo.InvariantCulture); } internal static void AppendString(StringBuilder sb, string s) { sb.Append('"'); foreach (char c in s) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4")); } else { sb.Append(c); } } sb.Append('"'); } internal static object ParseJson(string text) { int i = 0; return ParseValue(text, ref i); } internal static Dictionary AsObj(object o) { return o as Dictionary; } internal static List AsArr(object o) { return o as List; } internal static string Str(Dictionary o, string key, string def) { if (o != null && o.TryGetValue(key, out object value) && value is string) { return (string)value; } return def; } internal static double Num(Dictionary o, string key, double def) { if (o != null && o.TryGetValue(key, out object value) && value is double) { return (double)value; } return def; } private static void SkipWs(string s, ref int i) { while (i < s.Length && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r')) { i++; } } private static object ParseValue(string s, ref int i) { SkipWs(s, ref i); if (i >= s.Length) { throw new Exception("unexpected end of JSON"); } switch (s[i]) { case '{': return ParseObject(s, ref i); case '[': return ParseArray(s, ref i); case '"': return ParseString(s, ref i); default: if (s.Length - i >= 4 && s.Substring(i, 4) == "true") { i += 4; return true; } if (s.Length - i >= 5 && s.Substring(i, 5) == "false") { i += 5; return false; } if (s.Length - i >= 4 && s.Substring(i, 4) == "null") { i += 4; return null; } return ParseNumber(s, ref i); } } private static Dictionary ParseObject(string s, ref int i) { Dictionary dictionary = new Dictionary(); i++; SkipWs(s, ref i); if (i < s.Length && s[i] == '}') { i++; return dictionary; } while (true) { SkipWs(s, ref i); string key = ParseString(s, ref i); SkipWs(s, ref i); if (i >= s.Length || s[i] != ':') { throw new Exception("expected ':' at " + i); } i++; dictionary[key] = ParseValue(s, ref i); SkipWs(s, ref i); if (i >= s.Length) { throw new Exception("unterminated object"); } if (s[i] != ',') { break; } i++; } if (s[i] == '}') { i++; return dictionary; } throw new Exception("expected ',' or '}' at " + i); } private static List ParseArray(string s, ref int i) { List list = new List(); i++; SkipWs(s, ref i); if (i < s.Length && s[i] == ']') { i++; return list; } while (true) { list.Add(ParseValue(s, ref i)); SkipWs(s, ref i); if (i >= s.Length) { throw new Exception("unterminated array"); } if (s[i] != ',') { break; } i++; } if (s[i] == ']') { i++; return list; } throw new Exception("expected ',' or ']' at " + i); } private static string ParseString(string s, ref int i) { if (s[i] != '"') { throw new Exception("expected string at " + i); } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); continue; case '\\': break; } if (i >= s.Length) { break; } char c2 = s[i++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': stringBuilder.Append((char)Convert.ToInt32(s.Substring(i, 4), 16)); i += 4; break; default: stringBuilder.Append(c2); break; } } throw new Exception("unterminated string"); } private static object ParseNumber(string s, ref int i) { int num = i; while (i < s.Length && (char.IsDigit(s[i]) || s[i] == '-' || s[i] == '+' || s[i] == '.' || s[i] == 'e' || s[i] == 'E')) { i++; } if (num == i) { throw new Exception("expected number at " + i); } return double.Parse(s.Substring(num, i - num), CultureInfo.InvariantCulture); } } internal sealed class GroupSkillCatchupModule : FeatureModule { private sealed class Report { public string Key; public string Name; public long When; public Dictionary Levels = new Dictionary(); } internal const string RpcReport = "NVLB_SkillReport"; internal const string RpcCeiling = "NVLB_SkillCeiling"; private static GroupSkillCatchupModule _self; private static object _rpcRegisteredOn; private ConfigEntry _reportSec; private ConfigEntry _windowDays; private ConfigEntry _broadcastSec; private ConfigEntry _bonus; private ConfigEntry _maxFactor; private readonly Dictionary _reports = new Dictionary(); private readonly Dictionary _serverCeiling = new Dictionary(); private float _nextBroadcast; private bool _loggedOnce; private bool _selfTestDone; private static readonly Dictionary _ceiling = new Dictionary(); private float _nextReport; public override string Name => "GroupSkillCatchup"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "SkillCatchup"; protected override string EnabledDescription => "Lift skills that are below the group's best towards it. Capped, and the leaders themselves get nothing."; protected override void Bind() { _reportSec = BindSynced("ReportSec", 60, "Seconds between a client sending its skill levels to the server."); _windowDays = BindSynced("WindowDays", 14, "Only reports newer than this many days count towards the group ceiling."); _broadcastSec = BindSynced("BroadcastSec", 300, "Server re-broadcasts the ceiling at least this often, even when unchanged."); _bonus = BindSynced("Bonus", 1f, "Strength of the catch-up. 1.0 = a skill at half the group ceiling gains 1.5x."); _maxFactor = BindSynced("MaxFactor", 3f, "Hard cap on this module's multiplier, whatever the gap."); if (CatchupUtil.SelfTestCfg == null) { CatchupUtil.SelfTestCfg = BindLocal("Catchup", "SelfTest", defaultValue: false, "LOCAL diagnostic. Seeds fake playtime and skill data once, logs the computed median / factors / ceilings, then does nothing more. Never sync this on."); } } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ZNet.Awake() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(GroupSkillCatchupModule), "ZNetAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "Update", (Type[])null, (Type[])null); if (methodInfo2 == null) { throw new Exception("ZNet.Update() not found"); } Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(GroupSkillCatchupModule), "ZNetUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo3 = AccessTools.Method(typeof(Skills), "RaiseSkill", new Type[2] { typeof(SkillType), typeof(float) }, (Type[])null); if (methodInfo3 == null) { throw new Exception("Skills.RaiseSkill(SkillType, float) not found"); } Harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(GroupSkillCatchupModule), "RaiseSkillPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null); if (methodInfo4 == null) { throw new Exception("ZNet.Disconnect(ZNetPeer) not found"); } Harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(GroupSkillCatchupModule), "DisconnectPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _self = this; } public override void Disable() { _ceiling.Clear(); base.Disable(); } public override void OnConfigChanged(ConfigEntryBase entry) { _nextReport = 0f; _nextBroadcast = 0f; FeatureModule.Log.LogInfo((object)("[SkillCatchup] " + entry.Definition.Key + " = " + entry.BoxedValue)); } public override string StatusDetail() { //IL_017b: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); if (NoVikingLeftBehindPlugin.IsServerSide) { stringBuilder.Append("reports=").Append(_reports.Count).Append(" ceilings=") .Append(_serverCeiling.Count) .Append(" window=") .Append(_windowDays.Value) .Append("d") .Append(" bonus=") .Append(CatchupUtil.F(_bonus.Value)) .Append(" max=x") .Append(CatchupUtil.F(_maxFactor.Value)); } else { stringBuilder.Append("ceilings=").Append(_ceiling.Count).Append(" bonus=") .Append(CatchupUtil.F(_bonus.Value)) .Append(" max=x") .Append(CatchupUtil.F(_maxFactor.Value)); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && _ceiling.Count > 0) { Skills skills = ((Character)localPlayer).GetSkills(); string text = null; float num = 1f; foreach (KeyValuePair item in _ceiling) { float num2 = MultiplierFor(skills.GetSkillLevel((SkillType)item.Key), item.Value); if (num2 > num) { num = num2; text = ((object)(SkillType)item.Key/*cast due to .constrained prefix*/).ToString(); } } if (text != null) { stringBuilder.Append(" biggest gap: ").Append(text).Append(" x") .Append(CatchupUtil.F(num)); } } } return stringBuilder.ToString(); } private static void ZNetAwakePostfix() { if (_self == null || !_self.Active) { return; } try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _rpcRegisteredOn) { instance.Register("NVLB_SkillReport", (Action)RPC_SkillReport); instance.Register("NVLB_SkillCeiling", (Action)RPC_SkillCeiling); _rpcRegisteredOn = instance; _self._reports.Clear(); _self._serverCeiling.Clear(); _ceiling.Clear(); FeatureModule.Log.LogInfo((object)"[SkillCatchup] routed RPCs 'NVLB_SkillReport' / 'NVLB_SkillCeiling' registered"); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[SkillCatchup] RPC register failed: " + ex)); } } private static void ZNetUpdatePostfix() { if (_self == null || !_self.Active) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; try { if (FeatureModule.ServerActive() && realtimeSinceStartup >= _self._nextBroadcast) { _self._nextBroadcast = realtimeSinceStartup + (float)Mathf.Max(10, _self._broadcastSec.Value); _self.RunSelfTestOnce(); _self.RecomputeCeiling(forceBroadcast: true); } if (FeatureModule.ClientActive() && realtimeSinceStartup >= _self._nextReport) { _self._nextReport = realtimeSinceStartup + (float)Mathf.Max(10, _self._reportSec.Value); _self.SendReport(); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[SkillCatchup] tick failed: " + ex)); } } private void SendReport() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Expected I4, but got Unknown Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return; } Skills skills = ((Character)localPlayer).GetSkills(); if ((Object)(object)skills == (Object)null) { return; } ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } List skillList = skills.GetSkillList(); ZPackage val = new ZPackage(); int num = 0; foreach (Skill item in skillList) { if (item != null && item.m_info != null && item.m_level > 0f) { num++; } } val.Write(num); foreach (Skill item2 in skillList) { if (item2 != null && item2.m_info != null && !(item2.m_level <= 0f)) { val.Write((int)item2.m_info.m_skill); val.Write(item2.m_level); } } instance.InvokeRoutedRPC("NVLB_SkillReport", new object[1] { val }); } private static void RPC_SkillReport(long sender, ZPackage pkg) { if (_self == null || !_self.Active || !FeatureModule.ServerActive() || pkg == null) { return; } try { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } ZNetPeer peer = instance.GetPeer(sender); string text; string text2; if (peer != null) { text = CatchupUtil.PeerKey(peer); text2 = peer.m_playerName; } else { Player localPlayer = Player.m_localPlayer; text2 = (((Object)(object)localPlayer != (Object)null) ? localPlayer.GetPlayerName() : "host"); text = "local|" + text2; } if (string.IsNullOrEmpty(text)) { return; } pkg.SetPos(0); int num = pkg.ReadInt(); if (num < 0 || num > 256) { return; } Report report = new Report { Key = text, Name = text2, When = CatchupUtil.NowUnix() }; for (int i = 0; i < num; i++) { int key = pkg.ReadInt(); float num2 = pkg.ReadSingle(); if (!float.IsNaN(num2) && !(num2 <= 0f) && !(num2 > 100f)) { report.Levels[key] = num2; } } _self._reports[text] = report; _self.RecomputeCeiling(forceBroadcast: false); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[SkillCatchup] bad skill report from " + sender + ": " + ex.Message)); } } private void RecomputeCeiling(bool forceBroadcast) { long num = CatchupUtil.NowUnix() - (long)Mathf.Max(1, _windowDays.Value) * 86400L; Dictionary dictionary = new Dictionary(); int num2 = 0; foreach (Report value3 in _reports.Values) { if (value3.When < num) { continue; } num2++; foreach (KeyValuePair level in value3.Levels) { if (!dictionary.TryGetValue(level.Key, out var value) || level.Value > value) { dictionary[level.Key] = level.Value; } } } bool flag = dictionary.Count != _serverCeiling.Count; if (!flag) { foreach (KeyValuePair item in dictionary) { if (!_serverCeiling.TryGetValue(item.Key, out var value2) || Mathf.Abs(value2 - item.Value) > 0.01f) { flag = true; break; } } } if (flag) { _serverCeiling.Clear(); foreach (KeyValuePair item2 in dictionary) { _serverCeiling[item2.Key] = item2.Value; } } if (flag || forceBroadcast) { string text = "[SkillCatchup] ceiling over " + num2 + " report(s) in the last " + _windowDays.Value + "d: " + Describe(_serverCeiling); if (!_loggedOnce) { FeatureModule.Log.LogInfo((object)text); _loggedOnce = true; } else { FeatureModule.Log.LogDebug((object)text); } Broadcast(); } } private void Broadcast() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown ZRoutedRpc instance = ZRoutedRpc.instance; if (instance == null) { return; } ZPackage val = new ZPackage(); val.Write(_serverCeiling.Count); foreach (KeyValuePair item in _serverCeiling) { val.Write(item.Key); val.Write(item.Value); } instance.InvokeRoutedRPC(ZRoutedRpc.Everybody, "NVLB_SkillCeiling", new object[1] { val }); } private static string Describe(Dictionary d) { if (d.Count == 0) { return "(empty)"; } StringBuilder stringBuilder = new StringBuilder(); bool flag = true; foreach (KeyValuePair item in d) { if (!flag) { stringBuilder.Append(", "); } flag = false; stringBuilder.Append((object)(SkillType)item.Key).Append('=').Append(CatchupUtil.F(item.Value)); } return stringBuilder.ToString(); } private static void DisconnectPrefix(ZNetPeer peer) { if (_self != null && _self.Active && !FeatureModule.ServerActive()) { _ceiling.Clear(); } } private static void RPC_SkillCeiling(long sender, ZPackage pkg) { if (_self == null || !_self.Active || !FeatureModule.ClientActive() || pkg == null) { return; } try { pkg.SetPos(0); int num = pkg.ReadInt(); if (num < 0 || num > 256) { return; } _ceiling.Clear(); for (int i = 0; i < num; i++) { int key = pkg.ReadInt(); float num2 = pkg.ReadSingle(); if (!float.IsNaN(num2) && !(num2 <= 0f)) { _ceiling[key] = Mathf.Clamp(num2, 0f, 100f); } } FeatureModule.Log.LogInfo((object)("[SkillCatchup] group ceiling: " + Describe(_ceiling))); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[SkillCatchup] bad ceiling packet: " + ex.Message)); } } private static float MultiplierFor(float level, float ceiling) { if (_self == null) { return 1f; } if (ceiling <= 0f || level >= ceiling) { return 1f; } float num = 1f + Mathf.Max(0f, _self._bonus.Value) * ((ceiling - level) / ceiling); float num2 = Mathf.Max(1f, _self._maxFactor.Value); return Mathf.Clamp(num, 1f, num2); } private static void RaiseSkillPrefix(Skills __instance, SkillType skillType, ref float factor) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Expected I4, but got Unknown //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (_self == null || !_self.Active || !FeatureModule.ClientActive() || (int)skillType == 0 || _ceiling.Count == 0) { return; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && !((Object)(object)__instance == (Object)null) && __instance.m_player == localPlayer && _ceiling.TryGetValue((int)skillType, out var value)) { float num = MultiplierFor(__instance.GetSkillLevel(skillType), value); if (num > 1.0001f) { factor *= num; } } } private unsafe void RunSelfTestOnce() { //IL_0128: Unknown result type (might be due to invalid IL or missing references) if (_selfTestDone || !CatchupUtil.SelfTest) { return; } _selfTestDone = true; try { long when = CatchupUtil.NowUnix(); _reports.Clear(); AddFake("Alfr", when, 60f, 45f, 30f); AddFake("Bjorn", when, 40f, 55f, 20f); AddFake("Dagny", when, 12f, 8f, 35f); RecomputeCeiling(forceBroadcast: true); FeatureModule.Log.LogInfo((object)("[Catchup SelfTest] skills: " + _reports.Count + " seeded reports, ceiling = " + Describe(_serverCeiling) + " (Bonus=" + CatchupUtil.F(_bonus.Value) + " MaxFactor=" + CatchupUtil.F(_maxFactor.Value) + ")")); foreach (KeyValuePair item in _serverCeiling) { SkillType val = (SkillType)item.Key; FeatureModule.Log.LogInfo((object)("[Catchup SelfTest] " + ((object)(*(SkillType*)(&val))/*cast due to .constrained prefix*/).ToString() + ": ceiling " + CatchupUtil.F(item.Value) + " -> Dagny(" + CatchupUtil.F(_reports["fake|Dagny"].Levels[item.Key]) + ") x" + CatchupUtil.F(MultiplierFor(_reports["fake|Dagny"].Levels[item.Key], item.Value)) + ", Alfr(" + CatchupUtil.F(_reports["fake|Alfr"].Levels[item.Key]) + ") x" + CatchupUtil.F(MultiplierFor(_reports["fake|Alfr"].Levels[item.Key], item.Value)))); } _reports.Clear(); _serverCeiling.Clear(); FeatureModule.Log.LogInfo((object)"[Catchup SelfTest] skill self test done, fake reports discarded"); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Catchup SelfTest] skill self test failed: " + ex)); } } private void AddFake(string name, long when, float axes, float blocking, float woodcutting) { Report report = new Report { Key = "fake|" + name, Name = name, When = when }; report.Levels[7] = axes; report.Levels[6] = blocking; report.Levels[13] = woodcutting; _reports[report.Key] = report; } } internal sealed class PlaytimeRubberBandModule : FeatureModule { private sealed class Rec { public string Key; public string SteamId; public string Name; public double Seconds; public long LastSeen; public double Hours => Seconds / 3600.0; } internal const string RpcCatchup = "NVLB_Catchup"; private const string StateFile = "playtime.json"; private static PlaytimeRubberBandModule _self; private ConfigEntry _recomputeSec; private ConfigEntry _windowDays; private ConfigEntry _minGroupSize; private ConfigEntry _maxBonus; private ConfigEntry _gatherBonusEnabled; private ConfigEntry _xpBonusEnabled; private readonly Dictionary _players = new Dictionary(); private readonly Dictionary _accrualMark = new Dictionary(); private readonly Dictionary> _sent = new Dictionary>(); private bool _loaded; private bool _dirty; private float _nextTick; private bool _loggedOnce; private double _lastMedian = -1.0; private bool _selfTestDone; private static float _gatherFactor = 1f; private static float _xpFactor = 1f; private static object _rpcRegisteredOn; public override string Name => "PlaytimeRubberBand"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "Playtime"; protected override string EnabledDescription => "Give players with less connected time than the group median a small, capped bonus to gathering and skill XP. Never slows anyone down."; protected override void Bind() { _recomputeSec = BindSynced("RecomputeSec", 60, "Seconds between playtime accrual + recompute passes on the server."); _windowDays = BindSynced("WindowDays", 14, "Only players seen within this many days count towards the group median."); _minGroupSize = BindSynced("MinGroupSize", 3, "Below this many players in the window, no bonus is handed out at all."); _maxBonus = BindSynced("MaxBonus", 1f, "Maximum bonus. 1.0 = at most double rate for the furthest-behind player."); _gatherBonusEnabled = BindSynced("GatherBonusEnabled", defaultValue: true, "Apply the catch-up factor to item drops the client produces (ore, wood, loot)."); _xpBonusEnabled = BindSynced("XpBonusEnabled", defaultValue: true, "Apply the catch-up factor to skill XP gain."); if (CatchupUtil.SelfTestCfg == null) { CatchupUtil.SelfTestCfg = BindLocal("Catchup", "SelfTest", defaultValue: false, "LOCAL diagnostic. Seeds fake playtime and skill data once, logs the computed median / factors / ceilings, then does nothing more. Never sync this on."); } } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0104: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Expected O, but got Unknown //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e7: Expected O, but got Unknown //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Expected O, but got Unknown //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ZNet.Awake() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "ZNetAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(ZNet), "Update", (Type[])null, (Type[])null); if (methodInfo2 == null) { throw new Exception("ZNet.Update() not found"); } Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "ZNetUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo3 = AccessTools.Method(typeof(ZNet), "RPC_PeerInfo", new Type[2] { typeof(ZRpc), typeof(ZPackage) }, (Type[])null); if (methodInfo3 == null) { throw new Exception("ZNet.RPC_PeerInfo(ZRpc, ZPackage) not found"); } Harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "PeerInfoPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo4 = AccessTools.Method(typeof(ZNet), "Disconnect", new Type[1] { typeof(ZNetPeer) }, (Type[])null); if (methodInfo4 == null) { throw new Exception("ZNet.Disconnect(ZNetPeer) not found"); } Harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "DisconnectPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo5 = AccessTools.Method(typeof(Skills), "RaiseSkill", new Type[2] { typeof(SkillType), typeof(float) }, (Type[])null); if (methodInfo5 == null) { throw new Exception("Skills.RaiseSkill(SkillType, float) not found"); } Harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "RaiseSkillPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo6 = AccessTools.Method(typeof(DropTable), "GetDropList", Type.EmptyTypes, (Type[])null); if (methodInfo6 == null) { throw new Exception("DropTable.GetDropList() not found"); } Harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "GetDropListPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo7 = AccessTools.Method(typeof(CharacterDrop), "GenerateDropList", Type.EmptyTypes, (Type[])null); if (methodInfo7 == null) { throw new Exception("CharacterDrop.GenerateDropList() not found"); } Harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(typeof(PlaytimeRubberBandModule), "GenerateDropListPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _self = this; } public override void Disable() { try { if (_self == this && _dirty) { Save(); } } catch { } _gatherFactor = 1f; _xpFactor = 1f; base.Disable(); } public override void OnConfigChanged(ConfigEntryBase entry) { _nextTick = 0f; FeatureModule.Log.LogInfo((object)("[Playtime] " + entry.Definition.Key + " = " + entry.BoxedValue)); } public override string StatusDetail() { StringBuilder stringBuilder = new StringBuilder(); if (NoVikingLeftBehindPlugin.IsServerSide) { stringBuilder.Append("tracked=").Append(_players.Count); stringBuilder.Append(" window=").Append(_windowDays.Value).Append("d"); stringBuilder.Append(" minGroup=").Append(_minGroupSize.Value); stringBuilder.Append(" maxBonus=").Append(CatchupUtil.F(_maxBonus.Value)); if (_lastMedian >= 0.0) { stringBuilder.Append(" median=").Append(CatchupUtil.F(_lastMedian)).Append("h"); } } else { stringBuilder.Append("gather=x").Append(CatchupUtil.F(_gatherFactor)); stringBuilder.Append(" xp=x").Append(CatchupUtil.F(_xpFactor)); if (!_gatherBonusEnabled.Value) { stringBuilder.Append(" (gather off)"); } if (!_xpBonusEnabled.Value) { stringBuilder.Append(" (xp off)"); } } return stringBuilder.ToString(); } private static void ZNetAwakePostfix() { if (_self == null || !_self.Active) { return; } try { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _rpcRegisteredOn) { instance.Register("NVLB_Catchup", (Action)RPC_Catchup); _rpcRegisteredOn = instance; _self._loaded = false; _self._players.Clear(); _self._accrualMark.Clear(); _self._sent.Clear(); _gatherFactor = 1f; _xpFactor = 1f; FeatureModule.Log.LogInfo((object)"[Playtime] routed RPC 'NVLB_Catchup' registered"); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Playtime] RPC register failed: " + ex)); } } private static void RPC_Catchup(long sender, float gather, float xp) { if (_self != null && _self.Active && !float.IsNaN(gather) && !float.IsNaN(xp)) { _gatherFactor = Mathf.Clamp(gather, 1f, 10f); _xpFactor = Mathf.Clamp(xp, 1f, 10f); FeatureModule.Log.LogInfo((object)("[Playtime] catch-up factors from server: gather=x" + CatchupUtil.F(_gatherFactor) + " xp=x" + CatchupUtil.F(_xpFactor))); } } private static void PeerInfoPostfix(ZNet __instance, ZRpc rpc) { if (_self == null || !_self.Active || !FeatureModule.ServerActive()) { return; } try { ZNetPeer peer = __instance.GetPeer(rpc); if (peer != null && peer.IsReady()) { string text = CatchupUtil.PeerKey(peer); if (text != null) { _self.EnsureLoaded(); Rec rec = _self.Get(text, CatchupUtil.PeerHost(peer), peer.m_playerName); rec.LastSeen = CatchupUtil.NowUnix(); _self._accrualMark[peer.m_uid] = rec.LastSeen; _self._dirty = true; FeatureModule.Log.LogInfo((object)("[Playtime] " + peer.m_playerName + " joined (" + CatchupUtil.F(rec.Hours) + " h tracked)")); _self._nextTick = 0f; } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Playtime] join hook failed: " + ex.Message)); } } private static void DisconnectPrefix(ZNetPeer peer) { if (_self == null || !_self.Active || !FeatureModule.ServerActive()) { return; } try { if (peer != null) { _self.Accrue(peer); _self._accrualMark.Remove(peer.m_uid); _self._sent.Remove(peer.m_uid); if (_self._dirty) { _self.Save(); } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Playtime] leave hook failed: " + ex.Message)); } } private static void ZNetUpdatePostfix() { if (_self == null || !_self.Active || !FeatureModule.ServerActive()) { return; } try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!(realtimeSinceStartup < _self._nextTick)) { int num = Mathf.Max(5, _self._recomputeSec.Value); _self._nextTick = realtimeSinceStartup + (float)num; _self.EnsureLoaded(); _self.RunSelfTestOnce(); _self.AccrueAll(); _self.Recompute(); if (_self._dirty) { _self.Save(); } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Playtime] tick failed: " + ex)); } } private Rec Get(string key, string steamId, string name) { if (!_players.TryGetValue(key, out Rec value)) { value = new Rec { Key = key, SteamId = steamId, Name = name, Seconds = 0.0, LastSeen = CatchupUtil.NowUnix() }; _players[key] = value; } return value; } private void AccrueAll() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { Accrue(connectedPeer); } } private void Accrue(ZNetPeer peer) { if (peer == null || !peer.IsReady()) { return; } string text = CatchupUtil.PeerKey(peer); if (text != null) { long num = CatchupUtil.NowUnix(); if (!_accrualMark.TryGetValue(peer.m_uid, out var value)) { value = num; } long num2 = num - value; _accrualMark[peer.m_uid] = num; Rec rec = Get(text, CatchupUtil.PeerHost(peer), peer.m_playerName); rec.LastSeen = num; if (num2 > 0 && num2 < 86400) { rec.Seconds += num2; _dirty = true; } } } private void Recompute() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return; } long num = CatchupUtil.NowUnix() - (long)Mathf.Max(1, _windowDays.Value) * 86400L; List list = new List(); foreach (Rec value2 in _players.Values) { if (value2.LastSeen >= num) { list.Add(value2); } } bool flag = !_loggedOnce; _loggedOnce = true; if (list.Count < Mathf.Max(1, _minGroupSize.Value)) { _lastMedian = -1.0; string text = "[Playtime] group too small (" + list.Count + " < " + _minGroupSize.Value + " in the last " + _windowDays.Value + "d), no bonus"; if (flag) { FeatureModule.Log.LogInfo((object)text); } else { FeatureModule.Log.LogDebug((object)text); } { foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { Push(connectedPeer, 1f, 1f); } return; } } double num2 = (_lastMedian = Median(list)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[Playtime] median=").Append(CatchupUtil.F(num2)).Append("h over ") .Append(list.Count) .Append(" players"); foreach (ZNetPeer connectedPeer2 in instance.GetConnectedPeers()) { if (connectedPeer2.IsReady()) { string text2 = CatchupUtil.PeerKey(connectedPeer2); if (text2 != null && _players.TryGetValue(text2, out Rec value)) { float num3 = FactorFor(value.Hours, num2); stringBuilder.Append("; ").Append(value.Name).Append("=") .Append(CatchupUtil.F(value.Hours)) .Append("h x") .Append(CatchupUtil.F(num3)); Push(connectedPeer2, _gatherBonusEnabled.Value ? num3 : 1f, _xpBonusEnabled.Value ? num3 : 1f); } } } if (flag) { FeatureModule.Log.LogInfo((object)stringBuilder.ToString()); } else { FeatureModule.Log.LogDebug((object)stringBuilder.ToString()); } } private float FactorFor(double hours, double median) { if (median <= 0.0 || hours >= median) { return 1f; } double num = Mathf.Max(0f, _maxBonus.Value); double num2 = (median - hours) / median * num; if (num2 > num) { num2 = num; } if (num2 < 0.0) { num2 = 0.0; } return (float)(1.0 + num2); } private static double Median(List recs) { List list = new List(recs.Count); foreach (Rec rec in recs) { list.Add(rec.Hours); } list.Sort(); int count = list.Count; if (count == 0) { return 0.0; } if (count % 2 != 1) { return (list[count / 2 - 1] + list[count / 2]) / 2.0; } return list[count / 2]; } private void Push(ZNetPeer peer, float gather, float xp) { if (peer != null && peer.IsReady() && (!_sent.TryGetValue(peer.m_uid, out var value) || !(Mathf.Abs(value.Key - gather) < 0.001f) || !(Mathf.Abs(value.Value - xp) < 0.001f))) { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { instance.InvokeRoutedRPC(peer.m_uid, "NVLB_Catchup", new object[2] { gather, xp }); _sent[peer.m_uid] = new KeyValuePair(gather, xp); } } } private void EnsureLoaded() { if (_loaded) { return; } _loaded = true; string text = CatchupUtil.DataFile("playtime.json"); try { if (!File.Exists(text)) { CatchupUtil.EnsureDataDir(); Save(); FeatureModule.Log.LogInfo((object)("[Playtime] created " + text)); } else { Load(File.ReadAllText(text)); FeatureModule.Log.LogInfo((object)("[Playtime] loaded " + _players.Count + " player record(s) from " + text)); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Playtime] could not read " + text + ": " + ex.Message + " - starting empty")); } } private void Load(string json) { _players.Clear(); Dictionary dictionary = CatchupUtil.AsObj(CatchupUtil.ParseJson(json)); if (dictionary == null || !dictionary.TryGetValue("players", out var value)) { return; } List list = CatchupUtil.AsArr(value); if (list == null) { return; } foreach (object item in list) { Dictionary dictionary2 = CatchupUtil.AsObj(item); if (dictionary2 != null) { Rec rec = new Rec { SteamId = CatchupUtil.Str(dictionary2, "steamId", "unknown"), Name = CatchupUtil.Str(dictionary2, "name", ""), Seconds = CatchupUtil.Num(dictionary2, "seconds", 0.0), LastSeen = (long)CatchupUtil.Num(dictionary2, "lastSeen", 0.0) }; rec.Key = CatchupUtil.Str(dictionary2, "key", rec.SteamId + "|" + rec.Name); if (!string.IsNullOrEmpty(rec.Key)) { _players[rec.Key] = rec; } } } } private void Save() { string text = CatchupUtil.DataFile("playtime.json"); try { CatchupUtil.EnsureDataDir(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n \"version\": 1,\n \"savedUnix\": ").Append(CatchupUtil.NowUnix()); stringBuilder.Append(",\n \"players\": ["); bool flag = true; foreach (Rec value in _players.Values) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append("\n {\"key\": "); CatchupUtil.AppendString(stringBuilder, value.Key); stringBuilder.Append(", \"steamId\": "); CatchupUtil.AppendString(stringBuilder, value.SteamId ?? "unknown"); stringBuilder.Append(", \"name\": "); CatchupUtil.AppendString(stringBuilder, value.Name ?? ""); stringBuilder.Append(", \"seconds\": ").Append(value.Seconds.ToString("0.###", CultureInfo.InvariantCulture)); stringBuilder.Append(", \"lastSeen\": ").Append(value.LastSeen); stringBuilder.Append('}'); } stringBuilder.Append("\n ]\n}\n"); string text2 = text + ".tmp"; File.WriteAllText(text2, stringBuilder.ToString()); if (File.Exists(text)) { File.Delete(text); } File.Move(text2, text); _dirty = false; } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Playtime] could not write " + text + ": " + ex.Message)); } } private static bool LocalSkills(Skills s) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && (Object)(object)s != (Object)null) { return s.m_player == localPlayer; } return false; } private static void RaiseSkillPrefix(Skills __instance, SkillType skillType, ref float factor) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (_self != null && _self.Active && FeatureModule.ClientActive() && _self._xpBonusEnabled.Value && !(_xpFactor <= 1.0001f) && (int)skillType != 0 && LocalSkills(__instance)) { factor *= _xpFactor; } } private static bool GatherOn() { if (_self != null && _self.Active && FeatureModule.ClientActive() && _self._gatherBonusEnabled.Value && _gatherFactor > 1.0001f) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } private static void GetDropListPostfix(List __result) { if (__result == null || __result.Count == 0 || !GatherOn()) { return; } try { int count = __result.Count; int num = CatchupUtil.ProbRound((float)count * _gatherFactor); for (int i = count; i < num; i++) { __result.Add(__result[(i - count) % count]); } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Playtime] drop scale failed: " + ex.Message)); } } private static void GenerateDropListPostfix(List> __result) { if (__result == null || __result.Count == 0 || !GatherOn()) { return; } try { for (int i = 0; i < __result.Count; i++) { KeyValuePair keyValuePair = __result[i]; if (keyValuePair.Value > 0) { int num = CatchupUtil.ProbRound((float)keyValuePair.Value * _gatherFactor); if (num < keyValuePair.Value) { num = keyValuePair.Value; } __result[i] = new KeyValuePair(keyValuePair.Key, num); } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Playtime] creature drop scale failed: " + ex.Message)); } } private void RunSelfTestOnce() { if (_selfTestDone || !CatchupUtil.SelfTest) { return; } _selfTestDone = true; try { long lastSeen = CatchupUtil.NowUnix(); double[] array = new double[4] { 40.0, 35.0, 30.0, 5.0 }; string[] array2 = new string[4] { "Alfr", "Bjorn", "Cato", "Dagny" }; _players.Clear(); for (int i = 0; i < array.Length; i++) { string key = "76561198000000" + (10 + i) + "|" + array2[i]; _players[key] = new Rec { Key = key, SteamId = "76561198000000" + (10 + i), Name = array2[i], Seconds = array[i] * 3600.0, LastSeen = lastSeen }; } _dirty = true; Save(); List list = new List(_players.Values); double num = Median(list); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[Catchup SelfTest] playtime: ").Append(list.Count).Append(" seeded players, median=") .Append(CatchupUtil.F(num)) .Append("h, MinGroupSize=") .Append(_minGroupSize.Value) .Append(", MaxBonus=") .Append(CatchupUtil.F(_maxBonus.Value)); FeatureModule.Log.LogInfo((object)stringBuilder.ToString()); foreach (Rec item in list) { FeatureModule.Log.LogInfo((object)("[Catchup SelfTest] " + item.Name + ": " + CatchupUtil.F(item.Hours) + "h -> gather x" + CatchupUtil.F(FactorFor(item.Hours, num)) + " xp x" + CatchupUtil.F(FactorFor(item.Hours, num)))); } FeatureModule.Log.LogInfo((object)("[Catchup SelfTest] wrote " + CatchupUtil.DataFile("playtime.json") + " - set [Catchup] SelfTest = false and delete that file when done")); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Catchup SelfTest] playtime self test failed: " + ex)); } } } internal struct Box { public Container C; public Inventory Inv; public string Label; public static Box Of(Container c, string prefabName) { return new Box { C = c, Inv = c.GetInventory(), Label = prefabName }; } public static Box Of(Inventory inv, string label) { return new Box { C = null, Inv = inv, Label = label }; } } internal static class ChestSource { private sealed class Reg { public string Prefab; public bool Vehicle; public bool Skip; } internal static float Range = 20f; internal static bool LeaveOne; internal static bool IncludeVehicles = true; internal static HashSet ExcludedContainers = new HashSet(StringComparer.OrdinalIgnoreCase); internal static HashSet ExcludedItems = new HashSet(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _all = new Dictionary(); private static readonly List _dead = new List(); private static readonly List _cached = new List(64); private static readonly List _empty = new List(0); private static int _cacheFrame = -1; private static Vector3 _cachePos; internal static int Registered => _all.Count; internal static void Register(Container c) { if (!((Object)(object)c == (Object)null) && !_all.ContainsKey(c)) { Reg reg = new Reg(); try { reg.Prefab = Utils.GetPrefabName(((Component)c).gameObject); reg.Vehicle = (Object)(object)c.m_wagon != (Object)null || (Object)(object)((Component)c).GetComponentInParent() != (Object)null; reg.Skip = (Object)(object)((Component)c).GetComponentInParent() != (Object)null; } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Chests] could not classify a container: " + ex.Message)); reg.Prefab = ""; reg.Skip = true; } _all[c] = reg; } } internal static void Unregister(Container c) { if (!((Object)(object)c == (Object)null)) { _all.Remove(c); _cacheFrame = -1; } } internal static void Clear() { _all.Clear(); _cached.Clear(); _cacheFrame = -1; } internal static List Nearby(Vector3 pos) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: 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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) int frameCount = Time.frameCount; Vector3 val; if (frameCount == _cacheFrame) { val = pos - _cachePos; if (((Vector3)(ref val)).sqrMagnitude < 0.0625f) { return _cached; } } _cacheFrame = frameCount; _cachePos = pos; _cached.Clear(); long num = 0L; try { num = Game.instance.GetPlayerProfile().GetPlayerID(); } catch { return _empty; } float num2 = Range * Range; foreach (KeyValuePair item in _all) { Container key = item.Key; if ((Object)(object)key == (Object)null) { _dead.Add(key); continue; } Reg value = item.Value; if (value.Skip || (!IncludeVehicles && value.Vehicle) || ExcludedContainers.Contains(value.Prefab)) { continue; } try { val = ((Component)key).transform.position - pos; if (!(((Vector3)(ref val)).sqrMagnitude > num2)) { ZNetView nview = key.m_nview; if (!((Object)(object)nview == (Object)null) && nview.IsValid() && key.GetInventory() != null && nview.GetZDO().GetLong(StringExtensionMethods.GetStableHashCode("creator"), 0L) != 0L && (!key.IsInUse() || key.IsOwner()) && (!((Object)(object)key.m_wagon != (Object)null) || !key.m_wagon.InUse()) && key.CheckAccess(num) && (!key.m_checkGuardStone || PrivateArea.CheckAccess(((Component)key).transform.position, 0f, false, false))) { _cached.Add(Box.Of(key, value.Prefab)); } } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Chests] skipping a container: " + ex.Message)); } } if (_dead.Count > 0) { foreach (Container item2 in _dead) { _all.Remove(item2); } _dead.Clear(); } return _cached; } internal static bool ItemBlocked(string prefabName, string sharedName) { if (ExcludedItems.Count == 0) { return false; } if (!string.IsNullOrEmpty(prefabName) && ExcludedItems.Contains(prefabName)) { return true; } if (!string.IsNullOrEmpty(sharedName) && ExcludedItems.Contains(sharedName)) { return true; } return false; } internal static int Count(string sharedName, List boxes, int quality = -1) { if (boxes == null) { return 0; } int num = 0; for (int i = 0; i < boxes.Count; i++) { Inventory inv = boxes[i].Inv; if (inv != null) { int num2 = inv.CountItems(sharedName, quality, true); if (LeaveOne) { num2--; } if (num2 > 0) { num += num2; } } } return num; } internal static int Consume(string sharedName, int amount, int itemQuality, List boxes) { if (boxes == null || amount <= 0) { return 0; } int num = amount; int num2 = 0; for (int i = 0; i < boxes.Count && num > 0; i++) { Box b = boxes[i]; Inventory inv = b.Inv; if (inv == null) { continue; } int num3 = inv.CountItems(sharedName, itemQuality, true); if (LeaveOne) { num3--; } if (num3 <= 0) { continue; } int num4 = Math.Min(num, num3); if (num4 <= 0) { continue; } if (!Claim(b)) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Chests] could not take ownership of " + b.Label + " - skipping it rather than risking a duplicate")); continue; } int num5 = inv.CountItems(sharedName, itemQuality, true); try { inv.RemoveItem(sharedName, num4, itemQuality, true); } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Chests] RemoveItem failed on " + b.Label + ": " + ex.Message)); continue; } int num6 = num5 - inv.CountItems(sharedName, itemQuality, true); if (num6 > 0) { Commit(b); num2 += num6; num -= num6; } } return num2; } private static bool Claim(Box b) { if ((Object)(object)b.C == (Object)null) { return true; } ZNetView nview = b.C.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return false; } if (!nview.IsOwner()) { nview.ClaimOwnership(); } return nview.IsOwner(); } private static void Commit(Box b) { try { if ((Object)(object)b.C != (Object)null) { b.C.Save(); } if (b.Inv != null) { b.Inv.Changed(); } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Chests] could not save " + b.Label + ": " + ex.Message)); } } internal static HashSet ParseNames(string csv) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (string.IsNullOrEmpty(csv)) { return hashSet; } string[] array = csv.Split(',', ';', ' ', '\t', '\n', '\r'); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { hashSet.Add(text); } } return hashSet; } } internal sealed class ChestsSelfTestModule : FeatureModule { private static ConfigEntry _selfTest; private static bool _ran; public override string Name => "ChestsSelfTest"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "ChestsSelfTest"; protected override void Bind() { _selfTest = BindLocal("Chests", "SelfTest", defaultValue: false, "Diagnostic. Once per world load, log the CraftFromChests station toggles, which CookingStation prefabs this build actually has, and a unit run of the counting/consuming core against inventories this module creates itself. Machine-local and never synced, so turning it on for a server boot affects no client. Leave it false in normal use."); } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ZoneSystem.Start() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(ChestsSelfTestModule), "WorldReady", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void WorldReady() { if (_ran || _selfTest == null || !_selfTest.Value) { return; } _ran = true; try { string[] array = Run().Split(new char[1] { '\n' }); foreach (string text in array) { FeatureModule.Log.LogInfo((object)text); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[ChestsSelfTest] threw: " + ex)); } } internal static string Run() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[SelfTest][Chests] --- begin ---"); stringBuilder.Append("\n toggles: ").Append(CraftFromChestsModule.Numbers()); stringBuilder.Append("\n PullForCookingStations=").Append(CraftFromChestsModule.PullCooking).Append(" -> cooking stations take the ") .Append(CraftFromChestsModule.PullCooking ? "PULL" : "NO-PULL") .Append(" path (both the cookable item and the station's own fuel)"); ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || instance.m_prefabs == null) { stringBuilder.Append("\n ZNetScene has no prefabs - cannot enumerate cooking stations"); } else { List list = new List(); foreach (GameObject prefab2 in instance.m_prefabs) { if (!((Object)(object)prefab2 == (Object)null)) { CookingStation component = prefab2.GetComponent(); if (!((Object)(object)component == (Object)null)) { string text = (((Object)(object)component.m_fuelItem != (Object)null) ? ((Object)component.m_fuelItem).name : "none"); list.Add(((Object)prefab2).name + " (fuel=" + text + ", conversions=" + ((component.m_conversion != null) ? component.m_conversion.Count : 0) + ")"); } } } stringBuilder.Append("\n CookingStation prefabs in ZNetScene: ").Append(list.Count); foreach (string item in list) { stringBuilder.Append("\n ").Append(item).Append(" -> ") .Append(CraftFromChestsModule.PullCooking ? "pull" : "NO PULL (default)"); } string[] array = new string[3] { "piece_cookingstation", "piece_cookingstation_iron", "piece_oven" }; foreach (string text2 in array) { GameObject prefab = instance.GetPrefab(text2); bool flag = (Object)(object)prefab != (Object)null && (Object)(object)prefab.GetComponent() != (Object)null; stringBuilder.Append("\n expected ").Append(text2).Append(": ") .Append(((Object)(object)prefab == (Object)null) ? "MISSING from ZNetScene" : (flag ? "present, CookingStation -> gated by PullForCookingStations" : "present but NOT a CookingStation")); } int num = 0; int num2 = 0; foreach (GameObject prefab3 in instance.m_prefabs) { if (!((Object)(object)prefab3 == (Object)null)) { if ((Object)(object)prefab3.GetComponent() != (Object)null) { num++; } if ((Object)(object)prefab3.GetComponent() != (Object)null) { num2++; } } } stringBuilder.Append("\n for contrast: ").Append(num).Append(" Smelter prefabs (PullForSmelters) and ") .Append(num2) .Append(" Fireplace prefabs (PullForFires)"); } stringBuilder.Append("\n core: ").Append(CoreTest()); stringBuilder.Append("\n[SelfTest][Chests] --- end ---"); return stringBuilder.ToString(); } private static string CoreTest() { //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_0227: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Expected O, but got Unknown ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return "ObjectDB not ready - skipped"; } GameObject itemPrefab = instance.GetItemPrefab("Wood"); GameObject itemPrefab2 = instance.GetItemPrefab("RawMeat"); if ((Object)(object)itemPrefab == (Object)null) { return "no Wood prefab in ObjectDB - skipped"; } string name = itemPrefab.GetComponent().m_itemData.m_shared.m_name; string text = (((Object)(object)itemPrefab2 != (Object)null) ? itemPrefab2.GetComponent().m_itemData.m_shared.m_name : null); bool leaveOne = ChestSource.LeaveOne; StringBuilder stringBuilder = new StringBuilder(); try { ChestSource.LeaveOne = false; Inventory val = new Inventory("selftest-player", (Sprite)null, 4, 4); Inventory val2 = new Inventory("selftest-chest", (Sprite)null, 4, 4); val.AddItem(itemPrefab, 10); val2.AddItem(itemPrefab, 20); if ((Object)(object)itemPrefab2 != (Object)null) { val2.AddItem(itemPrefab2, 6); } List boxes = new List { Box.Of(val2, "selftest-chest") }; int num = val.CountItems(name, -1, true); int num2 = ChestSource.Count(name, boxes); stringBuilder.Append("Count(Wood) player=").Append(num).Append(" containers=") .Append(num2) .Append(" total=") .Append(num + num2); if (text != null) { stringBuilder.Append("; Count(RawMeat) containers=").Append(ChestSource.Count(text, boxes)); } int num3 = Mathf.Min(15, val.CountItems(name, -1, true)); val.RemoveItem(name, num3, -1, true); int amount = 15 - num3; int num4 = ChestSource.Consume(name, amount, -1, boxes); stringBuilder.Append("\n Consume(Wood,15): player gave ").Append(num3).Append(", containers gave ") .Append(num4) .Append(" -> player=") .Append(val.CountItems(name, -1, true)) .Append(" container=") .Append(val2.CountItems(name, -1, true)) .Append((num3 == 10 && num4 == 5 && val.CountItems(name, -1, true) == 0 && val2.CountItems(name, -1, true) == 15) ? " PASS (expected 0 / 15)" : " FAIL"); ChestSource.LeaveOne = true; Inventory val3 = new Inventory("selftest-chest2", (Sprite)null, 4, 4); val3.AddItem(itemPrefab, 5); List boxes2 = new List { Box.Of(val3, "selftest-chest2") }; int num5 = ChestSource.Count(name, boxes2); int num6 = ChestSource.Consume(name, 10, -1, boxes2); stringBuilder.Append("\n LeaveOneItem=true, container has 5: Count=").Append(num5).Append(", Consume(Wood,10) took ") .Append(num6) .Append(" -> container=") .Append(val3.CountItems(name, -1, true)) .Append((num5 == 4 && num6 == 4 && val3.CountItems(name, -1, true) == 1) ? " PASS (keeps 1)" : " FAIL"); } catch (Exception ex) { stringBuilder.Append(" EXCEPTION: ").Append(ex.Message); } finally { ChestSource.LeaveOne = leaveOne; } return stringBuilder.ToString(); } public override string StatusDetail() { return "SelfTest=" + (_selfTest != null && _selfTest.Value) + (_ran ? " (already run)" : ""); } } internal sealed class CraftFromChestsModule : FeatureModule { private static CraftFromChestsModule _self; private static ConfigEntry _range; private static ConfigEntry _pullCrafting; private static ConfigEntry _pullBuilding; private static ConfigEntry _pullSmelters; private static ConfigEntry _pullFires; private static ConfigEntry _pullCooking; private static ConfigEntry _leaveOne; private static ConfigEntry _includeVehicles; private static ConfigEntry _excludedContainers; private static ConfigEntry _excludedItems; private static ConfigEntry _showNearbyCount; private static ConfigEntry _toggleKey; private static bool _userOn = true; private static KeyCode _keyMain = (KeyCode)0; private static KeyCode[] _keyMods = (KeyCode[])(object)new KeyCode[0]; public override string Name => "CraftFromChests"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Chests"; internal static bool PullCooking { get { if (_pullCooking != null) { return _pullCooking.Value; } return false; } } private static bool Live() { if (_self != null && _self.Active && FeatureModule.ClientActive() && _userOn) { return (Object)(object)Player.m_localPlayer != (Object)null; } return false; } protected override void Bind() { _self = this; _range = BindSynced("Range", 20f, "How far a container may be from the player and still count, in metres."); _pullCrafting = BindSynced("PullForCrafting", defaultValue: true, "Recipes in the crafting / forge / workbench GUI, upgrades included, may take their materials from nearby containers."); _pullBuilding = BindSynced("PullForBuilding", defaultValue: true, "Build pieces placed with the hammer (and the hoe/cultivator) may take their materials from nearby containers."); _pullSmelters = BindSynced("PullForSmelters", defaultValue: true, "Smelter, blast furnace, charcoal kiln, windmill, spinning wheel: interacting with one may pull its ore and its fuel from nearby containers."); _pullFires = BindSynced("PullForFires", defaultValue: true, "Fireplaces, hearths, bonfires and standing torches may pull their fuel from nearby containers."); _pullCooking = BindSynced("PullForCookingStations", defaultValue: false, "COOKING STATIONS: meat racks, the iron cooking station and the oven. FALSE by default on purpose - the group keeps raw meat for recipes, and an auto-feeding rack empties the chests. Covers BOTH the cookable item and the station's fuel."); _leaveOne = BindSynced("LeaveOneItem", defaultValue: false, "Always leave one of an item behind in a container instead of emptying the stack. Useful if you sort chests by what is in them."); _includeVehicles = BindSynced("IncludeVehicles", defaultValue: true, "Count the cargo of carts and ships as nearby containers. A cart currently being pulled is skipped either way."); _excludedContainers = BindSynced("ExcludedContainers", "piece_chest_private", "Container PREFAB names that are never pulled from, comma-separated. Example: piece_chest_private, piece_chest_wood"); _excludedItems = BindSynced("ExcludedItems", "", "Item prefab names that are never pulled out of a container, comma-separated. They still count from the player's own inventory. Example: FineWood, Coins"); _showNearbyCount = BindSynced("ShowNearbyCount", defaultValue: true, "Show the requirement rows in the crafting and build UI as have/needed, where 'have' includes nearby containers, instead of just the required number."); _toggleKey = BindLocal("ToggleKey", "LeftAlt+O", "MACHINE-LOCAL. Key combination that turns this player's own container pulling on and off, with a HUD message. Format: optional modifiers then the key, joined by '+', using Unity KeyCode names - LeftAlt+O, LeftControl+LeftShift+K, F7, or None to disable the hotkey. A modifier is recommended so it cannot fire while you are typing in chat."); PushSettings(); } private static void PushSettings() { ChestSource.Range = Mathf.Max(0f, _range.Value); ChestSource.LeaveOne = _leaveOne.Value; ChestSource.IncludeVehicles = _includeVehicles.Value; ChestSource.ExcludedContainers = ChestSource.ParseNames(_excludedContainers.Value); ChestSource.ExcludedItems = ChestSource.ParseNames(_excludedItems.Value); ParseKey(_toggleKey.Value); } private static void ParseKey(string spec) { //IL_0001: 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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) _keyMain = (KeyCode)0; _keyMods = (KeyCode[])(object)new KeyCode[0]; if (string.IsNullOrEmpty(spec)) { return; } string[] array = spec.Split(new char[1] { '+' }); List list = new List(); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { KeyCode val; try { val = (KeyCode)Enum.Parse(typeof(KeyCode), text, ignoreCase: true); } catch { FeatureModule.Log.LogWarning((object)("[Chests] ToggleKey: '" + text + "' is not a Unity KeyCode - hotkey disabled")); _keyMain = (KeyCode)0; _keyMods = (KeyCode[])(object)new KeyCode[0]; return; } if (i == array.Length - 1) { _keyMain = val; } else { list.Add(val); } } } if ((int)_keyMain == 0) { list.Clear(); } _keyMods = list.ToArray(); } public override void OnConfigChanged(ConfigEntryBase entry) { PushSettings(); if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } } private static HarmonyMethod M(string name) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new HarmonyMethod(typeof(CraftFromChestsModule), name, (Type[])null); } private void Need(ref MethodInfo slot, Type t, string name, Type[] args, string label) { slot = ((args == null) ? AccessTools.DeclaredMethod(t, name, (Type[])null, (Type[])null) : AccessTools.DeclaredMethod(t, name, args, (Type[])null)); if (slot == null) { throw new Exception(label + " not found"); } } protected override void ApplyPatches() { MethodInfo slot = null; Need(ref slot, typeof(Container), "Awake", null, "Container.Awake()"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("ContainerAwakePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Container), "OnDestroyed", null, "Container.OnDestroyed()"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("ContainerDestroyedPost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Player), "HaveRequirements", new Type[4] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) }, "Player.HaveRequirements(Recipe,bool,int,int)"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("HaveRecipePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Player), "HaveRequirements", new Type[2] { typeof(Piece), typeof(RequirementMode) }, "Player.HaveRequirements(Piece,RequirementMode)"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("HavePiecePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Player), "ConsumeResources", new Type[4] { typeof(Requirement[]), typeof(int), typeof(int), typeof(int) }, "Player.ConsumeResources(Requirement[],int,int,int)"); Harmony.Patch((MethodBase)slot, M("ConsumePre"), M("ConsumePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(InventoryGui), "SetupRequirement", new Type[6] { typeof(Transform), typeof(Requirement), typeof(Player), typeof(bool), typeof(int), typeof(int) }, "InventoryGui.SetupRequirement(Transform,Requirement,Player,bool,int,int)"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("SetupRequirementPost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Smelter), "OnAddOre", new Type[3] { typeof(Switch), typeof(Humanoid), typeof(ItemData) }, "Smelter.OnAddOre"); Harmony.Patch((MethodBase)slot, M("SmelterAddOrePre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Smelter), "OnAddFuel", new Type[3] { typeof(Switch), typeof(Humanoid), typeof(ItemData) }, "Smelter.OnAddFuel"); Harmony.Patch((MethodBase)slot, M("SmelterAddFuelPre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Fireplace), "Interact", new Type[3] { typeof(Humanoid), typeof(bool), typeof(bool) }, "Fireplace.Interact"); Harmony.Patch((MethodBase)slot, M("FireplaceInteractPre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(CookingStation), "OnAddFuelSwitch", new Type[3] { typeof(Switch), typeof(Humanoid), typeof(ItemData) }, "CookingStation.OnAddFuelSwitch"); Harmony.Patch((MethodBase)slot, M("CookingAddFuelPre"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(CookingStation), "FindCookableItem", new Type[1] { typeof(Inventory) }, "CookingStation.FindCookableItem"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("CookingFindCookablePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Need(ref slot, typeof(Player), "Update", null, "Player.Update()"); Harmony.Patch((MethodBase)slot, (HarmonyMethod)null, M("PlayerUpdatePost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } public override void Disable() { base.Disable(); ChestSource.Clear(); } private static void ContainerAwakePost(Container __instance) { if (_self != null && _self.Enabled && FeatureModule.ClientActive()) { ChestSource.Register(__instance); } } private static void ContainerDestroyedPost(Container __instance) { ChestSource.Unregister(__instance); } private static void PlayerUpdatePost(Player __instance) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (_self == null || !_self.Active || !FeatureModule.ClientActive() || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (int)_keyMain == 0 || !Input.GetKeyDown(_keyMain)) { return; } for (int i = 0; i < _keyMods.Length; i++) { if (!Input.GetKey(_keyMods[i])) { return; } } _userOn = !_userOn; string text = (_userOn ? "Craft from chests: ON" : "Craft from chests: OFF (this character only)"); FeatureModule.Log.LogInfo((object)("[Chests] " + text)); try { ((Character)__instance).Message((MessageType)2, text, 0, (Sprite)null); } catch { } } private static void HaveRecipePost(Player __instance, Recipe recipe, bool discover, int qualityLevel, int amount, ref bool __result) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (__result || discover || !Live() || !_pullCrafting.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)recipe == (Object)null || recipe.m_resources == null || (Object)(object)recipe.m_item == (Object)null || recipe.m_requireOnlyOneIngredient) { return; } try { if (!__instance.RequiredCraftingStation(recipe, qualityLevel, true)) { return; } string dlc = recipe.m_item.m_itemData.m_shared.m_dlc; if (dlc.Length > 0 && !DLCMan.instance.IsDLCInstalled(dlc)) { return; } List list = ChestSource.Nearby(((Component)__instance).transform.position); if (list.Count == 0) { return; } Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (val != null && Object.op_Implicit((Object)(object)val.m_resItem)) { int num = val.GetAmount(qualityLevel) * amount; if (num > 0 && Available(__instance, val, num, list) < num) { return; } } } __result = true; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] HaveRequirements(Recipe) postfix: " + ex.Message)); } } private static int Available(Player p, Requirement req, int need, List boxes) { string name = req.m_resItem.m_itemData.m_shared.m_name; bool flag = ChestSource.ItemBlocked(Utils.GetPrefabName(((Component)req.m_resItem).gameObject), name); int num = 0; int num2 = Mathf.Max(1, req.m_resItem.m_itemData.m_shared.m_maxQuality); for (int i = 1; i <= num2; i++) { int num3 = ((Humanoid)p).m_inventory.CountItems(name, i, true); if (!flag && num3 < need) { num3 += ChestSource.Count(name, boxes, i); } if (num3 > num) { num = num3; } if (num >= need) { break; } } return num; } private static void HavePiecePost(Player __instance, Piece piece, RequirementMode mode, ref bool __result) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_0060: 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_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Invalid comparison between Unknown and I4 if (__result || ((int)mode != 0 && (int)mode != 2) || !Live() || !_pullBuilding.Value || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)piece == (Object)null || piece.m_resources == null) { return; } try { if ((Object.op_Implicit((Object)(object)piece.m_craftingStation) && !Object.op_Implicit((Object)(object)CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)__instance).transform.position)) && !ZoneSystem.instance.GetGlobalKey((GlobalKeys)22)) || (piece.m_dlc.Length > 0 && !DLCMan.instance.IsDLCInstalled(piece.m_dlc))) { return; } List list = ChestSource.Nearby(((Component)__instance).transform.position); if (list.Count == 0) { return; } Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (val != null && Object.op_Implicit((Object)(object)val.m_resItem) && val.m_amount > 0) { string name = val.m_resItem.m_itemData.m_shared.m_name; string prefabName = Utils.GetPrefabName(((Component)val.m_resItem).gameObject); int num = (((int)mode == 2) ? 1 : val.m_amount); int num2 = ((Humanoid)__instance).m_inventory.CountItems(name, -1, true); if (num2 < num && !ChestSource.ItemBlocked(prefabName, name)) { num2 += ChestSource.Count(name, list); } if (num2 < num) { return; } } } __result = true; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] HaveRequirements(Piece) postfix: " + ex.Message)); } } private static void ConsumePre(Player __instance, Requirement[] requirements, out int[] __state) { __state = null; if (Live() && (_pullCrafting.Value || _pullBuilding.Value) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && requirements != null) { int[] array = new int[requirements.Length]; for (int i = 0; i < requirements.Length; i++) { Requirement val = requirements[i]; array[i] = ((val != null && Object.op_Implicit((Object)(object)val.m_resItem)) ? ((Humanoid)__instance).m_inventory.CountItems(val.m_resItem.m_itemData.m_shared.m_name, -1, true) : 0); } __state = array; } } private static void ConsumePost(Player __instance, Requirement[] requirements, int qualityLevel, int itemQuality, int multiplier, int[] __state) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (__state == null || requirements == null) { return; } try { List list = ChestSource.Nearby(((Component)__instance).transform.position); if (list.Count == 0) { return; } for (int i = 0; i < requirements.Length && i < __state.Length; i++) { Requirement val = requirements[i]; if (val == null || !Object.op_Implicit((Object)(object)val.m_resItem)) { continue; } int num = val.GetAmount(qualityLevel) * multiplier; if (num <= 0) { continue; } string name = val.m_resItem.m_itemData.m_shared.m_name; string prefabName = Utils.GetPrefabName(((Component)val.m_resItem).gameObject); if (ChestSource.ItemBlocked(prefabName, name)) { continue; } int num2 = __state[i] - ((Humanoid)__instance).m_inventory.CountItems(name, -1, true); int num3 = num - num2; if (num3 > 0) { int num4 = ChestSource.Consume(name, num3, itemQuality, list); if (num4 < num3) { FeatureModule.Log.LogWarning((object)("[Chests] only " + num4 + "/" + num3 + " " + prefabName + " came out of nearby containers - the recipe was charged short")); } } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] ConsumeResources postfix: " + ex.Message)); } } private static void SetupRequirementPost(Transform elementRoot, Requirement req, Player player, bool craft, int quality, int craftMultiplier, bool __result) { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) if (!__result || !Live() || !_showNearbyCount.Value || (Object)(object)player != (Object)(object)Player.m_localPlayer || req == null || !Object.op_Implicit((Object)(object)req.m_resItem) || !(craft ? _pullCrafting.Value : _pullBuilding.Value)) { return; } try { Transform val = elementRoot.Find("res_amount"); if ((Object)(object)val == (Object)null) { return; } TMP_Text component = ((Component)val).GetComponent(); if ((Object)(object)component == (Object)null) { return; } if (!int.TryParse(component.text, out var result)) { result = req.GetAmount(quality) * craftMultiplier; } if (result > 0) { string name = req.m_resItem.m_itemData.m_shared.m_name; string prefabName = Utils.GetPrefabName(((Component)req.m_resItem).gameObject); int num = ((Humanoid)player).m_inventory.CountItems(name, -1, true); if (!ChestSource.ItemBlocked(prefabName, name)) { num += ChestSource.Count(name, ChestSource.Nearby(((Component)player).transform.position)); } component.text = num + "/" + result; if (num >= result) { ((Graphic)component).color = Color.white; } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] SetupRequirement postfix: " + ex.Message)); } } private static bool SmelterAddOrePre(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) if (!Live() || !_pullSmelters.Value) { return true; } if (item != null || (Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return true; } try { Inventory inventory = user.GetInventory(); if (inventory == null) { return true; } if (__instance.GetQueueSize() >= __instance.m_maxOre) { return true; } ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return true; } foreach (ItemConversion item2 in __instance.m_conversion) { if (item2 != null && (Object)(object)item2.m_from != (Object)null && inventory.HaveItem(item2.m_from.m_itemData.m_shared.m_name, true)) { return true; } } List list = ChestSource.Nearby(((Component)__instance).transform.position); if (list.Count == 0) { return true; } foreach (ItemConversion item3 in __instance.m_conversion) { if (item3 != null && !((Object)(object)item3.m_from == (Object)null)) { string name = item3.m_from.m_itemData.m_shared.m_name; string prefabName = Utils.GetPrefabName(((Component)item3.m_from).gameObject); if (!ChestSource.ItemBlocked(prefabName, name) && ChestSource.Consume(name, 1, -1, list) == 1) { ((Character)user).Message((MessageType)2, "$msg_added " + name, 0, (Sprite)null); nview.InvokeRPC("RPC_AddOre", new object[1] { prefabName }); __result = true; return false; } } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] Smelter.OnAddOre prefix: " + ex.Message)); } return true; } private static bool SmelterAddFuelPre(Smelter __instance, Humanoid user, ItemData item, ref bool __result) { //IL_00ea: Unknown result type (might be due to invalid IL or missing references) if (!Live() || !_pullSmelters.Value) { return true; } if ((Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return true; } if ((Object)(object)__instance.m_fuelItem == (Object)null) { return true; } try { string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (item != null && item.m_shared.m_name != name) { return true; } if (__instance.GetFuel() > (float)(__instance.m_maxFuel - 1)) { return true; } Inventory inventory = user.GetInventory(); if (inventory == null || inventory.HaveItem(name, true)) { return true; } ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return true; } if (ChestSource.ItemBlocked(Utils.GetPrefabName(((Component)__instance.m_fuelItem).gameObject), name)) { return true; } List boxes = ChestSource.Nearby(((Component)__instance).transform.position); if (ChestSource.Consume(name, 1, -1, boxes) != 1) { return true; } ((Character)user).Message((MessageType)2, "$msg_added " + name, 0, (Sprite)null); nview.InvokeRPC("RPC_AddFuel", Array.Empty()); __result = true; return false; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] Smelter.OnAddFuel prefix: " + ex.Message)); } return true; } private static bool FireplaceInteractPre(Fireplace __instance, Humanoid user, bool hold, bool alt, ref bool __result) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) if (!Live() || !_pullFires.Value) { return true; } if (hold || (Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return true; } if (__instance.m_infiniteFuel || !__instance.m_canRefill || (Object)(object)__instance.m_fuelItem == (Object)null) { return true; } try { ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return true; } float num = nview.GetZDO().GetFloat(ZDOVars.s_fuel, 0f); if (__instance.m_canTurnOff && !alt && num > 0f) { return true; } if ((float)Mathf.CeilToInt(num) >= __instance.m_maxFuel) { return true; } Inventory inventory = user.GetInventory(); string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (inventory == null || inventory.HaveItem(name, true)) { return true; } if (ChestSource.ItemBlocked(Utils.GetPrefabName(((Component)__instance.m_fuelItem).gameObject), name)) { return true; } List boxes = ChestSource.Nearby(((Component)__instance).transform.position); if (ChestSource.Consume(name, 1, -1, boxes) != 1) { return true; } if (!nview.HasOwner()) { nview.ClaimOwnership(); } ((Character)user).Message((MessageType)2, Localization.instance.Localize("$msg_fireadding", new string[1] { name }), 0, (Sprite)null); nview.InvokeRPC("RPC_AddFuel", Array.Empty()); __result = true; return false; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] Fireplace.Interact prefix: " + ex.Message)); } return true; } private static bool CookingAddFuelPre(CookingStation __instance, Humanoid user, ItemData item, ref bool __result) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) if (!Live() || !PullCooking) { return true; } if ((Object)(object)user == (Object)null || (Object)(object)user != (Object)(object)Player.m_localPlayer) { return true; } if ((Object)(object)__instance.m_fuelItem == (Object)null) { return true; } try { string name = __instance.m_fuelItem.m_itemData.m_shared.m_name; if (item != null && item.m_shared.m_name != name) { return true; } if (__instance.GetFuel() > (float)(__instance.m_maxFuel - 1)) { return true; } Inventory inventory = user.GetInventory(); if (inventory == null || inventory.HaveItem(name, true)) { return true; } ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return true; } if (ChestSource.ItemBlocked(Utils.GetPrefabName(((Component)__instance.m_fuelItem).gameObject), name)) { return true; } List boxes = ChestSource.Nearby(((Component)__instance).transform.position); if (ChestSource.Consume(name, 1, -1, boxes) != 1) { return true; } ((Character)user).Message((MessageType)2, "$msg_added " + name, 0, (Sprite)null); nview.InvokeRPC("RPC_AddFuel", Array.Empty()); __result = true; return false; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] CookingStation.OnAddFuelSwitch prefix: " + ex.Message)); } return true; } private static void CookingFindCookablePost(CookingStation __instance, ref ItemData __result) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) if (__result != null || !Live() || !PullCooking) { return; } try { if ((__instance.m_requireFire && !__instance.IsFireLit()) || __instance.GetFreeSlot() == -1) { return; } List list = ChestSource.Nearby(((Component)__instance).transform.position); if (list.Count == 0) { return; } foreach (ItemConversion item in __instance.m_conversion) { if (item != null && !((Object)(object)item.m_from == (Object)null)) { string name = item.m_from.m_itemData.m_shared.m_name; if (!ChestSource.ItemBlocked(Utils.GetPrefabName(((Component)item.m_from).gameObject), name) && ChestSource.Count(name, list) > 0 && ChestSource.Consume(name, 1, -1, list) == 1) { ItemData val = item.m_from.m_itemData.Clone(); val.m_stack = 1; val.m_dropPrefab = ((Component)item.m_from).gameObject; __result = val; break; } } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Chests] CookingStation.FindCookableItem postfix: " + ex.Message)); } } internal static string Numbers() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("range ").Append(_range.Value.ToString("0.#")).Append("m") .Append(", crafting=") .Append(_pullCrafting.Value) .Append(" building=") .Append(_pullBuilding.Value) .Append(" smelters=") .Append(_pullSmelters.Value) .Append(" fires=") .Append(_pullFires.Value) .Append(" cookingStations=") .Append(_pullCooking.Value) .Append(_pullCooking.Value ? "" : " (meat stays in the chests)") .Append(", leaveOne=") .Append(_leaveOne.Value) .Append(" vehicles=") .Append(_includeVehicles.Value) .Append(" showCounts=") .Append(_showNearbyCount.Value) .Append(", excludedContainers=") .Append(ChestSource.ExcludedContainers.Count) .Append(" excludedItems=") .Append(ChestSource.ExcludedItems.Count) .Append(", toggleKey=") .Append(_toggleKey.Value); return stringBuilder.ToString(); } public override string StatusDetail() { return Numbers() + ", registered containers=" + ChestSource.Registered + (_userOn ? "" : ", TOGGLED OFF by this player"); } } internal sealed class CorpseRunPlusModule : FeatureModule { internal static readonly int PullHash = StringExtensionMethods.GetStableHashCode("NVLB_GravePull"); internal static readonly int RestedHash = StringExtensionMethods.GetStableHashCode("Rested"); private static CorpseRunPlusModule _inst; private static GravePullStatusEffect _pullTemplate; private static bool _selfTestDone; private ConfigEntry _compassEnabled; private ConfigEntry _compassHideDistance; private ConfigEntry _compassUpdateSec; private ConfigEntry _compassOffsetX; private ConfigEntry _compassOffsetY; private ConfigEntry _compassArrow; private ConfigEntry _compassArrowScale; private ConfigEntry _respawnFoodEnabled; private ConfigEntry _respawnFoods; private ConfigEntry _respawnFoodCount; private ConfigEntry _respawnRestedEnabled; private ConfigEntry _restedMinutes; private ConfigEntry _pullEnabled; private ConfigEntry _pullMinDistance; private ConfigEntry _pullFullDistance; private ConfigEntry _pullMaxRegenBonus; private ConfigEntry _pullMaxDrainReduction; private ConfigEntry _pullUpdateSec; private ConfigEntry _pullIconFrom; private ConfigEntry _scaledEnabled; private ConfigEntry _scaledDurationPer100m; private ConfigEntry _scaledMaxDurationSec; private ConfigEntry _scaledExtraRegen; private ConfigEntry _scaledRegenFullDistance; private ConfigEntry _lootMatchDistance; private ConfigEntry _selfTest; private static bool _diedThisSession; private static bool _looted; private static Vector3 _grave; private static bool _haveGrave; private static string _graveWorld = ""; private static float _lootDistanceFromHome = -1f; private static bool _commandRegistered; private static bool _grantPending; private static float _grantAt; private static bool _scalePending; private static float _scalePendingUntil; private static int _corpseRunHash; private static StatusEffect _lastScaled; private static float _compassAcc; private static float _pullAcc; private static float _lastDistance = -1f; private static float _lastStrength; private static bool _pullOn; private static string _lastGrantText = "none"; private static float _lastScaledTtl; private static MethodInfo _updateFood; public override string Name => "CorpseRunPlus"; public override string Section => "CorpseRun"; public override ModuleSide Side { get { if (_selfTest == null || !_selfTest.Value) { return ModuleSide.Client; } return ModuleSide.Both; } } private CorpseRunPlusModule() { _inst = this; } protected override void Bind() { _selfTest = BindLocal("SelfTest", defaultValue: false, "Machine-local diagnostic: flip the module's Side to Both so a dedicated server registers the status effect and runs the self test (top-10 stamina foods, the vanilla Rested/CorpseRun fields, and the pull/duration maths) with zero players. Never synced. Leave false in normal play."); _compassEnabled = BindSynced("CompassEnabled", defaultValue: true, "GraveCompass: show a HUD arrow and distance pointing at your death point until you reach or loot the grave."); _compassHideDistance = BindSynced("CompassHideDistance", 10f, "GraveCompass: hide the compass once you are this close to the grave, in metres."); _compassUpdateSec = BindSynced("CompassUpdateSec", 0.25f, "GraveCompass: seconds between compass refreshes."); _compassOffsetX = BindLocal("CompassOffsetX", 0f, "GraveCompass: horizontal position of the compass, in HUD units from the centre of the screen. Machine-local - it is a personal HUD preference."); _compassOffsetY = BindLocal("CompassOffsetY", 200f, "GraveCompass: vertical position of the compass, in HUD units from the centre of the screen (positive = up). Machine-local."); _compassArrow = BindLocal("CompassArrow", "^", "GraveCompass: the character used as the arrow. It is rotated to point at the grave. \"^\" is ASCII and always renders; a nicer glyph may not exist in the font."); _compassArrowScale = BindLocal("CompassArrowScale", 1.6f, "GraveCompass: arrow font size as a multiple of the donor label's size."); _respawnFoodEnabled = BindSynced("RespawnFoodEnabled", defaultValue: true, "RespawnFood: put food in your belly when you respawn after a death (never on login). The item is created from the prefab - it is not taken from any inventory."); _respawnFoods = BindSynced("RespawnFoods", "Bread", "RespawnFood: comma-separated item prefab names, best first. Only the first RespawnFoodCount that exist in ObjectDB are used (Valheim allows 3 food slots)."); _respawnFoodCount = BindSynced("RespawnFoodCount", 1, "RespawnFood: how many of the RespawnFoods entries to grant, 0-3."); _respawnRestedEnabled = BindSynced("RespawnRestedEnabled", defaultValue: true, "RespawnRested: give the vanilla Rested buff on a death-respawn, with at least RestedMinutes left on it."); _restedMinutes = BindSynced("RestedMinutes", 10f, "RespawnRested: minimum minutes of Rested granted on a death-respawn. Vanilla's base is 5 minutes plus 1 per comfort level; this raises it, never lowers it."); _pullEnabled = BindSynced("PullEnabled", defaultValue: true, "GravePull: a stamina buff that scales with how far your corpse still is. Affects only your own stamina - enemies are completely untouched."); _pullMinDistance = BindSynced("PullMinDistance", 50f, "GravePull: no buff at all within this many metres of the grave."); _pullFullDistance = BindSynced("PullFullDistance", 1000f, "GravePull: metres BEYOND PullMinDistance at which the buff reaches full strength."); _pullMaxRegenBonus = BindSynced("PullMaxRegenBonus", 1f, "GravePull: extra stamina regeneration at full strength (1.0 = +100%)."); _pullMaxDrainReduction = BindSynced("PullMaxDrainReduction", 0.5f, "GravePull: fraction of run and jump stamina cost removed at full strength (0.5 = half price)."); _pullUpdateSec = BindSynced("PullUpdateSec", 1f, "GravePull: seconds between strength recalculations."); _pullIconFrom = BindSynced("PullIconFrom", "Rested", "GravePull: borrow this status effect's HUD icon. The mod ships no art. 'CorpseRun' is the other obvious choice."); _scaledEnabled = BindSynced("ScaledEnabled", defaultValue: true, "CorpseRunScaled: stretch vanilla's 'CorpseRun' loot buff by how far the grave was from your bed or home point, so a long death costs less than a short one."); _scaledDurationPer100m = BindSynced("ScaledDurationPer100m", 0.2f, "CorpseRunScaled: extra duration per 100 m from home, as a fraction of the vanilla duration (0.2 = +20% per 100 m)."); _scaledMaxDurationSec = BindSynced("ScaledMaxDurationSec", 900f, "CorpseRunScaled: hard cap on the stretched duration, in seconds."); _scaledExtraRegen = BindSynced("ScaledExtraRegen", 0.5f, "CorpseRunScaled: extra stamina-regen multiplier added at ScaledRegenFullDistance and beyond, scaled linearly by distance from home. 0 disables the strengthening."); _scaledRegenFullDistance = BindSynced("ScaledRegenFullDistance", 1000f, "CorpseRunScaled: distance from home, in metres, at which ScaledExtraRegen is applied in full."); _lootMatchDistance = BindSynced("LootMatchDistance", 20f, "How close a tombstone must be to your recorded death point to count as YOUR grave when it is looted or emptied. Guards against another player's grave clearing your compass."); } public override void OnConfigChanged(ConfigEntryBase entry) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) PushNumbers(); GraveCompassHud.SetOffset(new Vector2(_compassOffsetX.Value, _compassOffsetY.Value), _compassArrow.Value, _compassArrowScale.Value); if (!_compassEnabled.Value || !base.Active) { GraveCompassHud.Hide(); } if (!_pullEnabled.Value || !base.Active) { RemovePull(); } FeatureModule.Log.LogInfo((object)("[CorpseRun] config: " + Numbers())); } private void PushNumbers() { //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) GravePullStatusEffect.MaxRegenBonus = _pullMaxRegenBonus.Value; GravePullStatusEffect.MaxDrainReduction = _pullMaxDrainReduction.Value; if ((Object)(object)_pullTemplate != (Object)null) { float num = _pullUpdateSec.Value * 3f; ((StatusEffect)_pullTemplate).m_ttl = ((num < 3f) ? 3f : num); } GraveCompassHud.Offset = new Vector2(_compassOffsetX.Value, _compassOffsetY.Value); GraveCompassHud.ArrowChar = (string.IsNullOrEmpty(_compassArrow.Value) ? "^" : _compassArrow.Value); GraveCompassHud.ArrowScale = _compassArrowScale.Value; } private string Numbers() { return "compass=" + _compassEnabled.Value + "(hide<" + _compassHideDistance.Value + "m every " + _compassUpdateSec.Value + "s) food=" + _respawnFoodEnabled.Value + "(" + _respawnFoods.Value + " x" + _respawnFoodCount.Value + ") rested=" + _respawnRestedEnabled.Value + "(" + _restedMinutes.Value + "min) pull=" + _pullEnabled.Value + "(>" + _pullMinDistance.Value + "m ramp " + _pullFullDistance.Value + "m regen+" + Mathf.RoundToInt(_pullMaxRegenBonus.Value * 100f) + "% drain-" + Mathf.RoundToInt(_pullMaxDrainReduction.Value * 100f) + "% every " + _pullUpdateSec.Value + "s icon=" + _pullIconFrom.Value + ") scaled=" + _scaledEnabled.Value + "(+" + Mathf.RoundToInt(_scaledDurationPer100m.Value * 100f) + "%/100m cap " + _scaledMaxDurationSec.Value + "s regen+" + _scaledExtraRegen.Value + "@" + _scaledRegenFullDistance.Value + "m) selfTest=" + _selfTest.Value; } protected override void ApplyPatches() { //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02f6: Expected O, but got Unknown //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Expected O, but got Unknown //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Expected O, but got Unknown //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_038e: Expected O, but got Unknown //IL_03a8: Unknown result type (might be due to invalid IL or missing references) //IL_03b5: Expected O, but got Unknown //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Expected O, but got Unknown //IL_03f6: Unknown result type (might be due to invalid IL or missing references) //IL_0403: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ObjectDB.Awake() not found"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("ObjectDB.CopyOtherDB(ObjectDB) not found"); } MethodInfo methodInfo3 = AccessTools.Method(typeof(Player), "Update", (Type[])null, (Type[])null); if (methodInfo3 == null) { throw new Exception("Player.Update() not found"); } MethodInfo methodInfo4 = AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null); if (methodInfo4 == null) { throw new Exception("Player.OnDeath() not found"); } MethodInfo methodInfo5 = AccessTools.Method(typeof(Player), "OnSpawned", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo5 == null) { throw new Exception("Player.OnSpawned(bool) not found"); } MethodInfo methodInfo6 = AccessTools.Method(typeof(TombStone), "GiveBoost", (Type[])null, (Type[])null); if (methodInfo6 == null) { throw new Exception("TombStone.GiveBoost() not found"); } MethodInfo methodInfo7 = AccessTools.Method(typeof(TombStone), "OnTakeAllSuccess", (Type[])null, (Type[])null); if (methodInfo7 == null) { throw new Exception("TombStone.OnTakeAllSuccess() not found"); } string[] array = new string[3] { "ModifyStaminaRegen", "ModifyRunStaminaDrain", "ModifyJumpStaminaUsage" }; foreach (string text in array) { if (AccessTools.Method(typeof(SEMan), text, (Type[])null, (Type[])null) == null) { throw new Exception("SEMan." + text + " not found - GravePull would be inert"); } } if (AccessTools.Method(typeof(PlayerProfile), "GetHomePoint", (Type[])null, (Type[])null) == null || AccessTools.Method(typeof(PlayerProfile), "GetCustomSpawnPoint", (Type[])null, (Type[])null) == null) { throw new Exception("PlayerProfile home point API not found"); } if (AccessTools.Method(typeof(ZNet), "GetWorldName", (Type[])null, (Type[])null) == null) { throw new Exception("ZNet.GetWorldName() not found - the grave record could not be scoped to a world"); } MethodInfo methodInfo8 = AccessTools.Method(typeof(Terminal), "InitTerminal", (Type[])null, (Type[])null); if (methodInfo8 == null) { throw new Exception("Terminal.InitTerminal() not found"); } if (AccessTools.Method(typeof(Player), "GetFoods", (Type[])null, (Type[])null) == null) { throw new Exception("Player.GetFoods() not found - RespawnFood would be inert"); } _updateFood = AccessTools.Method(typeof(Player), "UpdateFood", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); if (_updateFood == null) { FeatureModule.Log.LogWarning((object)"[CorpseRun] Player.UpdateFood(float,bool) not found - granted food will show up on the HUD a second late instead of immediately."); } HarmonyMethod val = new HarmonyMethod(typeof(CorpseRunPlusModule), "ObjectDBPostfix", (Type[])null); Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "PlayerUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "OnDeathPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "OnSpawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo6, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "GiveBoostPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "TakeAllPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseRunPlusModule), "RegisterCommand", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); EnsureTemplate(); PushNumbers(); FeatureModule.Log.LogInfo((object)("[CorpseRun] " + Numbers())); } public override void Disable() { try { RemovePull(); GraveCompassHud.Destroy(); ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance != (Object)null && instance.m_StatusEffects != null && (Object)(object)_pullTemplate != (Object)null) { instance.m_StatusEffects.Remove((StatusEffect)(object)_pullTemplate); } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] teardown: " + ex.Message)); } base.Disable(); } private static void EnsureTemplate() { if (!((Object)(object)_pullTemplate != (Object)null)) { _pullTemplate = GravePullStatusEffect.Create(); if (_inst != null) { _inst.PushNumbers(); } } } private static void ObjectDBPostfix(ObjectDB __instance) { if (_inst == null || !_inst.Active) { return; } try { Register(__instance); if (_inst._selfTest.Value) { RunSelfTest(__instance); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[CorpseRun] ObjectDB registration failed: " + ex)); } } private static void Register(ObjectDB odb) { if ((Object)(object)odb == (Object)null || odb.m_StatusEffects == null) { return; } EnsureTemplate(); for (int i = 0; i < odb.m_StatusEffects.Count; i++) { StatusEffect val = odb.m_StatusEffects[i]; if ((Object)(object)val != (Object)null && ((Object)val).name == "NVLB_GravePull") { if ((object)val != _pullTemplate) { odb.m_StatusEffects[i] = (StatusEffect)(object)_pullTemplate; break; } return; } } BorrowIcon(odb); odb.m_StatusEffects.Add((StatusEffect)(object)_pullTemplate); ManualLogSource log = FeatureModule.Log; string[] obj = new string[6] { "[CorpseRun] registered status effect 'NVLB_GravePull' (hash ", null, null, null, null, null }; int pullHash = PullHash; obj[1] = pullHash.ToString(); obj[2] = ") in ObjectDB, "; obj[3] = odb.m_StatusEffects.Count.ToString(); obj[4] = " total"; obj[5] = (((Object)(object)((StatusEffect)_pullTemplate).m_icon != (Object)null) ? (", icon borrowed from '" + _inst._pullIconFrom.Value + "'") : ", NO ICON (donor not found)"); log.LogInfo((object)string.Concat(obj)); } private static void BorrowIcon(ObjectDB odb) { if ((Object)(object)((StatusEffect)_pullTemplate).m_icon != (Object)null) { return; } string b = ((_inst != null) ? _inst._pullIconFrom.Value : "Rested"); Sprite val = null; foreach (StatusEffect statusEffect in odb.m_StatusEffects) { if (!((Object)(object)statusEffect == (Object)null) && !((Object)(object)statusEffect.m_icon == (Object)null)) { if ((Object)(object)val == (Object)null) { val = statusEffect.m_icon; } if (string.Equals(((Object)statusEffect).name, b, StringComparison.OrdinalIgnoreCase)) { ((StatusEffect)_pullTemplate).m_icon = statusEffect.m_icon; return; } } } ((StatusEffect)_pullTemplate).m_icon = val; } private static void OnDeathPostfix(Player __instance) { //IL_0056: Unknown result type (might be due to invalid IL or missing references) if (_inst == null || !_inst.Active || !FeatureModule.ClientActive() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { _diedThisSession = true; _looted = false; _lastScaled = null; _lootDistanceFromHome = -1f; GraveRecord.Write(__instance, ((Component)__instance).transform.position); ReadGrave(); FeatureModule.Log.LogInfo((object)("[CorpseRun] death recorded in world '" + GraveRecord.CurrentWorld() + "' at " + (_haveGrave ? ((Vector3)(ref _grave)).ToString("F0") : "?") + " - respawn grants armed (food=" + _inst._respawnFoodEnabled.Value + " rested=" + _inst._respawnRestedEnabled.Value + ")")); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] OnDeath: " + ex.Message)); } } private static void OnSpawnedPostfix(Player __instance) { if (_inst != null && _inst.Active && FeatureModule.ClientActive() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer) && _diedThisSession) { _diedThisSession = false; _grantPending = true; _grantAt = Time.time + 0.5f; ReadGrave(); } } private static void ReadGrave() { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) _haveGrave = false; _graveWorld = ""; try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && GraveRecord.TryRead(localPlayer, out Vector3 pos, out double _, out string recordWorld)) { _grave = pos; _graveWorld = recordWorld ?? ""; _haveGrave = true; } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] grave record read: " + ex.Message)); } } private static Vector3 HomePoint() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) PlayerProfile val = (((Object)(object)Game.instance != (Object)null) ? Game.instance.GetPlayerProfile() : null); if (val == null) { return Vector3.zero; } if (!val.HaveCustomSpawnPoint()) { return val.GetHomePoint(); } return val.GetCustomSpawnPoint(); } private static void GrantRespawnGifts(Player me) { CorpseRunPlusModule inst = _inst; List list = new List(); if (inst._respawnFoodEnabled.Value) { int num = Mathf.Clamp(inst._respawnFoodCount.Value, 0, 3); int num2 = 0; string[] array = inst._respawnFoods.Value.Split(new char[1] { ',' }); foreach (string text in array) { if (num2 >= num) { break; } string text2 = text.Trim(); if (text2.Length != 0 && GiveFood(me, text2)) { list.Add("food:" + text2); num2++; } } if (num2 < num) { FeatureModule.Log.LogWarning((object)("[CorpseRun] RespawnFood: wanted " + num + " item(s) from '" + inst._respawnFoods.Value + "', granted " + num2 + " (missing prefab, or all 3 food slots were full)")); } } if (inst._respawnRestedEnabled.Value) { float num3 = inst._restedMinutes.Value * 60f; SEMan sEMan = ((Character)me).GetSEMan(); if (sEMan != null && num3 > 0f) { sEMan.AddStatusEffect(RestedHash, true, 0, 0f); StatusEffect statusEffect = sEMan.GetStatusEffect(RestedHash); if ((Object)(object)statusEffect != (Object)null) { if (statusEffect.m_ttl < num3) { statusEffect.m_ttl = num3; } list.Add("rested:" + Mathf.RoundToInt(statusEffect.m_ttl) + "s"); } else { ManualLogSource log = FeatureModule.Log; int i = RestedHash; log.LogWarning((object)("[CorpseRun] RespawnRested: 'Rested' status effect not found in ObjectDB (hash " + i + ")")); } } } _lastGrantText = ((list.Count == 0) ? "none" : string.Join(" ", list.ToArray())); FeatureModule.Log.LogInfo((object)("[CorpseRun] death-respawn grants: " + _lastGrantText)); } private static bool GiveFood(Player me, string prefabName) { //IL_017d: Unknown result type (might be due to invalid IL or missing references) //IL_0184: Expected O, but got Unknown try { ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { return false; } GameObject itemPrefab = instance.GetItemPrefab(prefabName); if ((Object)(object)itemPrefab == (Object)null) { FeatureModule.Log.LogWarning((object)("[CorpseRun] RespawnFood: no item prefab named '" + prefabName + "'")); return false; } ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { return false; } SharedData shared = component.m_itemData.m_shared; if (shared.m_food <= 0f && shared.m_foodStamina <= 0f && shared.m_foodEitr <= 0f) { FeatureModule.Log.LogWarning((object)("[CorpseRun] RespawnFood: '" + prefabName + "' is not a food")); return false; } List foods = me.GetFoods(); if (foods == null) { return false; } foreach (Food item in foods) { if (item != null && item.m_item != null && item.m_item.m_shared != null && item.m_item.m_shared.m_name == shared.m_name) { return false; } } if (foods.Count >= 3) { return false; } ItemData val = component.m_itemData.Clone(); val.m_dropPrefab = itemPrefab; val.m_stack = 1; Food val2 = new Food(); val2.m_name = ((Object)itemPrefab).name; val2.m_item = val; val2.m_time = shared.m_foodBurnTime; val2.m_health = shared.m_food; val2.m_stamina = shared.m_foodStamina; val2.m_eitr = shared.m_foodEitr; foods.Add(val2); if (_updateFood != null) { _updateFood.Invoke(me, new object[2] { 0f, true }); } FeatureModule.Log.LogInfo((object)("[CorpseRun] RespawnFood: granted '" + ((Object)itemPrefab).name + "' (hp " + shared.m_food + " / sta " + shared.m_foodStamina + " / eitr " + shared.m_foodEitr + " for " + shared.m_foodBurnTime + "s)")); return true; } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] RespawnFood('" + prefabName + "') failed: " + ex.Message)); return false; } } private static void GiveBoostPostfix(TombStone __instance) { OnGraveTouched(__instance, "emptied"); } private static void TakeAllPostfix(TombStone __instance) { OnGraveTouched(__instance, "looted"); } private static void OnGraveTouched(TombStone ts, string how) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) if (_inst == null || !_inst.Active || !FeatureModule.ClientActive()) { return; } try { if ((Object)(object)ts == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return; } if (!_haveGrave) { ReadGrave(); } if (!_haveGrave) { return; } float num = Vector3.Distance(((Component)ts).transform.position, _grave); if (!(num > _inst._lootMatchDistance.Value)) { if ((Object)(object)ts.m_lootStatusEffect != (Object)null) { _corpseRunHash = ts.m_lootStatusEffect.NameHash(); } _lootDistanceFromHome = Vector3.Distance(_grave, HomePoint()); _looted = true; _haveGrave = false; GraveRecord.Clear(Player.m_localPlayer); GraveCompassHud.Hide(); RemovePull(); if (_inst._scaledEnabled.Value) { _scalePending = true; _scalePendingUntil = Time.time + 5f; } FeatureModule.Log.LogInfo((object)("[CorpseRun] grave " + how + " " + num.ToString("0.0") + "m from the recorded death point - compass off, pull off" + (_inst._scaledEnabled.Value ? ", CorpseRun scaling armed" : ""))); } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] grave " + how + ": " + ex.Message)); } } private static void RegisterCommand() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (_commandRegistered) { return; } _commandRegistered = true; try { new ConsoleCommand("nvlb.grave.clear", "Forget the recorded grave: turns the Grave Compass and Grave Pull off until your next death.", new ConsoleEvent(ClearCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); FeatureModule.Log.LogInfo((object)"[CorpseRun] console command 'nvlb.grave.clear' registered"); } catch (Exception ex) { _commandRegistered = false; FeatureModule.Log.LogError((object)("[CorpseRun] could not register nvlb.grave.clear: " + ex)); } } private static void ClearCommand(ConsoleEventArgs args) { Player localPlayer = Player.m_localPlayer; string text = GraveRecord.Describe(localPlayer); bool num = GraveRecord.Clear(localPlayer); _haveGrave = false; _graveWorld = ""; _lastDistance = -1f; _lootDistanceFromHome = -1f; GraveCompassHud.Hide(); RemovePull(); string text2 = (num ? ("grave record cleared (was " + text + ")") : "no grave record to clear"); if (args != null && (Object)(object)args.Context != (Object)null) { args.Context.AddString("[CorpseRun] " + text2); } FeatureModule.Log.LogInfo((object)("[CorpseRun] " + text2)); } private static int CorpseRunHash() { if (_corpseRunHash == 0) { _corpseRunHash = StringExtensionMethods.GetStableHashCode("CorpseRun"); } return _corpseRunHash; } private static bool TryScaleCorpseRun(Player me) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) SEMan sEMan = ((Character)me).GetSEMan(); if (sEMan == null) { return false; } StatusEffect statusEffect = sEMan.GetStatusEffect(CorpseRunHash()); SE_Stats val = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); if ((Object)(object)val == (Object)null) { return false; } if ((object)val == _lastScaled) { return true; } SE_Stats val2 = (SE_Stats)(((Object)(object)ObjectDB.instance != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); float baseTtl = (((Object)(object)val2 != (Object)null && ((StatusEffect)val2).m_ttl > 0f) ? ((StatusEffect)val2).m_ttl : ((StatusEffect)val).m_ttl); float num = (((Object)(object)val2 != (Object)null) ? val2.m_staminaRegenMultiplier : val.m_staminaRegenMultiplier); float num2 = ((_lootDistanceFromHome >= 0f) ? _lootDistanceFromHome : (_haveGrave ? Vector3.Distance(_grave, HomePoint()) : 0f)); float lastScaledTtl = (((StatusEffect)val).m_ttl = ScaledDuration(baseTtl, num2, _inst._scaledDurationPer100m.Value, _inst._scaledMaxDurationSec.Value)); ((StatusEffect)val).ResetTime(); float num3 = 0f; if (_inst._scaledExtraRegen.Value != 0f && _inst._scaledRegenFullDistance.Value > 0f) { float num4 = Mathf.Clamp01(num2 / _inst._scaledRegenFullDistance.Value); num3 = _inst._scaledExtraRegen.Value * num4; val.m_staminaRegenMultiplier = num + num3; } _lastScaled = (StatusEffect)(object)val; _lastScaledTtl = lastScaledTtl; FeatureModule.Log.LogInfo((object)("[CorpseRun] CorpseRunScaled: grave was " + Mathf.RoundToInt(num2) + "m from home -> duration " + baseTtl.ToString("0") + "s -> " + lastScaledTtl.ToString("0") + "s, staminaRegen x" + val.m_staminaRegenMultiplier.ToString("0.00") + " (base " + num.ToString("0.00") + " + " + num3.ToString("0.00") + ")")); return true; } private static void PlayerUpdatePostfix(Player __instance) { if (_inst == null || !_inst.Active || !FeatureModule.ClientActive() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Tick(__instance, Time.deltaTime); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] tick: " + ex.Message)); } } private static void Tick(Player me, float dt) { //IL_007a: 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) //IL_0103: Unknown result type (might be due to invalid IL or missing references) CorpseRunPlusModule inst = _inst; if (_grantPending && Time.time >= _grantAt) { _grantPending = false; GrantRespawnGifts(me); } if (_scalePending && (TryScaleCorpseRun(me) || Time.time > _scalePendingUntil)) { _scalePending = false; } if (((Character)me).IsDead()) { GraveCompassHud.Hide(); return; } if (!_haveGrave) { ReadGrave(); } float num = -1f; if (_haveGrave && !_looted) { num = (_lastDistance = Vector3.Distance(((Component)me).transform.position, _grave)); } else { _lastDistance = -1f; } _compassAcc += dt; float num2 = ((inst._compassUpdateSec.Value < 0.05f) ? 0.05f : inst._compassUpdateSec.Value); if (_compassAcc >= num2) { _compassAcc = 0f; if (inst._compassEnabled.Value && num >= 0f && num > inst._compassHideDistance.Value) { GraveCompassHud.Show(me, _grave, num); } else { GraveCompassHud.Hide(); } } _pullAcc += dt; float num3 = ((inst._pullUpdateSec.Value < 0.1f) ? 0.1f : inst._pullUpdateSec.Value); if (_pullAcc >= num3) { _pullAcc = 0f; UpdatePull(me, num); } } private static void UpdatePull(Player me, float dist) { CorpseRunPlusModule inst = _inst; float num = (GravePullStatusEffect.Strength = (_lastStrength = ((inst._pullEnabled.Value && dist >= 0f) ? PullStrength(dist, inst._pullMinDistance.Value, inst._pullFullDistance.Value) : 0f))); GravePullStatusEffect.Distance = ((dist < 0f) ? 0f : dist); SEMan sEMan = ((Character)me).GetSEMan(); if (sEMan == null) { return; } StatusEffect statusEffect = sEMan.GetStatusEffect(PullHash); if (num > 0f) { if ((Object)(object)statusEffect == (Object)null) { EnsureTemplate(); if ((Object)(object)ObjectDB.instance != (Object)null) { Register(ObjectDB.instance); } sEMan.AddStatusEffect((StatusEffect)(object)_pullTemplate, false, 0, 0f); _pullOn = true; FeatureModule.Log.LogInfo((object)("[CorpseRun] GravePull ON (" + Mathf.RoundToInt(dist) + "m out, " + Mathf.RoundToInt(num * 100f) + "% strength)")); } else { statusEffect.ResetTime(); _pullOn = true; } } else if ((Object)(object)statusEffect != (Object)null) { sEMan.RemoveStatusEffect(PullHash, true); _pullOn = false; FeatureModule.Log.LogInfo((object)"[CorpseRun] GravePull OFF"); } else { _pullOn = false; } } private static void RemovePull() { try { GravePullStatusEffect.Strength = 0f; Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { SEMan sEMan = ((Character)localPlayer).GetSEMan(); if (sEMan != null && sEMan.HaveStatusEffect(PullHash)) { sEMan.RemoveStatusEffect(PullHash, true); } _pullOn = false; } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[CorpseRun] pull removal failed: " + ex.Message)); } } internal static float PullStrength(float dist, float min, float full) { if (full <= 0f) { if (!(dist > min)) { return 0f; } return 1f; } return Mathf.Clamp01((dist - min) / full); } internal static float ScaledDuration(float baseTtl, float dist, float per100m, float cap) { float num = baseTtl * (1f + per100m * (dist / 100f)); if (cap > 0f && num > cap) { num = cap; } return num; } public override string StatusDetail() { string text = ((_lastDistance >= 0f) ? (_lastDistance.ToString("0") + "m") : (_looted ? "looted" : (_haveGrave ? "> list = new List>(); if (odb.m_items != null) { foreach (GameObject item in odb.m_items) { if ((Object)(object)item == (Object)null) { continue; } ItemDrop component = item.GetComponent(); if (!((Object)(object)component == (Object)null) && component.m_itemData != null && component.m_itemData.m_shared != null) { SharedData shared = component.m_itemData.m_shared; if (!(shared.m_foodStamina <= 0f)) { list.Add(new KeyValuePair(((Object)item).name, shared)); } } } } list.Sort((KeyValuePair a, KeyValuePair b) => b.Value.m_foodStamina.CompareTo(a.Value.m_foodStamina)); FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: " + list.Count + " items in ObjectDB with m_foodStamina > 0; top 10:")); for (int num = 0; num < list.Count && num < 10; num++) { KeyValuePair keyValuePair = list[num]; FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: " + (num + 1) + ". " + keyValuePair.Key + " stamina=" + keyValuePair.Value.m_foodStamina + " health=" + keyValuePair.Value.m_food + " eitr=" + keyValuePair.Value.m_foodEitr + " burn=" + keyValuePair.Value.m_foodBurnTime + "s regen=" + keyValuePair.Value.m_foodRegen)); } string[] array = inst._respawnFoods.Value.Split(new char[1] { ',' }); int num2; for (num2 = 0; num2 < array.Length; num2++) { string text = array[num2].Trim(); if (text.Length != 0) { GameObject itemPrefab = odb.GetItemPrefab(text); ItemDrop val = (((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent() : null); FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: default RespawnFoods entry '" + text + "' -> " + (((Object)(object)val != (Object)null && val.m_itemData != null && val.m_itemData.m_shared != null) ? ("PRESENT stamina=" + val.m_itemData.m_shared.m_foodStamina + " health=" + val.m_itemData.m_shared.m_food + " burn=" + val.m_itemData.m_shared.m_foodBurnTime + "s OK") : "MISSING"))); } } DumpVanillaSe(odb, "Rested"); DumpVanillaSe(odb, "CorpseRun"); StatusEffect statusEffect = odb.GetStatusEffect(PullHash); ManualLogSource log = FeatureModule.Log; string[] obj = new string[8] { "[CorpseRun] SelfTest: ObjectDB.GetStatusEffect(\"NVLB_GravePull\".GetStableHashCode()=", null, null, null, null, null, null, null }; num2 = PullHash; obj[1] = num2.ToString(); obj[2] = ") -> "; obj[3] = (((Object)(object)statusEffect == (Object)null) ? "NOT FOUND" : ("'" + ((Object)statusEffect).name + "' / m_name='" + statusEffect.m_name + "' ttl=" + statusEffect.m_ttl + " icon=" + (((Object)(object)statusEffect.m_icon != (Object)null) ? "yes" : "no") + (((object)statusEffect == _pullTemplate) ? " SAME INSTANCE OK" : " DIFFERENT INSTANCE"))); obj[4] = ", is GravePullStatusEffect="; obj[5] = (statusEffect is GravePullStatusEffect).ToString(); obj[6] = ", list size="; obj[7] = odb.m_StatusEffects.Count.ToString(); log.LogInfo((object)string.Concat(obj)); float value = inst._pullMinDistance.Value; float value2 = inst._pullFullDistance.Value; float value3 = inst._scaledDurationPer100m.Value; float value4 = inst._scaledMaxDurationSec.Value; StatusEffect statusEffect2 = odb.GetStatusEffect(StringExtensionMethods.GetStableHashCode("CorpseRun")); SE_Stats val2 = (SE_Stats)(object)((statusEffect2 is SE_Stats) ? statusEffect2 : null); float baseTtl = (((Object)(object)val2 != (Object)null && ((StatusEffect)val2).m_ttl > 0f) ? ((StatusEffect)val2).m_ttl : 50f); FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: maths with PullMin=" + value + " PullFull=" + value2 + " Per100m=" + value3 + " Cap=" + value4 + " baseCorpseRunTtl=" + baseTtl + "s")); float[] array2 = new float[4] { 0f, 100f, 500f, 2000f }; for (num2 = 0; num2 < array2.Length; num2++) { float num3 = array2[num2]; float num4 = PullStrength(num3, value, value2); float num5 = 1f + inst._pullMaxRegenBonus.Value * num4; float num6 = 1f - inst._pullMaxDrainReduction.Value * num4; float num7 = ScaledDuration(baseTtl, num3, value3, value4); float num8 = Mathf.Clamp01(num3 / inst._scaledRegenFullDistance.Value); FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: d=" + num3.ToString("0") + "m pull s=" + num4.ToString("0.000") + " -> staminaRegen x" + num5.ToString("0.00") + ", run/jump cost x" + num6.ToString("0.00") + " | CorpseRun ttl " + num7.ToString("0.0") + "s" + ((value4 > 0f && num7 >= value4) ? " (CAPPED)" : "") + ", extraRegen +" + (inst._scaledExtraRegen.Value * num8).ToString("0.00"))); } GraveRecordSelfTest(); FeatureModule.Log.LogInfo((object)"[CorpseRun] SelfTest: --- end ---"); } private static void GraveRecordSelfTest() { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) int pass = 0; int fail = 0; Action action = delegate(bool ok, string what) { if (ok) { pass++; FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: PASS " + what)); } else { fail++; FeatureModule.Log.LogError((object)("[CorpseRun] SelfTest: FAIL " + what)); } }; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(1234.5f, -12.25f, -678.75f); Dictionary data = new Dictionary(); action(!GraveRecord.TryRead(data, "NEWTEST", out Vector3 pos, out double gameTime, out string recordWorld), "(1) a character with a stale profile death point but no nvlb.grave record reads as NO grave - compass, GravePull and CorpseRunScaled all stay off"); GraveRecord.Write(data, "NEWTEST", val, 4242.5); int arg; if (GraveRecord.TryRead(data, "NEWTEST", out pos, out gameTime, out recordWorld)) { Vector3 val2 = pos - val; if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f && recordWorld == "NEWTEST") { arg = ((Math.Abs(gameTime - 4242.5) < 0.001) ? 1 : 0); goto IL_00ce; } } arg = 0; goto IL_00ce; IL_00ce: action((byte)arg != 0, "(2) a record written in 'NEWTEST' reads back there: " + ((Vector3)(ref pos)).ToString("F2") + " world='" + recordWorld + "' t=" + gameTime); action(!GraveRecord.TryRead(data, "BLACKWORLD", out pos, out gameTime, out recordWorld), "(3) the same record is invisible in 'BLACKWORLD' (the 0.4.2 bug: an old grave from another world used to light the compass)"); action(GraveRecord.Has(data), "(3b) ...and it is left in place, so returning to 'NEWTEST' still finds the grave"); action(GraveRecord.Clear(data) && !GraveRecord.Has(data) && !GraveRecord.TryRead(data, "NEWTEST", out pos, out gameTime, out recordWorld), "(4) looting/emptying the grave clears the record - no grave anywhere afterwards"); Dictionary data2 = new Dictionary { { "nvlb.grave", "1|OnlyTwo" } }; action(!GraveRecord.TryRead(data2, "NEWTEST", out pos, out gameTime, out recordWorld), "(5) a corrupt record decodes to no grave instead of throwing"); Dictionary data3 = new Dictionary(); GraveRecord.Write(data3, "a|b", val, 1.0); action(GraveRecord.TryRead(data3, "a|b", out pos, out gameTime, out recordWorld) && recordWorld == "a|b", "(6) a world name containing '|' round trips"); FeatureModule.Log.LogInfo((object)("[CorpseRun] SelfTest: grave record - " + pass + " passed, " + fail + " FAILED")); } private static void DumpVanillaSe(ObjectDB odb, string name) { StatusEffect statusEffect = odb.GetStatusEffect(StringExtensionMethods.GetStableHashCode(name)); if ((Object)(object)statusEffect == (Object)null) { FeatureModule.Log.LogWarning((object)("[CorpseRun] SelfTest: vanilla status effect '" + name + "' NOT FOUND in ObjectDB")); return; } string text = "[CorpseRun] SelfTest: vanilla '" + ((Object)statusEffect).name + "' (" + ((object)statusEffect).GetType().Name + ") m_name='" + statusEffect.m_name + "' ttl=" + statusEffect.m_ttl + " icon=" + (((Object)(object)statusEffect.m_icon != (Object)null) ? "yes" : "no"); SE_Stats val = (SE_Stats)(object)((statusEffect is SE_Stats) ? statusEffect : null); if ((Object)(object)val != (Object)null) { text = text + " staminaRegenMul=" + val.m_staminaRegenMultiplier + " runStaminaDrainMod=" + val.m_runStaminaDrainModifier + " jumpStaminaUseMod=" + val.m_jumpStaminaUseModifier + " addMaxCarryWeight=" + val.m_addMaxCarryWeight; } SE_Rested val2 = (SE_Rested)(object)((statusEffect is SE_Rested) ? statusEffect : null); if ((Object)(object)val2 != (Object)null) { text = text + " baseTTL=" + val2.m_baseTTL + " ttlPerComfort=" + val2.m_TTLPerComfortLevel; } FeatureModule.Log.LogInfo((object)text); } } internal static class GraveCompassHud { public static Vector2 Offset = new Vector2(0f, 200f); public static string ArrowChar = "^"; public static float ArrowScale = 1.6f; private static Hud _hud; private static GameObject _arrowGo; private static GameObject _labelGo; private static RectTransform _arrowRt; private static RectTransform _labelRt; private static TMP_Text _arrow; private static TMP_Text _label; private static bool _built; private static bool _failed; private static bool _visible; public static bool Failed => _failed; public static bool Visible => _visible; public static void Show(Player me, Vector3 target, float distance) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if (_failed || (Object)(object)me == (Object)null) { return; } try { Hud instance = Hud.instance; if ((Object)(object)instance == (Object)null) { HideInternal(); } else { if (!Ensure(instance)) { return; } Vector3 val = target - ((Component)me).transform.position; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = ((Component)me).transform.forward; } Vector3 val2 = CameraForward(me); val2.y = 0f; if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f) { val2 = ((Component)me).transform.forward; } float num = Vector3.SignedAngle(val2, val, Vector3.up); if ((Object)(object)_arrowRt != (Object)null) { ((Transform)_arrowRt).localRotation = Quaternion.Euler(0f, 0f, 0f - num); } if ((Object)(object)_arrow != (Object)null && _arrow.text != ArrowChar) { _arrow.text = ArrowChar; } if ((Object)(object)_label != (Object)null) { _label.text = Mathf.RoundToInt(distance) + " m"; } if (!_visible) { if ((Object)(object)_arrowGo != (Object)null) { _arrowGo.SetActive(true); } if ((Object)(object)_labelGo != (Object)null) { _labelGo.SetActive(true); } _visible = true; } } } catch (Exception ex) { Fail("compass update failed: " + ex.Message); } } public static void Hide() { if (_failed) { return; } try { HideInternal(); } catch (Exception ex) { Fail("compass hide failed: " + ex.Message); } } private static void HideInternal() { if (_visible) { if ((Object)(object)_arrowGo != (Object)null) { _arrowGo.SetActive(false); } if ((Object)(object)_labelGo != (Object)null) { _labelGo.SetActive(false); } _visible = false; } } private static Vector3 CameraForward(Player me) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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) GameCamera instance = GameCamera.instance; if ((Object)(object)instance != (Object)null && (Object)(object)((Component)instance).transform != (Object)null) { return ((Component)instance).transform.forward; } Camera main = Camera.main; if ((Object)(object)main != (Object)null) { return ((Component)main).transform.forward; } return ((Component)me).transform.forward; } private unsafe static bool Ensure(Hud hud) { //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) if (_built && (Object)(object)_arrowGo != (Object)null && (Object)(object)_labelGo != (Object)null && _hud == hud) { return true; } if (_hud != hud) { Destroy(); } TMP_Text val = PickDonor(hud); if ((Object)(object)val == (Object)null) { Fail("no TMP_Text donor found under Hud - no grave compass (the rest of CorpseRunPlus still works)."); return false; } RectTransform val2 = (RectTransform)(((Object)(object)hud.m_rootObject != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); if ((Object)(object)val2 == (Object)null) { Transform parent = val.transform.parent; val2 = (RectTransform)(object)((parent is RectTransform) ? parent : null); } if ((Object)(object)val2 == (Object)null) { Fail("Hud.m_rootObject has no RectTransform and the donor has no parent - no grave compass."); return false; } _arrowGo = Build(val, val2, "NVLB_GraveCompassArrow", out _arrowRt, out _arrow); _labelGo = Build(val, val2, "NVLB_GraveCompassLabel", out _labelRt, out _label); if ((Object)(object)_arrow == (Object)null || (Object)(object)_label == (Object)null) { Fail("cloned compass label carries no TMP_Text - no grave compass."); Destroy(); return false; } _arrow.text = ArrowChar; _arrow.fontSize = val.fontSize * ArrowScale; _label.text = ""; Reposition(); _arrowGo.SetActive(false); _labelGo.SetActive(false); _visible = false; _hud = hud; _built = true; ManualLogSource log = NoVikingLeftBehindPlugin.Log; string[] obj = new string[6] { "[CorpseRun] grave compass built from donor '", ((Object)val).name, "' under '", ((Object)val2).name, "' at offset ", null }; Vector2 offset = Offset; obj[5] = ((object)(*(Vector2*)(&offset))/*cast due to .constrained prefix*/).ToString(); log.LogInfo((object)string.Concat(obj)); return true; } private static TMP_Text PickDonor(Hud hud) { if ((Object)(object)hud.m_gpName != (Object)null) { return hud.m_gpName; } if ((Object)(object)hud.m_healthText != (Object)null) { return hud.m_healthText; } if ((Object)(object)hud.m_rootObject != (Object)null) { TMP_Text[] componentsInChildren = hud.m_rootObject.GetComponentsInChildren(true); if (componentsInChildren != null && componentsInChildren.Length != 0) { return componentsInChildren[0]; } } return null; } private static GameObject Build(TMP_Text donor, RectTransform parent, string name, out RectTransform rt, out TMP_Text text) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: 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_0073: 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) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate(((Component)donor).gameObject, (Transform)(object)parent); ((Object)val).name = name; rt = val.GetComponent(); text = val.GetComponent(); if ((Object)(object)rt != (Object)null) { rt.anchorMin = new Vector2(0.5f, 0.5f); rt.anchorMax = new Vector2(0.5f, 0.5f); rt.pivot = new Vector2(0.5f, 0.5f); ((Transform)rt).localScale = Vector3.one; ((Transform)rt).localRotation = Quaternion.identity; rt.sizeDelta = new Vector2(220f, 40f); } if ((Object)(object)text != (Object)null) { text.alignment = (TextAlignmentOptions)514; text.enableWordWrapping = false; ((Graphic)text).raycastTarget = false; ((Graphic)text).color = Color.white; } return val; } private static void Reposition() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_arrowRt != (Object)null) { _arrowRt.anchoredPosition = Offset; } if ((Object)(object)_labelRt != (Object)null) { _labelRt.anchoredPosition = Offset + new Vector2(0f, -34f); } } public static void SetOffset(Vector2 offset, string arrowChar, float arrowScale) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) Offset = offset; ArrowChar = (string.IsNullOrEmpty(arrowChar) ? "^" : arrowChar); ArrowScale = ((arrowScale <= 0f) ? 1.6f : arrowScale); try { Reposition(); if ((Object)(object)_arrow != (Object)null) { _arrow.text = ArrowChar; } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[CorpseRun] compass reposition: " + ex.Message)); } } private static void Fail(string message) { _failed = true; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[CorpseRun] " + message)); Destroy(); } public static void Destroy() { try { if ((Object)(object)_arrowGo != (Object)null) { Object.Destroy((Object)(object)_arrowGo); } if ((Object)(object)_labelGo != (Object)null) { Object.Destroy((Object)(object)_labelGo); } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[CorpseRun] compass teardown: " + ex.Message)); } _arrowGo = null; _labelGo = null; _arrowRt = null; _labelRt = null; _arrow = null; _label = null; _hud = null; _built = false; _visible = false; } public static void Reset() { Destroy(); _failed = false; } } internal sealed class GravePullStatusEffect : StatusEffect { public const string SeName = "NVLB_GravePull"; public static float Strength; public static float MaxRegenBonus; public static float MaxDrainReduction; public static float Distance; public static GravePullStatusEffect Create() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) GravePullStatusEffect gravePullStatusEffect = ScriptableObject.CreateInstance(); ((Object)gravePullStatusEffect).name = "NVLB_GravePull"; ((StatusEffect)gravePullStatusEffect).m_name = "Grave Pull"; ((StatusEffect)gravePullStatusEffect).m_category = ""; ((StatusEffect)gravePullStatusEffect).m_flashIcon = false; ((StatusEffect)gravePullStatusEffect).m_cooldownIcon = false; ((StatusEffect)gravePullStatusEffect).m_ttl = 0f; ((StatusEffect)gravePullStatusEffect).m_tooltip = ""; ((StatusEffect)gravePullStatusEffect).m_startMessageType = (MessageType)1; ((StatusEffect)gravePullStatusEffect).m_stopMessageType = (MessageType)1; return gravePullStatusEffect; } private static float RegenMul() { float num = 1f + MaxRegenBonus * Strength; if (!(num < 0.01f)) { return num; } return 0.01f; } private static float CostMul() { float num = 1f - MaxDrainReduction * Strength; if (num < 0.05f) { num = 0.05f; } if (num > 1f) { num = 1f; } return num; } public override string GetIconText() { return Mathf.RoundToInt(Strength * 100f) + "%"; } public override string GetTooltipString() { return "The pull of your own grave.\nStrength " + Mathf.RoundToInt(Strength * 100f) + "% (" + Mathf.RoundToInt(Distance) + " m to go)\n$se_staminaregen: +" + Mathf.RoundToInt(MaxRegenBonus * Strength * 100f) + "%\n$se_runstamina: -" + Mathf.RoundToInt(MaxDrainReduction * Strength * 100f) + "%\n$se_jumpstamina: -" + Mathf.RoundToInt(MaxDrainReduction * Strength * 100f) + "%"; } public override void ModifyStaminaRegen(ref float staminaRegen) { if (!(Strength <= 0f) && MaxRegenBonus != 0f) { staminaRegen *= RegenMul(); } } public override void ModifyRunStaminaDrain(float baseDrain, ref float drain, Vector3 dir) { if (!(Strength <= 0f) && MaxDrainReduction != 0f) { drain *= CostMul(); } } public override void ModifyJumpStaminaUsage(float baseStaminaUse, ref float staminaUse) { if (!(Strength <= 0f) && MaxDrainReduction != 0f) { staminaUse *= CostMul(); } } } internal static class GraveRecord { public const string Key = "nvlb.grave"; private const int Version = 1; private static string Esc(string s) { return (s ?? "").Replace("|", "%7C"); } private static string Unesc(string s) { return (s ?? "").Replace("%7C", "|"); } public static string Encode(string world, Vector3 pos, double gameTime) { CultureInfo invariantCulture = CultureInfo.InvariantCulture; return 1 + "|" + Esc(world) + "|" + pos.x.ToString("R", invariantCulture) + "|" + pos.y.ToString("R", invariantCulture) + "|" + pos.z.ToString("R", invariantCulture) + "|" + gameTime.ToString("R", invariantCulture); } public static bool TryDecode(string s, out string world, out Vector3 pos, out double gameTime) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) world = null; pos = Vector3.zero; gameTime = 0.0; if (string.IsNullOrEmpty(s)) { return false; } try { string[] array = s.Split(new char[1] { '|' }); if (array.Length != 6) { return false; } if (!int.TryParse(array[0], out var result) || result != 1) { return false; } CultureInfo invariantCulture = CultureInfo.InvariantCulture; if (!float.TryParse(array[2], NumberStyles.Float, invariantCulture, out var result2)) { return false; } if (!float.TryParse(array[3], NumberStyles.Float, invariantCulture, out var result3)) { return false; } if (!float.TryParse(array[4], NumberStyles.Float, invariantCulture, out var result4)) { return false; } if (!double.TryParse(array[5], NumberStyles.Float, invariantCulture, out var result5)) { return false; } world = Unesc(array[1]); pos = new Vector3(result2, result3, result4); gameTime = result5; return true; } catch { return false; } } public static void Write(Dictionary data, string world, Vector3 pos, double gameTime) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (data != null) { data["nvlb.grave"] = Encode(world, pos, gameTime); } } public static bool TryRead(Dictionary data, string currentWorld, out Vector3 pos, out double gameTime, out string recordWorld) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: 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_005b: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; gameTime = 0.0; recordWorld = null; if (data == null) { return false; } if (!data.TryGetValue("nvlb.grave", out string value)) { return false; } if (!TryDecode(value, out string world, out pos, out gameTime)) { return false; } recordWorld = world; if (!string.Equals(world, currentWorld ?? "", StringComparison.Ordinal)) { pos = Vector3.zero; return false; } return true; } public static bool Clear(Dictionary data) { return data?.Remove("nvlb.grave") ?? false; } public static bool Has(Dictionary data) { return data?.ContainsKey("nvlb.grave") ?? false; } public static string CurrentWorld() { try { return ((Object)(object)ZNet.instance != (Object)null) ? (ZNet.instance.GetWorldName() ?? "") : ""; } catch { return ""; } } public static double GameTime() { try { return ((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetTimeSeconds() : 0.0; } catch { return 0.0; } } public static void Write(Player p, Vector3 pos) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)p == (Object)null)) { if (p.m_customData == null) { p.m_customData = new Dictionary(); } Write(p.m_customData, CurrentWorld(), pos, GameTime()); } } public static bool TryRead(Player p, out Vector3 pos, out double gameTime, out string recordWorld) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; gameTime = 0.0; recordWorld = null; if ((Object)(object)p != (Object)null) { return TryRead(p.m_customData, CurrentWorld(), out pos, out gameTime, out recordWorld); } return false; } public static bool Clear(Player p) { if ((Object)(object)p != (Object)null) { return Clear(p.m_customData); } return false; } public static string Describe(Player p) { if ((Object)(object)p == (Object)null || p.m_customData == null) { return "none"; } if (!p.m_customData.TryGetValue("nvlb.grave", out var value)) { return "none"; } if (!TryDecode(value, out string world, out Vector3 pos, out double _)) { return "unreadable"; } string text = CurrentWorld(); return ((Vector3)(ref pos)).ToString("F0") + " in '" + world + "'" + (string.Equals(world, text, StringComparison.Ordinal) ? "" : (" (OTHER world, ignored here: '" + text + "')")); } } internal sealed class EconomySelfTestModule : FeatureModule { private static ConfigEntry _selfTest; private static bool _ran; public override string Name => "EconomySelfTest"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "Economy"; protected override void Bind() { _selfTest = BindLocal("SelfTest", defaultValue: false, "Diagnostic. Log what the Economy modules (TrailingTierDiscount, RichSmelting, TraderStock) WOULD do with the current world tier, ObjectDB and config, once per world load. Machine-local and never synced, so turning it on for a server boot does not affect any client. Leave it false in normal use."); } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ZoneSystem.Start() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(EconomySelfTestModule), "WorldReady", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void WorldReady() { if (!_ran && _selfTest != null && _selfTest.Value) { _ran = true; Run(); } } internal static void Run() { FeatureModule.Log.LogInfo((object)"[EconomySelfTest] --- begin ---"); One("TrailingTierDiscount", TrailingTierDiscountModule.SelfTest); One("RichSmelting", RichSmeltingModule.SelfTest); One("TraderStock", TraderStockModule.SelfTest); FeatureModule.Log.LogInfo((object)"[EconomySelfTest] --- end ---"); } private static void One(string name, Func f) { try { string[] array = f().Split(new char[1] { '\n' }); foreach (string text in array) { FeatureModule.Log.LogInfo((object)text); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[EconomySelfTest] " + name + " threw: " + ex)); } } public override string StatusDetail() { return "SelfTest=" + (_selfTest != null && _selfTest.Value) + (_ran ? " (already run)" : ""); } } internal sealed class RichSmeltingModule : FeatureModule { private static ConfigEntry _outputMultiplier; private static ConfigEntry _recipeYieldMultiplier; private static RichSmeltingModule _self; public override string Name => "RichSmelting"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Smelting"; private static bool Live() { if (_self != null && _self.Active) { return FeatureModule.ClientActive(); } return false; } protected override void Bind() { _self = this; _outputMultiplier = BindSynced("OutputMultiplier", 2, "Smelters, blast furnaces and kilns produce this many items per input when the OUTPUT material is behind the frontier (bronze/iron once the group has moved on). 1 = vanilla. Clamped to at least 1 and to the item's max stack size."); _recipeYieldMultiplier = BindSynced("RecipeYieldMultiplier", 2, "Crafting recipes whose OUTPUT is a material behind the frontier (Bronze at the forge, BronzeNails, ...) yield this many times as much. Only raw materials listed in [Tiers] MaterialTiers qualify, so tools and armour are never affected. 1 = vanilla."); } protected override void ApplyPatches() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Smelter), "Spawn", new Type[2] { typeof(string), typeof(int) }, (Type[])null); if (methodInfo == null) { throw new Exception("Smelter.Spawn(string,int) not found"); } Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(RichSmeltingModule), "SpawnPre", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Recipe), "GetAmount", new Type[4] { typeof(int), typeof(int).MakeByRefType(), typeof(ItemData).MakeByRefType(), typeof(int) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("Recipe.GetAmount(int,out int,out ItemDrop.ItemData,int) not found"); } Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(RichSmeltingModule), "RecipeAmountPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } private static void SpawnPre(Smelter __instance, string ore, ref int stack) { if (!Live() || stack <= 0) { return; } int num = Mathf.Max(1, _outputMultiplier.Value); if (num == 1) { return; } ItemDrop val = OutputOf(__instance, ore); if (!((Object)(object)val == (Object)null) && Tiers.IsBehind(Tiers.OfItem(((Object)val).name))) { long num2 = (long)stack * (long)num; int num3 = ((val.m_itemData != null && val.m_itemData.m_shared != null) ? val.m_itemData.m_shared.m_maxStackSize : 0); if (num3 > 0 && num2 > num3) { num2 = num3; } if (num2 < stack) { num2 = stack; } stack = (int)num2; } } private static ItemDrop OutputOf(Smelter smelter, string ore) { if ((Object)(object)smelter == (Object)null || smelter.m_conversion == null || string.IsNullOrEmpty(ore)) { return null; } foreach (ItemConversion item in smelter.m_conversion) { if (item != null && !((Object)(object)item.m_from == (Object)null) && !((Object)(object)item.m_to == (Object)null) && ((Object)((Component)item.m_from).gameObject).name == ore) { return item.m_to; } } return null; } private static void RecipeAmountPost(Recipe __instance, ref int __result) { if (Live() && __result > 0) { int num = Mathf.Max(1, _recipeYieldMultiplier.Value); if (num != 1 && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance.m_item == (Object)null) && Tiers.IsBehind(Tiers.OfItem(((Object)__instance.m_item).name))) { long num2 = (long)__result * (long)num; __result = (int)((num2 > int.MaxValue) ? int.MaxValue : num2); } } } private string Numbers() { return "smelter output x" + Mathf.Max(1, _outputMultiplier.Value) + ", recipe yield x" + Mathf.Max(1, _recipeYieldMultiplier.Value) + " for outputs in behind-the-frontier tiers: " + Frontier.BehindRangeText(); } public override void OnConfigChanged(ConfigEntryBase entry) { if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } } public override string StatusDetail() { return Numbers(); } internal static string SelfTest() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[SelfTest][RichSmelting] OutputMultiplier=").Append((_outputMultiplier != null) ? _outputMultiplier.Value : (-1)).Append(" RecipeYieldMultiplier=") .Append((_recipeYieldMultiplier != null) ? _recipeYieldMultiplier.Value : (-1)) .Append(" behind=") .Append(Frontier.BehindRangeText()); int num = 0; int num2 = 0; ZNetScene instance = ZNetScene.instance; if ((Object)(object)instance == (Object)null || instance.m_prefabs == null) { stringBuilder.Append("\n ZNetScene has no prefabs - cannot list conversions"); } else { foreach (GameObject prefab in instance.m_prefabs) { if ((Object)(object)prefab == (Object)null) { continue; } Smelter component = prefab.GetComponent(); if ((Object)(object)component == (Object)null || component.m_conversion == null) { continue; } foreach (ItemConversion item in component.m_conversion) { if (item != null && !((Object)(object)item.m_from == (Object)null) && !((Object)(object)item.m_to == (Object)null)) { num++; int num3 = Tiers.OfItem(((Object)item.m_to).name); if (Tiers.IsBehind(num3)) { num2++; stringBuilder.Append("\n smelt ").Append(((Object)prefab).name).Append(": ") .Append(Tiers.CleanName(((Object)((Component)item.m_from).gameObject).name)) .Append(" -> ") .Append(Tiers.CleanName(((Object)item.m_to).name)) .Append("(t") .Append(num3) .Append(") x") .Append(Mathf.Max(1, (_outputMultiplier == null) ? 1 : _outputMultiplier.Value)); } } } } stringBuilder.Append("\n conversions: ").Append(num2).Append(" of ") .Append(num) .Append(" would be multiplied"); } ObjectDB instance2 = ObjectDB.instance; if ((Object)(object)instance2 == (Object)null || instance2.m_recipes == null) { stringBuilder.Append("\n ObjectDB has no recipes - cannot list yields"); return stringBuilder.ToString(); } int num4 = 0; foreach (Recipe recipe in instance2.m_recipes) { if ((Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null) { continue; } int num5 = Tiers.OfItem(((Object)recipe.m_item).name); if (Tiers.IsBehind(num5)) { num4++; if (num4 <= 12) { stringBuilder.Append("\n craft ").Append(Tiers.CleanName(((Object)recipe.m_item).name)).Append("(t") .Append(num5) .Append(") ") .Append(recipe.m_amount) .Append("->") .Append(recipe.m_amount * Mathf.Max(1, (_recipeYieldMultiplier == null) ? 1 : _recipeYieldMultiplier.Value)); } } } stringBuilder.Append("\n recipes: ").Append(num4).Append(" of ") .Append(instance2.m_recipes.Count) .Append(" would yield more"); return stringBuilder.ToString(); } } internal sealed class TraderStockModule : FeatureModule { private sealed class Entry { public string Prefab; public int Stack; public int Price; } internal const string DefaultItems = "Bronze:5:60,Iron:5:80,Silver:5:120,BlackMetal:5:150"; private static ConfigEntry _items; private static ConfigEntry _traderNames; private static TraderStockModule _self; public override string Name => "TraderStock"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Trader"; private static bool Live() { if (_self != null && _self.Active) { return FeatureModule.ClientActive(); } return false; } protected override void Bind() { _self = this; _items = BindSynced("Items", "Bronze:5:60,Iron:5:80,Silver:5:120,BlackMetal:5:150", "Comma-separated PrefabName:stack:price entries added to the trader's stock. An entry only appears once its material is behind the frontier: it is gated by vanilla's own TradeItem.m_requiredGlobalKey, set to the boss key for (material tier + [Frontier] TiersBehind). Unknown prefab names are logged and skipped. Materials at tier 0, or whose gating boss is past the last tier, are skipped too."); _traderNames = BindSynced("TraderNames", "Haldor", "Comma-separated trader prefab names (or Trader.m_name values) that get the extra stock. Default: Haldor only. Add Hildir or BogWitch to include them. '*' means every trader."); } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Trader), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("Trader.Start() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(TraderStockModule), "StartPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } private static void StartPost(Trader __instance) { if (!Live() || (Object)(object)__instance == (Object)null) { return; } try { Stock(__instance); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[TraderStock] could not add stock to " + ((Object)__instance).name + ": " + ex.Message)); } } private static void Stock(Trader trader) { //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: 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_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown if (!Matches(trader)) { return; } if (trader.m_items == null) { trader.m_items = new List(); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (TradeItem item in trader.m_items) { if (item != null && (Object)(object)item.m_prefab != (Object)null) { hashSet.Add(Tiers.CleanName(((Object)item.m_prefab).name)); } } int num = 0; foreach (Entry item2 in Parse((_items != null) ? _items.Value : "Bronze:5:60,Iron:5:80,Silver:5:120,BlackMetal:5:150", warn: true)) { if (!hashSet.Contains(item2.Prefab)) { string requiredKey; ItemDrop val = Resolve(item2.Prefab, out requiredKey, warn: true); if (!((Object)(object)val == (Object)null)) { trader.m_items.Add(new TradeItem { m_prefab = val, m_stack = Mathf.Max(1, item2.Stack), m_price = Mathf.Max(1, item2.Price), m_requiredGlobalKey = requiredKey }); hashSet.Add(item2.Prefab); num++; } } } if (num > 0) { FeatureModule.Log.LogInfo((object)("[TraderStock] " + ((Object)trader).name + " (" + trader.m_name + "): added " + num + " item(s), total " + trader.m_items.Count)); } } private static bool Matches(Trader trader) { string[] array = ((_traderNames != null) ? _traderNames.Value : "Haldor").Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0) { if (text == "*") { return true; } if (Tiers.CleanName(((Object)trader).name).IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } if (!string.IsNullOrEmpty(trader.m_name) && trader.m_name.IndexOf(text, StringComparison.OrdinalIgnoreCase) >= 0) { return true; } } } return false; } private static List Parse(string raw, bool warn) { List list = new List(); if (string.IsNullOrEmpty(raw)) { return list; } string[] array = raw.Split(new char[3] { ',', ';', '\n' }, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string[] array2 = text.Split(new char[1] { ':' }); if (array2.Length != 3 || !int.TryParse(array2[1].Trim(), out var result) || !int.TryParse(array2[2].Trim(), out var result2)) { if (warn) { FeatureModule.Log.LogWarning((object)("[TraderStock] ignoring '" + text + "' (want Prefab:stack:price)")); } } else { list.Add(new Entry { Prefab = array2[0].Trim(), Stack = result, Price = result2 }); } } return list; } private static ItemDrop Resolve(string prefabName, out string requiredKey, bool warn) { requiredKey = null; int num = Tiers.OfItem(prefabName); if (num <= 0) { if (warn) { FeatureModule.Log.LogWarning((object)("[TraderStock] '" + prefabName + "' is tier 0 in [Tiers] MaterialTiers - skipped")); } return null; } int num2 = num + ((Frontier.TiersBehind == null) ? 1 : Frontier.TiersBehind.Value); if (num2 > 7) { if (warn) { FeatureModule.Log.LogWarning((object)("[TraderStock] '" + prefabName + "' (tier " + num + ") can never be " + num2 + " tiers behind - skipped")); } return null; } requiredKey = (Frontier.IsOverridden ? "" : Frontier.KeyFor(num2)); if (Frontier.IsOverridden && !Tiers.IsBehind(num)) { return null; } ObjectDB instance = ObjectDB.instance; GameObject val = (((Object)(object)instance != (Object)null) ? instance.GetItemPrefab(prefabName) : null); ItemDrop obj = (((Object)(object)val != (Object)null) ? val.GetComponent() : null); if ((Object)(object)obj == (Object)null && warn) { FeatureModule.Log.LogWarning((object)("[TraderStock] '" + prefabName + "' not found in ObjectDB - skipped")); } return obj; } private string Numbers() { List list = Parse(_items.Value, warn: false); return list.Count + " item(s) for " + _traderNames.Value + " [" + _items.Value + "], gated by boss key for (tier + " + Frontier.TiersBehind.Value + ")"; } public override void OnConfigChanged(ConfigEntryBase entry) { if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers() + " - traders already in the world keep their current list until they respawn")); } } public override string StatusDetail() { return Numbers(); } internal static string SelfTest() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[SelfTest][TraderStock] TraderNames=").Append((_traderNames != null) ? _traderNames.Value : "?").Append(" Items=") .Append((_items != null) ? _items.Value : "?") .Append(" ") .Append(Frontier.Describe()) .Append(" TiersBehind=") .Append(Frontier.TiersBehind.Value); foreach (Entry item in Parse((_items != null) ? _items.Value : "Bronze:5:60,Iron:5:80,Silver:5:120,BlackMetal:5:150", warn: false)) { int num = Tiers.OfItem(item.Prefab); string requiredKey; ItemDrop val = Resolve(item.Prefab, out requiredKey, warn: false); stringBuilder.Append("\n ").Append(item.Prefab).Append(" t") .Append(num) .Append(" stack=") .Append(item.Stack) .Append(" price=") .Append(item.Price) .Append(" key=") .Append(string.IsNullOrEmpty(requiredKey) ? "(none)" : requiredKey) .Append(((Object)(object)val == (Object)null) ? " -> SKIPPED" : " -> would be added") .Append(Tiers.IsBehind(num) ? " [behind the frontier now]" : " [not behind yet]"); } return stringBuilder.ToString(); } } internal sealed class TrailingTierDiscountModule : FeatureModule { private static ConfigEntry _costMultiplier; private static ConfigEntry _extraPerTierBehind; private static ConfigEntry _minAmount; private static TrailingTierDiscountModule _self; [ThreadStatic] private static int _ctxTier; private static FieldInfo _fiSelectedRecipe; private static MethodInfo _piRecipeGet; public override string Name => "TrailingTierDiscount"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Discount"; private static bool Live() { if (_self != null && _self.Active) { return FeatureModule.ClientActive(); } return false; } protected override void Bind() { _self = this; _costMultiplier = BindSynced("CostMultiplier", 0.5f, "Cost of a recipe or build piece whose tier is behind the frontier, as a fraction of vanilla. 0.5 = half price. 1 = no discount. Values above 1 are clamped to 1: this module never makes anything more expensive."); _extraPerTierBehind = BindSynced("ExtraPerTierBehind", 0f, "Extra discount per FURTHER tier behind the frontier. 0 = the same discount whether the recipe is 1 or 3 tiers behind. 0.1 with CostMultiplier 0.5 means 0.5 at the threshold, 0.4 one tier further back, 0.3 two tiers further back. The multiplier is clamped to a minimum of 0.01."); _minAmount = BindSynced("MinAmount", 1, "Floor for a discounted requirement. 1 = a cost never drops to zero. A requirement that already costs less than this is left alone."); } protected override void ApplyPatches() { MethodInfo methodInfo = AccessTools.Method(typeof(Requirement), "GetAmount", new Type[1] { typeof(int) }, (Type[])null); if (methodInfo == null) { throw new Exception("Piece.Requirement.GetAmount(int) not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, Post("GetAmountPost"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); PatchCtx(AccessTools.Method(typeof(Player), "HaveRequirements", new Type[4] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) }, (Type[])null), "Player.HaveRequirements(Recipe,bool,int,int)", "RecipeCtxPre", "CtxFin"); PatchCtx(AccessTools.Method(typeof(Player), "GetFirstRequiredItem", (Type[])null, (Type[])null), "Player.GetFirstRequiredItem(Inventory,Recipe,...)", "RecipeCtxPre", "CtxFin"); PatchCtx(AccessTools.Method(typeof(Player), "ConsumeResources", new Type[4] { typeof(Requirement[]), typeof(int), typeof(int), typeof(int) }, (Type[])null), "Player.ConsumeResources(Requirement[],int,int,int)", "ConsumeCtxPre", "CtxFin"); PatchCtx(AccessTools.Method(typeof(Hud), "SetupPieceInfo", new Type[1] { typeof(Piece) }, (Type[])null), "Hud.SetupPieceInfo(Piece)", "PieceCtxPre", "CtxFin"); _fiSelectedRecipe = AccessTools.Field(typeof(InventoryGui), "m_selectedRecipe"); if (_fiSelectedRecipe == null) { throw new Exception("InventoryGui.m_selectedRecipe not found"); } _piRecipeGet = AccessTools.PropertyGetter(_fiSelectedRecipe.FieldType, "Recipe"); if (_piRecipeGet == null) { throw new Exception("InventoryGui." + _fiSelectedRecipe.FieldType.Name + ".Recipe getter not found"); } PatchCtx(AccessTools.Method(typeof(InventoryGui), "SetupRequirementList", new Type[4] { typeof(int), typeof(Player), typeof(bool), typeof(int) }, (Type[])null), "InventoryGui.SetupRequirementList(int,Player,bool,int)", "GuiCtxPre", "CtxFin"); MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "HaveRequirements", new Type[2] { typeof(Piece), typeof(RequirementMode) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("Player.HaveRequirements(Piece,RequirementMode) not found"); } Harmony.Patch((MethodBase)methodInfo2, Post("HavePiecePre"), (HarmonyMethod)null, (HarmonyMethod)null, Post("HavePieceFin"), (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } private void PatchCtx(MethodBase target, string label, string pre, string fin) { if (target == null) { throw new Exception(label + " not found"); } Harmony.Patch(target, Post(pre), (HarmonyMethod)null, (HarmonyMethod)null, Post(fin), (HarmonyMethod)null); } private static HarmonyMethod Post(string name) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown return new HarmonyMethod(typeof(TrailingTierDiscountModule), name, (Type[])null); } private static void GetAmountPost(ref int __result) { int ctxTier = _ctxTier; if (ctxTier > 0 && __result > 0 && Live() && Tiers.IsBehind(ctxTier)) { __result = DiscountedAmount(__result, ctxTier); } } private static void RecipeCtxPre(Recipe recipe, out int __state) { __state = _ctxTier; _ctxTier = (Live() ? Tiers.OfRecipe(recipe) : 0); } private static void ConsumeCtxPre(Requirement[] requirements, out int __state) { __state = _ctxTier; _ctxTier = (Live() ? Tiers.OfRequirements(requirements) : 0); } private static void PieceCtxPre(Piece piece, out int __state) { __state = _ctxTier; _ctxTier = (Live() ? Tiers.OfPiece(piece) : 0); } private static void GuiCtxPre(InventoryGui __instance, out int __state) { __state = _ctxTier; _ctxTier = 0; if (!Live()) { return; } try { object value = _fiSelectedRecipe.GetValue(__instance); _ctxTier = Tiers.OfRecipe((Recipe)((value == null) ? null : /*isinst with value type is only supported in some contexts*/)); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[TrailingTierDiscount] could not read the selected recipe: " + ex.Message)); } } private static void CtxFin(int __state) { _ctxTier = __state; } private static void HavePiecePre(Piece piece, RequirementMode mode, out int[] __state) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) __state = null; if ((int)mode != 0 || !Live() || (Object)(object)piece == (Object)null || piece.m_resources == null) { return; } int num = Tiers.OfPiece(piece); if (num <= 0 || !Tiers.IsBehind(num)) { return; } Requirement[] resources = piece.m_resources; int[] array = new int[resources.Length]; for (int i = 0; i < resources.Length; i++) { Requirement val = resources[i]; array[i] = val?.m_amount ?? 0; if (val != null && val.m_amount > 0) { val.m_amount = DiscountedAmount(val.m_amount, num); } } __state = array; } private static void HavePieceFin(Piece piece, int[] __state) { if (__state == null || (Object)(object)piece == (Object)null || piece.m_resources == null) { return; } Requirement[] resources = piece.m_resources; int num = Math.Min(resources.Length, __state.Length); for (int i = 0; i < num; i++) { if (resources[i] != null) { resources[i].m_amount = __state[i]; } } } internal static float MultiplierFor(int tier) { if (_costMultiplier == null) { return 1f; } int num = Frontier.WorldTier - ((Frontier.TiersBehind == null) ? 1 : Frontier.TiersBehind.Value) - tier; if (num < 0) { return 1f; } float num2 = ((_extraPerTierBehind != null) ? _extraPerTierBehind.Value : 0f); return Mathf.Clamp(_costMultiplier.Value - num2 * (float)num, 0.01f, 1f); } internal static int DiscountedAmount(int amount, int tier) { if (amount <= 0) { return amount; } float num = MultiplierFor(tier); if (num >= 1f) { return amount; } int num2 = Mathf.RoundToInt((float)amount * num); int num3 = Mathf.Min(amount, Mathf.Max(0, (_minAmount == null) ? 1 : _minAmount.Value)); if (num2 < num3) { num2 = num3; } if (num2 > amount) { num2 = amount; } return num2; } private string Numbers() { return "cost x" + _costMultiplier.Value.ToString("0.##") + ((_extraPerTierBehind.Value != 0f) ? (" (-" + _extraPerTierBehind.Value.ToString("0.##") + " per further tier behind)") : "") + " min " + _minAmount.Value + ", behind-the-frontier tiers: " + Frontier.BehindRangeText(); } public override void OnConfigChanged(ConfigEntryBase entry) { if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } } public override string StatusDetail() { return Numbers(); } internal static string SelfTest() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[SelfTest][TrailingTierDiscount] ").Append(Frontier.Describe()).Append(" TiersBehind=") .Append(Frontier.TiersBehind.Value) .Append(" behind=") .Append(Frontier.BehindRangeText()) .Append(" CostMultiplier=") .Append((_costMultiplier != null) ? _costMultiplier.Value : (-1f)) .Append(" ExtraPerTierBehind=") .Append((_extraPerTierBehind != null) ? _extraPerTierBehind.Value : (-1f)) .Append(" MinAmount=") .Append((_minAmount != null) ? _minAmount.Value : (-1)); ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null || instance.m_recipes == null) { stringBuilder.Append("\n ObjectDB has no recipes - cannot price anything"); return stringBuilder.ToString(); } string[] array = new string[4] { "AxeBronze", "Bronze", "ArmorBronzeChest", "AxeIron" }; foreach (string text in array) { Recipe val = null; foreach (Recipe recipe in instance.m_recipes) { if (!((Object)(object)recipe == (Object)null) && !((Object)(object)recipe.m_item == (Object)null) && string.Equals(Tiers.CleanName(((Object)recipe.m_item).name), text, StringComparison.OrdinalIgnoreCase)) { val = recipe; break; } } if ((Object)(object)val == (Object)null) { stringBuilder.Append("\n ").Append(text).Append(": no recipe in ObjectDB"); continue; } int num = Tiers.OfRecipe(val); bool flag = Tiers.IsBehind(num); stringBuilder.Append("\n ").Append(text).Append(": recipeTier=") .Append(num) .Append(flag ? (" BEHIND x" + MultiplierFor(num).ToString("0.##")) : " at/ahead of the frontier -> vanilla") .Append(" ->"); Requirement[] resources = val.m_resources; foreach (Requirement val2 in resources) { if (val2 != null && !((Object)(object)val2.m_resItem == (Object)null)) { int amount = val2.GetAmount(1); int value = (flag ? DiscountedAmount(amount, num) : amount); stringBuilder.Append(' ').Append(Tiers.CleanName(((Object)val2.m_resItem).name)).Append("(t") .Append(Tiers.OfItem(((Object)val2.m_resItem).name)) .Append(") ") .Append(amount) .Append("->") .Append(value) .Append(';'); } } } return stringBuilder.ToString(); } } internal sealed class FoodNoDecayModule : FeatureModule { private static ConfigEntry _keepFraction; private static ConfigEntry _curveExponent; private static ConfigEntry _hidePulse; private static ConfigEntry _pulseBelowSeconds; private static ConfigEntry _selfTest; private static FoodNoDecayModule _self; private static bool _localTick; private static int _pulseErrors; public override string Name => "FoodNoDecay"; public override ModuleSide Side { get { if (_selfTest == null || !_selfTest.Value) { return ModuleSide.Client; } return ModuleSide.Both; } } public override string Section => "Food"; private static bool Live() { if (_self != null && _self.Active) { return FeatureModule.ClientActive(); } return false; } protected override void Bind() { _self = this; _keepFraction = BindSynced("KeepFraction", 1f, "Floor applied to each eaten food's health/stamina/eitr contribution, as a fraction of its full (freshly-eaten) value: 1.0 = no decay at all until the food expires (default). 0.5 = the value never decays below half, but may still decay further towards 0.5 like vanilla. 0.0 = vanilla behaviour, unchanged."); _curveExponent = BindSynced("CurveExponent", 0.3f, "Exponent used for the vanilla decay curve before the KeepFraction floor is applied. Leave at 0.3 (vanilla's own curve) unless you specifically want a different decay shape for the portion below KeepFraction."); _hidePulse = BindSynced("HidePulse", defaultValue: true, "Stop the food icons and their timers flashing in the HUD. Vanilla pulses an icon once the food is past half its timer and flashes its countdown under a minute; with decay removed that flashing is telling you about a decay that no longer happens. See PulseBelowSeconds to keep it as a last-seconds warning."); _pulseBelowSeconds = BindSynced("PulseBelowSeconds", 0f, "When HidePulse is on, still let a food icon pulse once it has fewer than this many seconds left, as an 'about to run out' warning. 0 (default) = never pulse."); _selfTest = BindLocal("SelfTest", defaultValue: false, "Local debug only, not synced. When true, on (re)load and on Enabled toggling logs the decay fraction across three consecutive simulated ticks for CookedMeat, asserting it does not move. Leave false in normal play."); } protected override void ApplyPatches() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0076: 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_0097: Expected O, but got Unknown //IL_0097: Expected O, but got Unknown //IL_0097: Expected O, but got Unknown //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Expected O, but got Unknown //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Expected O, but got Unknown //IL_01b0: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Player), "UpdateFood", new Type[2] { typeof(float), typeof(bool) }, (Type[])null); if (methodInfo == null) { throw new Exception("Player.UpdateFood(float,bool) not found"); } Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FoodNoDecayModule), "UpdateFoodPre", (Type[])null), new HarmonyMethod(typeof(FoodNoDecayModule), "UpdateFoodPost", (Type[])null), new HarmonyMethod(typeof(FoodNoDecayModule), "UpdateFoodTranspiler", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Hud), "UpdateFood", new Type[1] { typeof(Player) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("Hud.UpdateFood(Player) not found"); } Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(FoodNoDecayModule), "HudFoodPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (_selfTest.Value) { MethodInfo methodInfo3 = AccessTools.Method(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null); if (methodInfo3 == null) { throw new Exception("ObjectDB.Awake() not found"); } MethodInfo methodInfo4 = AccessTools.Method(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null); if (methodInfo4 == null) { throw new Exception("ObjectDB.CopyOtherDB(ObjectDB) not found"); } Harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(FoodNoDecayModule), "ObjectDBPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(FoodNoDecayModule), "ObjectDBPost", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } private static void ObjectDBPost(ObjectDB __instance) { if (_self == null || !_self.Active || _selfTest == null || !_selfTest.Value) { return; } try { FeatureModule.Log.LogInfo((object)SelfTest()); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[FoodNoDecay] SelfTest threw: " + ex)); } } internal static float Fraction(float frac) { float num = Mathf.Pow(frac, (_curveExponent != null) ? _curveExponent.Value : 0.3f); float num2 = Mathf.Clamp01((_keepFraction != null) ? _keepFraction.Value : 1f); return Mathf.Max(num, num2); } public static float DecayFraction(float frac, float vanillaExponent) { if (!_localTick || !Live()) { return Mathf.Pow(frac, vanillaExponent); } return Fraction(frac); } private static void UpdateFoodPre(Player __instance) { _localTick = (Object)(object)__instance != (Object)null && (Object)(object)__instance == (Object)(object)Player.m_localPlayer; } private static void UpdateFoodPost() { _localTick = false; } private static IEnumerable UpdateFoodTranspiler(IEnumerable instructions) { MethodInfo methodInfo = AccessTools.Method(typeof(FoodNoDecayModule), "DecayFraction", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("FoodNoDecayModule.DecayFraction not found"); } List list = new List(instructions); int num = 0; for (int i = 0; i < list.Count; i++) { if (list[i].opcode != OpCodes.Call) { continue; } MethodInfo methodInfo2 = list[i].operand as MethodInfo; if (!(methodInfo2 == null) && !(methodInfo2.DeclaringType != typeof(Mathf)) && !(methodInfo2.Name != "Pow")) { ParameterInfo[] parameters = methodInfo2.GetParameters(); if (parameters.Length == 2 && !(parameters[0].ParameterType != typeof(float)) && !(parameters[1].ParameterType != typeof(float))) { list[i].operand = methodInfo; num++; } } } if (num != 1) { throw new Exception("Player.UpdateFood: expected exactly 1 Mathf.Pow call to replace, found " + num); } FeatureModule.Log.LogInfo((object)"[FoodNoDecay] UpdateFood decay curve replaced (1 call site)"); return list; } private static void HudFoodPost(Hud __instance, Player player) { //IL_00bf: 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) if (!Live() || _hidePulse == null || !_hidePulse.Value || _pulseErrors >= 3 || (Object)(object)__instance == (Object)null || (Object)(object)player == (Object)null || __instance.m_foodIcons == null || __instance.m_foodTime == null) { return; } try { List foods = player.GetFoods(); if (foods == null) { return; } float num = ((_pulseBelowSeconds != null) ? _pulseBelowSeconds.Value : 0f); int num2 = Mathf.Min(__instance.m_foodIcons.Length, __instance.m_foodTime.Length); for (int i = 0; i < num2 && i < foods.Count; i++) { Food val = foods[i]; if (val != null && (!(num > 0f) || !(val.m_time < num))) { Image val2 = __instance.m_foodIcons[i]; if ((Object)(object)val2 != (Object)null) { ((Graphic)val2).color = Color.white; } TMP_Text val3 = __instance.m_foodTime[i]; if ((Object)(object)val3 != (Object)null) { ((Graphic)val3).color = Color.white; } } } } catch (Exception ex) { if (++_pulseErrors <= 3) { FeatureModule.Log.LogWarning((object)("[FoodNoDecay] HidePulse failed (" + _pulseErrors + "/3): " + ex.Message)); } } } private string Numbers() { return "KeepFraction=" + ((_keepFraction != null) ? _keepFraction.Value.ToString("0.###") : "?") + " CurveExponent=" + ((_curveExponent != null) ? _curveExponent.Value.ToString("0.###") : "?") + " HidePulse=" + (_hidePulse != null && _hidePulse.Value) + " PulseBelowSeconds=" + ((_pulseBelowSeconds != null) ? _pulseBelowSeconds.Value.ToString("0.###") : "?"); } public override void OnConfigChanged(ConfigEntryBase entry) { if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] " + Numbers())); } if (base.Active && _selfTest != null && _selfTest.Value && (Object)(object)ObjectDB.instance != (Object)null) { try { FeatureModule.Log.LogInfo((object)SelfTest()); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[FoodNoDecay] SelfTest threw: " + ex)); } } } public override string StatusDetail() { return Numbers(); } internal static string SelfTest() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[SelfTest][FoodNoDecay] ").Append((_self != null) ? _self.Numbers() : "(unbound)"); ObjectDB instance = ObjectDB.instance; if ((Object)(object)instance == (Object)null) { stringBuilder.Append("\n ObjectDB not ready"); return stringBuilder.ToString(); } GameObject itemPrefab = instance.GetItemPrefab("CookedMeat"); if ((Object)(object)itemPrefab == (Object)null) { stringBuilder.Append("\n CookedMeat prefab not found in ObjectDB"); return stringBuilder.ToString(); } ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null || component.m_itemData.m_shared == null) { stringBuilder.Append("\n CookedMeat has no ItemDrop/ItemData"); return stringBuilder.ToString(); } SharedData shared = component.m_itemData.m_shared; if (shared.m_foodBurnTime <= 0f) { stringBuilder.Append("\n CookedMeat m_foodBurnTime <= 0, cannot test"); return stringBuilder.ToString(); } float foodBurnTime = shared.m_foodBurnTime; float num = foodBurnTime * 0.1f; stringBuilder.Append("\n CookedMeat burnTime=").Append(foodBurnTime).Append(" starting at ") .Append(num) .Append("s (10% remaining), 3 consecutive 1s ticks:"); float num2 = 0f; float num3 = 0f; float num4 = 0f; bool flag = true; for (int i = 1; i <= 3; i++) { num -= 1f; float num5 = Mathf.Clamp01(num / foodBurnTime); float num6 = Mathf.Pow(num5, 0.3f); float num7 = Fraction(num5); float num8 = shared.m_food * num7; float num9 = shared.m_foodStamina * num7; float num10 = shared.m_foodEitr * num7; if (i == 1) { num2 = num8; num3 = num9; num4 = num10; } else if (num8 != num2 || num9 != num3 || num10 != num4) { flag = false; } stringBuilder.Append("\n tick ").Append(i).Append(" t=") .Append(num) .Append(" vanilla f=") .Append(num6.ToString("0.#####")) .Append(" -> health=") .Append((shared.m_food * num6).ToString("0.####")) .Append(" | kept f=") .Append(num7.ToString("0.#####")) .Append(" -> health=") .Append(num8.ToString("0.####")) .Append(" stamina=") .Append(num9.ToString("0.####")) .Append(" eitr=") .Append(num10.ToString("0.####")); } stringBuilder.Append("\n ").Append(flag ? "PASS" : "FAIL").Append(" the kept values are identical across all 3 ticks (no oscillation, so ") .Append("SetMaxHealth/SetMaxStamina/SetMaxEitr never see a rising max and never flash the bars)"); float num11 = Mathf.Clamp01((_keepFraction != null) ? _keepFraction.Value : 1f); float num12 = Fraction(0f); stringBuilder.Append("\n ").Append((Mathf.Abs(num12 - num11) < 0.0001f) ? "PASS" : "FAIL").Append(" at 0 time left the fraction is the KeepFraction floor (") .Append(num12.ToString("0.####")) .Append(" vs ") .Append(num11.ToString("0.####")) .Append(")"); return stringBuilder.ToString(); } } internal sealed class LoadoutSpec { public string RightPrefab = ""; public int RightQuality = 1; public int RightVariant; public string LeftPrefab = ""; public int LeftQuality = 1; public int LeftVariant; public bool IsEmpty { get { if (string.IsNullOrEmpty(RightPrefab)) { return string.IsNullOrEmpty(LeftPrefab); } return false; } } public override string ToString() { return (string.IsNullOrEmpty(RightPrefab) ? "-" : RightPrefab) + " / " + (string.IsNullOrEmpty(LeftPrefab) ? "-" : LeftPrefab); } } internal sealed class LoadoutsModule : FeatureModule { public const string KeyPrefix = "nvlb.loadout"; public const int MaxSlots = 4; private static LoadoutsModule _inst; private ConfigEntry _slots; private ConfigEntry _key1; private ConfigEntry _key2; private ConfigEntry _saveModifier; private static KeyCode[] _keys = (KeyCode[])(object)new KeyCode[0]; private static KeyCode _modifier = (KeyCode)306; private static int _tickErrors; public override string Name => "Loadouts"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Loadouts"; private int SlotCount => Mathf.Clamp(_slots.Value, 0, 4); protected override void Bind() { _slots = BindSynced("Slots", 2, "Server: how many weapon loadouts each player gets (0-" + 4 + ")."); _key1 = BindLocal("Loadout1Key", "V", "Local: key that equips loadout 1. Unity KeyCode name, or None."); _key2 = BindLocal("Loadout2Key", "B", "Local: key that equips loadout 2. Unity KeyCode name, or None."); _saveModifier = BindLocal("SaveModifier", "LeftControl", "Local: hold this and press a loadout key to SAVE what you are currently holding into that loadout instead of equipping it."); ParseKeys(); } public override void OnConfigChanged(ConfigEntryBase entry) { ParseKeys(); } private void ParseKeys() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected I4, but got Unknown //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) List list = new List { _key1.Value, _key2.Value, "None", "None" }; KeyCode[] array = (KeyCode[])(object)new KeyCode[4]; for (int i = 0; i < 4; i++) { array[i] = (KeyCode)(int)ParseKey(list[i]); } _keys = array; _modifier = ParseKey(_saveModifier.Value); } private static KeyCode ParseKey(string s) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) s = (s ?? "").Trim(); if (s.Length == 0) { return (KeyCode)0; } try { return (KeyCode)Enum.Parse(typeof(KeyCode), s, ignoreCase: true); } catch { FeatureModule.Log.LogWarning((object)("[Loadouts] '" + s + "' is not a Unity KeyCode - that binding is off")); return (KeyCode)0; } } protected override void ApplyPatches() { //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown _inst = this; MethodInfo methodInfo = AccessTools.Method(typeof(Player), "Update", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("Player.Update() not found"); } if (AccessTools.Method(typeof(Humanoid), "EquipItem", new Type[2] { typeof(ItemData), typeof(bool) }, (Type[])null) == null) { throw new Exception("Humanoid.EquipItem(ItemData,bool) not found"); } if (AccessTools.Method(typeof(Humanoid), "UnequipItem", new Type[2] { typeof(ItemData), typeof(bool) }, (Type[])null) == null) { throw new Exception("Humanoid.UnequipItem(ItemData,bool) not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(LoadoutsModule), "PlayerUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void PlayerUpdatePostfix(Player __instance) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (_inst == null || !_inst.Active || !FeatureModule.ClientActive() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { if (!InputAllowed(__instance)) { return; } bool flag = (int)_modifier != 0 && ZInput.GetKey(_modifier, false); for (int i = 0; i < _inst.SlotCount && i < _keys.Length; i++) { if (_keys[i] && ZInput.GetKeyDown(_keys[i], false)) { if (flag) { SaveLoadout(__instance, i + 1); } else { ApplyLoadout(__instance, i + 1); } } } } catch (Exception ex) { if (_tickErrors++ < 3) { FeatureModule.Log.LogError((object)("[Loadouts] input tick failed (" + _tickErrors + "/3): " + ex)); } } } private static bool InputAllowed(Player me) { if (!((Character)me).TakeInput()) { return false; } if (Hud.InRadial() || Hud.IsPieceSelectionVisible()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } if (Console.IsVisible() || TextInput.IsVisible()) { return false; } if (InventoryGui.IsVisible() || StoreGui.IsVisible() || Menu.IsVisible() || Minimap.IsOpen()) { return false; } return true; } internal static void SaveLoadout(Player p, int slot) { LoadoutSpec loadoutSpec = new LoadoutSpec(); Fill(((Humanoid)p).RightItem, ref loadoutSpec.RightPrefab, ref loadoutSpec.RightQuality, ref loadoutSpec.RightVariant); Fill(((Humanoid)p).LeftItem, ref loadoutSpec.LeftPrefab, ref loadoutSpec.LeftQuality, ref loadoutSpec.LeftVariant); if (loadoutSpec.IsEmpty) { p.m_customData.Remove("nvlb.loadout" + slot); ((Character)p).Message((MessageType)2, "Loadout " + slot + " cleared", 0, (Sprite)null); FeatureModule.Log.LogInfo((object)("[Loadouts] slot " + slot + " cleared (nothing in hand)")); } else { p.m_customData["nvlb.loadout" + slot] = Encode(loadoutSpec); ((Character)p).Message((MessageType)2, "Loadout " + slot + " saved: " + Pretty(p, loadoutSpec), 0, (Sprite)null); FeatureModule.Log.LogInfo((object)("[Loadouts] slot " + slot + " saved: " + loadoutSpec)); } } private static void Fill(ItemData item, ref string prefab, ref int quality, ref int variant) { if (item != null) { string text = SlotBlob.PrefabNameOf(item); if (text != null) { prefab = text; quality = item.m_quality; variant = item.m_variant; } } } internal static void ApplyLoadout(Player p, int slot) { if (!p.m_customData.TryGetValue("nvlb.loadout" + slot, out var value) || string.IsNullOrEmpty(value)) { ((Character)p).Message((MessageType)2, "Loadout " + slot + " is empty - hold the save key and press it again to store what you hold", 0, (Sprite)null); return; } LoadoutSpec loadoutSpec = Decode(value); if (loadoutSpec == null) { FeatureModule.Log.LogWarning((object)("[Loadouts] slot " + slot + " is unreadable: " + value)); ((Character)p).Message((MessageType)2, "Loadout " + slot + " is unreadable", 0, (Sprite)null); return; } Inventory inventory = ((Humanoid)p).GetInventory(); ItemData val = Find(inventory, loadoutSpec.RightPrefab, loadoutSpec.RightQuality, loadoutSpec.RightVariant); ItemData val2 = Find(inventory, loadoutSpec.LeftPrefab, loadoutSpec.LeftQuality, loadoutSpec.LeftVariant); if (val == null && val2 == null) { ((Character)p).Message((MessageType)2, "Loadout " + slot + ": nothing from it is in your inventory", 0, (Sprite)null); return; } int num = 0; if (val != null && (((Humanoid)p).IsItemEquiped(val) || ((Humanoid)p).EquipItem(val, false))) { num++; } bool flag = val != null && val.IsTwoHanded(); if (val2 != null && !flag && val2 != val) { if (((Humanoid)p).IsItemEquiped(val2) || ((Humanoid)p).EquipItem(val2, false)) { num++; } } else if (flag && ((Humanoid)p).LeftItem != null && ((Humanoid)p).LeftItem != val) { ((Humanoid)p).UnequipItem(((Humanoid)p).LeftItem, false); } ((Character)p).Message((MessageType)2, "Loadout " + slot + ": " + Pretty(p, loadoutSpec), 0, (Sprite)null); FeatureModule.Log.LogInfo((object)("[Loadouts] slot " + slot + " applied, " + num + " item(s) equipped: " + loadoutSpec)); } internal static ItemData Find(Inventory inv, string prefab, int quality, int variant) { if (inv == null || string.IsNullOrEmpty(prefab)) { return null; } ItemData val = null; List allItems = inv.GetAllItems(); for (int i = 0; i < allItems.Count; i++) { ItemData val2 = allItems[i]; if (!(SlotBlob.PrefabNameOf(val2) != prefab)) { if (val2.m_quality == quality && val2.m_variant == variant) { return val2; } if (val == null || val2.m_quality > val.m_quality) { val = val2; } } } return val; } private static string Pretty(Player p, LoadoutSpec spec) { Inventory inventory = ((Humanoid)p).GetInventory(); ItemData val = Find(inventory, spec.RightPrefab, spec.RightQuality, spec.RightVariant); ItemData val2 = Find(inventory, spec.LeftPrefab, spec.LeftQuality, spec.LeftVariant); string text = ((val != null && val.m_shared != null) ? val.m_shared.m_name : spec.RightPrefab); string text2 = ((val2 != null && val2.m_shared != null) ? val2.m_shared.m_name : spec.LeftPrefab); if (string.IsNullOrEmpty(text2) || text2 == "-") { return text; } return text + " + " + text2; } internal static string Encode(LoadoutSpec spec) { if (spec == null) { return ""; } return "1|" + Hand(spec.RightPrefab, spec.RightQuality, spec.RightVariant) + "|" + Hand(spec.LeftPrefab, spec.LeftQuality, spec.LeftVariant); } private static string Hand(string prefab, int quality, int variant) { if (string.IsNullOrEmpty(prefab)) { return "-"; } return prefab + ":" + quality.ToString(CultureInfo.InvariantCulture) + ":" + variant.ToString(CultureInfo.InvariantCulture); } internal static LoadoutSpec Decode(string s) { if (string.IsNullOrEmpty(s)) { return null; } string[] array = s.Split(new char[1] { '|' }); if (array.Length != 3 || array[0] != "1") { return null; } LoadoutSpec loadoutSpec = new LoadoutSpec(); if (!Hand(array[1], ref loadoutSpec.RightPrefab, ref loadoutSpec.RightQuality, ref loadoutSpec.RightVariant)) { return null; } if (!Hand(array[2], ref loadoutSpec.LeftPrefab, ref loadoutSpec.LeftQuality, ref loadoutSpec.LeftVariant)) { return null; } return loadoutSpec; } private static bool Hand(string s, ref string prefab, ref int quality, ref int variant) { if (s == "-") { prefab = ""; quality = 1; variant = 0; return true; } string[] array = s.Split(new char[1] { ':' }); if (array.Length != 3) { return false; } if (!int.TryParse(array[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out quality)) { return false; } if (!int.TryParse(array[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out variant)) { return false; } prefab = array[0]; return true; } public override string StatusDetail() { string text = "slots=" + SlotCount + " keys=" + _key1.Value + "," + _key2.Value + " saveModifier=" + _saveModifier.Value; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { for (int i = 1; i <= SlotCount; i++) { localPlayer.m_customData.TryGetValue("nvlb.loadout" + i, out var value); LoadoutSpec loadoutSpec = Decode(value); text = text + " L" + i + "=" + ((loadoutSpec == null) ? "-" : loadoutSpec.ToString()); } } return text; } } internal sealed class FastMiningModule : FeatureModule { public const string DefaultOreNodes = "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4"; private ConfigEntry _speedMult; private ConfigEntry _dropMult; private ConfigEntry _ignoreToolTier; private ConfigEntry _oreNodes; private ConfigEntry _selfTest; private static FastMiningModule _self; private readonly Dictionary _allow = new Dictionary(); private bool _allowResolved; private string _allowSummary = "(not resolved yet)"; private bool _selfTestDone; public override string Name => "FastMining"; public override ModuleSide Side { get { if (_selfTest == null || !_selfTest.Value) { return ModuleSide.Client; } return ModuleSide.Both; } } public override string Section => "Mining"; protected override string EnabledDescription => "Mining ore nodes whose material tier is behind the frontier is faster (and optionally drops more). Runs on the node-owning client - the same for every player who has the mod."; private static bool Live() { if (_self != null && _self.Active) { return FeatureModule.ClientActive(); } return false; } protected override void Bind() { _self = this; _speedMult = BindSynced("SpeedMultiplier", 3f, "Pickaxe damage multiplier applied to a hit on an ore node whose material tier is behind the frontier (see [Frontier] TiersBehind). 1.0 = vanilla speed."); _dropMult = BindSynced("DropMultiplier", 1f, "Extra-drops multiplier for the same behind-the-frontier ore nodes. 1.0 = vanilla drop count. 2.0 = always double, 1.5 = 50% chance of one extra full copy of the drop list, etc."); _ignoreToolTier = BindSynced("IgnoreToolTier", defaultValue: false, "Let a pickaxe below the node's required tool tier mine a behind-the-frontier ore node anyway (raises the hit's tool tier to the node's own requirement before the vanilla tool-tier check runs)."); _oreNodes = BindSynced("OreNodes", "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4", "Ore node prefabs FastMining applies to, as name:tier,name:tier. The tier is the material tier checked against the frontier (see [Tiers]/[Frontier]) - it does not have to match [Tiers] MaterialTiers, but normally should. Names are resolved against ZNetScene's prefab list at runtime; unknown names are logged and ignored."); _selfTest = BindLocal("SelfTest", defaultValue: false, "Diagnostic, local only, never synced. On next ZNetScene load, logs each configured ore prefab's component family (Destructible / MineRock5 / MineRock) and m_minToolTier, then runs the pure speed-multiplier function against rock4_copper and silvervein with a fake 30-damage hit and logs the result. Changes no game state. Leave false in normal use."); } protected override void ApplyPatches() { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Expected O, but got Unknown //IL_0083: Expected O, but got Unknown //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Expected O, but got Unknown //IL_0114: Expected O, but got Unknown //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Expected O, but got Unknown //IL_01a5: Expected O, but got Unknown //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01fa: Expected O, but got Unknown //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Destructible), "RPC_Damage", new Type[2] { typeof(long), typeof(HitData) }, (Type[])null); if (methodInfo == null) { throw new Exception("Destructible.RPC_Damage(long,HitData) not found"); } Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FastMiningModule), "DestructiblePrefix", (Type[])null), new HarmonyMethod(typeof(FastMiningModule), "RestoreDropContext", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo2 = AccessTools.Method(typeof(MineRock5), "RPC_Damage", new Type[3] { typeof(long), typeof(HitData), typeof(int) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("MineRock5.RPC_Damage(long,HitData,int) not found"); } Harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(FastMiningModule), "MineRock5Prefix", (Type[])null), new HarmonyMethod(typeof(FastMiningModule), "RestoreDropContext", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo3 = AccessTools.Method(typeof(MineRock), "RPC_Hit", new Type[3] { typeof(long), typeof(HitData), typeof(int) }, (Type[])null); if (methodInfo3 == null) { throw new Exception("MineRock.RPC_Hit(long,HitData,int) not found"); } Harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(FastMiningModule), "MineRockPrefix", (Type[])null), new HarmonyMethod(typeof(FastMiningModule), "RestoreDropContext", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo4 = AccessTools.Method(typeof(DropTable), "GetDropList", Type.EmptyTypes, (Type[])null); if (methodInfo4 == null) { throw new Exception("DropTable.GetDropList() not found"); } Harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeof(FastMiningModule), "GetDropListPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (_selfTest.Value) { MethodInfo methodInfo5 = AccessTools.Method(typeof(ZNetScene), "Awake", (Type[])null, (Type[])null); if (methodInfo5 == null) { throw new Exception("ZNetScene.Awake() not found"); } Harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeof(FastMiningModule), "ZNetSceneReady", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } FeatureModule.Log.LogInfo((object)("[" + Name + "] SpeedMultiplier=" + _speedMult.Value.ToString("0.##") + "x DropMultiplier=" + _dropMult.Value.ToString("0.##") + "x IgnoreToolTier=" + _ignoreToolTier.Value + " OreNodes=" + _oreNodes.Value + " SelfTest=" + _selfTest.Value)); } public override void Disable() { base.Disable(); if (_self == this) { _self = null; } } public override void OnConfigChanged(ConfigEntryBase entry) { if (entry != null) { if (entry.Definition.Key == "OreNodes") { _allowResolved = false; _allow.Clear(); FeatureModule.Log.LogInfo((object)("[" + Name + "] OreNodes changed, allowlist will be re-resolved on next use")); } else if (base.Active) { FeatureModule.Log.LogInfo((object)("[" + Name + "] SpeedMultiplier=" + _speedMult.Value.ToString("0.##") + "x DropMultiplier=" + _dropMult.Value.ToString("0.##") + "x IgnoreToolTier=" + _ignoreToolTier.Value)); } } } public override string StatusDetail() { EnsureAllowlist(); List list = new List(); foreach (KeyValuePair item in _allow) { if (Tiers.IsBehind(item.Value.Tier)) { list.Add(item.Value.Name); } } list.Sort(StringComparer.OrdinalIgnoreCase); return "SpeedMultiplier=" + _speedMult.Value.ToString("0.##") + "x DropMultiplier=" + _dropMult.Value.ToString("0.##") + "x IgnoreToolTier=" + _ignoreToolTier.Value + " ore=" + _allowSummary + " qualifying=[" + string.Join(",", list.ToArray()) + "]"; } private void EnsureAllowlist() { if (_allowResolved || (Object)(object)ZNetScene.instance == (Object)null) { return; } _allow.Clear(); List list = new List(); List list2 = new List(); string[] array = ((_oreNodes == null) ? "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4" : _oreNodes.Value).Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string[] array2 = text.Split(new char[1] { ':' }); string text2 = array2[0].Trim(); if (text2.Length != 0) { int result = 1; if (array2.Length > 1 && !int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) { FeatureModule.Log.LogWarning((object)("[" + Name + "] bad tier in OreNodes entry '" + text + "', using 1")); result = 1; } int stableHashCode = StringExtensionMethods.GetStableHashCode(text2); GameObject prefab = ZNetScene.instance.GetPrefab(stableHashCode); if ((Object)(object)prefab == (Object)null) { list2.Add(text2); continue; } string text3 = FamilyOf(prefab); _allow[stableHashCode] = new OrePrefab { Hash = stableHashCode, Name = text2, Tier = result, Family = text3 }; list.Add(text2 + ":" + result + "(" + text3 + ")"); } } _allowResolved = true; _allowSummary = _allow.Count + "/" + (list.Count + list2.Count); FeatureModule.Log.LogInfo((object)("[" + Name + "] ore allowlist resolved: " + ((list.Count == 0) ? "(none)" : string.Join(", ", list.ToArray())))); if (list2.Count > 0) { FeatureModule.Log.LogWarning((object)("[" + Name + "] OreNodes prefab(s) NOT found in ZNetScene (ignored): " + string.Join(", ", list2.ToArray()))); } } private static string FamilyOf(GameObject go) { MineRock5 componentInChildren = go.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { return "MineRock5,minToolTier=" + componentInChildren.m_minToolTier; } MineRock componentInChildren2 = go.GetComponentInChildren(true); if ((Object)(object)componentInChildren2 != (Object)null) { return "MineRock,minToolTier=" + componentInChildren2.m_minToolTier; } Destructible componentInChildren3 = go.GetComponentInChildren(true); if ((Object)(object)componentInChildren3 != (Object)null) { return "Destructible,minToolTier=" + componentInChildren3.m_minToolTier; } return "unknown-family"; } private static void DestructiblePrefix(Destructible __instance, HitData hit) { if (!Live() || hit == null) { return; } try { ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return; } ZDO zDO = nview.GetZDO(); if (zDO != null) { _self.EnsureAllowlist(); if (_self._allow.TryGetValue(zDO.GetPrefab(), out OrePrefab value) && Tiers.IsBehind(value.Tier)) { Apply(hit, __instance.m_minToolTier); } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[FastMining] Destructible prefix failed: " + ex.Message)); } } private static void MineRock5Prefix(MineRock5 __instance, HitData hit) { if (!Live() || hit == null) { return; } try { ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return; } ZDO zDO = nview.GetZDO(); if (zDO != null) { _self.EnsureAllowlist(); if (_self._allow.TryGetValue(zDO.GetPrefab(), out OrePrefab value) && Tiers.IsBehind(value.Tier)) { Apply(hit, __instance.m_minToolTier); } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[FastMining] MineRock5 prefix failed: " + ex.Message)); } } private static void MineRockPrefix(MineRock __instance, HitData hit) { if (!Live() || hit == null) { return; } try { ZNetView nview = __instance.m_nview; if ((Object)(object)nview == (Object)null || !nview.IsValid()) { return; } ZDO zDO = nview.GetZDO(); if (zDO != null) { _self.EnsureAllowlist(); if (_self._allow.TryGetValue(zDO.GetPrefab(), out OrePrefab value) && Tiers.IsBehind(value.Tier)) { Apply(hit, __instance.m_minToolTier); } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[FastMining] MineRock prefix failed: " + ex.Message)); } } private static void Apply(HitData hit, int minToolTier) { float value = _self._speedMult.Value; if (value > 0f && value != 1f) { hit.m_damage.m_pickaxe *= value; } if (_self._ignoreToolTier.Value && hit.m_toolTier < minToolTier) { hit.m_toolTier = (short)minToolTier; } float value2 = _self._dropMult.Value; if (value2 > 1f) { DropContext.Multiplier = value2; } } private static void RestoreDropContext() { DropContext.Multiplier = 0f; } private static void GetDropListPostfix(List __result) { float multiplier = DropContext.Multiplier; if (multiplier <= 1f || __result == null || __result.Count == 0) { return; } try { float num = multiplier - 1f; int num2 = Mathf.FloorToInt(num); float num3 = num - (float)num2; List collection = new List(__result); for (int i = 0; i < num2; i++) { __result.AddRange(collection); } if (num3 > 0f && Random.value < num3) { __result.AddRange(collection); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[FastMining] drop multiplier failed: " + ex.Message)); } } private static void ZNetSceneReady(ZNetScene __instance) { if (_self != null && _self._selfTest != null && _self._selfTest.Value) { _self.RunSelfTest(); } } private void RunSelfTest() { if (_selfTestDone) { return; } _selfTestDone = true; try { EnsureAllowlist(); FeatureModule.Log.LogInfo((object)("[FastMining][SelfTest] --- begin --- WorldTier=" + Frontier.Describe() + " TiersBehind=" + ((Frontier.TiersBehind == null) ? 1 : Frontier.TiersBehind.Value))); foreach (KeyValuePair item in _allow) { FeatureModule.Log.LogInfo((object)("[FastMining][SelfTest] " + item.Value.Name + " tier=" + item.Value.Tier + " family=" + item.Value.Family)); } ProbeSpeed("rock4_copper", 30f); ProbeSpeed("silvervein", 30f); FeatureModule.Log.LogInfo((object)"[FastMining][SelfTest] --- end ---"); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[FastMining][SelfTest] threw: " + ex)); } } private void ProbeSpeed(string name, float baseDamage) { int stableHashCode = StringExtensionMethods.GetStableHashCode(name); if (!_allow.TryGetValue(stableHashCode, out OrePrefab value)) { FeatureModule.Log.LogWarning((object)("[FastMining][SelfTest] " + name + " not in allowlist, skipped")); return; } bool flag = Tiers.IsBehind(value.Tier); float num = (flag ? (baseDamage * _speedMult.Value) : baseDamage); FeatureModule.Log.LogInfo((object)("[FastMining][SelfTest] " + name + " tier=" + value.Tier + " IsBehind=" + flag + " pickaxeDamage " + baseDamage.ToString("0.##") + " -> " + num.ToString("0.##"))); } } internal sealed class OrePrefab { public int Hash; public string Name; public int Tier; public string Family; } internal static class DropContext { internal static float Multiplier; } internal sealed class CombatRechargeModule : FeatureModule { private static CombatRechargeModule _inst; private ConfigEntry _perHitDealt; private ConfigEntry _perHitTaken; private ConfigEntry _maxPerSecond; private ConfigEntry _affectAllSlots; private ConfigEntry _countPlayerTargets; private ConfigEntry _showMessages; private static float _windowStart; private static float _spentThisWindow; private static int _hitsDealt; private static int _hitsTaken; private static float _totalGranted; private static float _totalClipped; private static int _errors; public override string Name => "CombatRecharge"; public override string Section => "Recharge"; public override ModuleSide Side => ModuleSide.Client; protected override void Bind() { _perHitDealt = BindSynced("SecondsPerHitDealt", 2f, "Seconds taken off your power cooldowns for every hit you land."); _perHitTaken = BindSynced("SecondsPerHitTaken", 3f, "Seconds taken off your power cooldowns for every hit you take."); _maxPerSecond = BindSynced("MaxPerSecond", 10f, "Hard cap on how many cooldown seconds one real second of combat can remove. Stops multi-hit AoE and damage-over-time from emptying a cooldown instantly."); _affectAllSlots = BindSynced("AffectAllSlots", defaultValue: true, "Also shorten the DualPowers extra slots. False = only the vanilla slot 1 cooldown."); _countPlayerTargets = BindSynced("CountPlayerTargets", defaultValue: false, "Count hits you land on other players (PvP) as well as on creatures."); _showMessages = BindLocal("ShowMessages", defaultValue: false, "Machine-local. Pop a small message every time a hit shortens a cooldown. Noisy - for tuning only."); _inst = this; } protected override void ApplyPatches() { //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Character), "Damage", new Type[1] { typeof(HitData) }, (Type[])null); if (methodInfo == null) { throw new Exception("Character.Damage(HitData) not found"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Player), "OnDamaged", new Type[1] { typeof(HitData) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("Player.OnDamaged(HitData) not found"); } if (AccessTools.Method(typeof(HitData), "GetAttacker", (Type[])null, (Type[])null) == null) { throw new Exception("HitData.GetAttacker() not found"); } if (AccessTools.Method(typeof(HitData), "GetTotalDamage", (Type[])null, (Type[])null) == null) { throw new Exception("HitData.GetTotalDamage() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(CombatRechargeModule), "DamagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(CombatRechargeModule), "OnDamagedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[CombatRecharge] dealt=-" + _perHitDealt.Value + "s taken=-" + _perHitTaken.Value + "s cap=" + _maxPerSecond.Value + "s/s allSlots=" + _affectAllSlots.Value + " countPlayerTargets=" + _countPlayerTargets.Value)); } private static void DamagePostfix(Character __instance, HitData hit) { if (_inst == null || !_inst.Active || !FeatureModule.ClientActive()) { return; } try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && hit != null && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance == (Object)(object)localPlayer) && !((Object)(object)hit.GetAttacker() != (Object)(object)localPlayer) && (_inst._countPlayerTargets.Value || !__instance.IsPlayer()) && !(hit.GetTotalDamage() <= 0f)) { _hitsDealt++; Apply(localPlayer, _inst._perHitDealt.Value, "hit"); } } catch (Exception ex) { if (_errors++ < 3) { FeatureModule.Log.LogError((object)("[CombatRecharge] dealt hook failed (" + _errors + "/3): " + ex)); } } } private static void OnDamagedPostfix(Player __instance, HitData hit) { if (_inst == null || !_inst.Active || !FeatureModule.ClientActive()) { return; } try { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && hit != null && !((Object)(object)__instance != (Object)(object)localPlayer) && !(hit.GetTotalDamage() <= 0f)) { _hitsTaken++; Apply(localPlayer, _inst._perHitTaken.Value, "hurt"); } } catch (Exception ex) { if (_errors++ < 3) { FeatureModule.Log.LogError((object)("[CombatRecharge] taken hook failed (" + _errors + "/3): " + ex)); } } } private static void Apply(Player me, float seconds, string why) { if (seconds <= 0f) { return; } float num = Grant(seconds, _inst._maxPerSecond.Value, Time.unscaledTime); if (!(num <= 0f)) { me.m_guardianPowerCooldown = Mathf.Max(0f, me.m_guardianPowerCooldown - num); if (_inst._affectAllSlots.Value) { DualPowersModule.ReduceCooldowns(num); } if (_inst._showMessages.Value) { ((Character)me).Message((MessageType)1, "Power recharge " + why + ": -" + num.ToString("0.#") + "s", 0, (Sprite)null); } } } public static float Grant(float want, float maxPerSecond, float now) { if (want <= 0f) { return 0f; } if (maxPerSecond <= 0f) { _totalGranted += want; return want; } if (now - _windowStart >= 1f || now < _windowStart) { _windowStart = now; _spentThisWindow = 0f; } float num = Mathf.Max(0f, maxPerSecond - _spentThisWindow); float num2 = Mathf.Min(want, num); _spentThisWindow += num2; _totalGranted += num2; _totalClipped += want - num2; return num2; } public static void ResetWindow() { _windowStart = 0f; _spentThisWindow = 0f; } public override void OnConfigChanged(ConfigEntryBase entry) { ResetWindow(); } public override string StatusDetail() { if (_perHitDealt == null) { return null; } return "dealt=-" + _perHitDealt.Value + "s taken=-" + _perHitTaken.Value + "s cap=" + _maxPerSecond.Value + "s/s allSlots=" + _affectAllSlots.Value + " hits=" + _hitsDealt + "/" + _hitsTaken + " granted=" + _totalGranted.ToString("0.#") + "s clipped=" + _totalClipped.ToString("0.#") + "s"; } public static void LogSelfTest(float cooldown, int dealt, int taken) { float num = ((_inst != null && _inst._perHitDealt != null) ? _inst._perHitDealt.Value : 2f); float num2 = ((_inst != null && _inst._perHitTaken != null) ? _inst._perHitTaken.Value : 3f); float num3 = ((_inst != null && _inst._maxPerSecond != null) ? _inst._maxPerSecond.Value : 10f); float num4 = (float)dealt * num + (float)taken * num2; ResetWindow(); float num5 = cooldown; float num6 = 0f; for (int i = 0; i < dealt; i++) { float num7 = Grant(num, num3, 100f); num6 += num7; num5 = Mathf.Max(0f, num5 - num7); } for (int j = 0; j < taken; j++) { float num8 = Grant(num2, num3, 100f); num6 += num8; num5 = Mathf.Max(0f, num5 - num8); } FeatureModule.Log.LogInfo((object)("[CombatRecharge] SelfTest: " + dealt + " hits dealt (-" + num + "s each) + " + taken + " taken (-" + num2 + "s each) = " + num4 + "s wanted, cap " + num3 + "s/s")); FeatureModule.Log.LogInfo((object)("[CombatRecharge] SelfTest: burst (all in one second): granted " + num6 + "s, cooldown " + cooldown + "s -> " + num5 + "s" + ((Mathf.Abs(num6 - Mathf.Min(num4, num3)) < 0.001f) ? " OK" : " *** FAIL ***"))); ResetWindow(); float num9 = cooldown; float num10 = 0f; float num11 = 1000f; for (int k = 0; k < dealt; k++) { float num12 = Grant(num, num3, num11); num11 += 1f; num10 += num12; num9 = Mathf.Max(0f, num9 - num12); } for (int l = 0; l < taken; l++) { float num13 = Grant(num2, num3, num11); num11 += 1f; num10 += num13; num9 = Mathf.Max(0f, num9 - num13); } FeatureModule.Log.LogInfo((object)("[CombatRecharge] SelfTest: spread (one hit per second): granted " + num10 + "s, cooldown " + cooldown + "s -> " + num9 + "s" + ((Mathf.Abs(num10 - num4) < 0.001f) ? " OK" : " *** FAIL ***"))); ResetWindow(); _totalGranted = 0f; _totalClipped = 0f; } } internal sealed class DualPowersModule : FeatureModule { private const float SuppressWindow = 2f; private static DualPowersModule _inst; private static bool _selfTestDone; private ConfigEntry _slots; private ConfigEntry _secondSlotKey; private ConfigEntry _thirdSlotKey; private ConfigEntry _independentCooldowns; private ConfigEntry _cooldownMultiplier; private ConfigEntry _showHud; private ConfigEntry _hudOffsetX; private ConfigEntry _hudOffsetY; private ConfigEntry _selfTest; private static readonly KeyCode[] Keys = (KeyCode[])(object)new KeyCode[2]; private static float _suppressUntil; private static bool _skippedVanilla; private static float _cdBeforeActivate; private static int _activations; private static int _tickErrors; public override string Name => "DualPowers"; public override string Section => "Powers"; public override ModuleSide Side { get { if (_selfTest == null || !_selfTest.Value) { return ModuleSide.Client; } return ModuleSide.Both; } } public static bool IsActive { get { if (_inst != null) { return _inst.Active; } return false; } } public static void ReduceCooldowns(float seconds) { if (IsActive && !(seconds <= 0f)) { PowerSlots.ReduceCooldowns(seconds); } } protected override void Bind() { _slots = BindSynced("Slots", 2, "How many Forsaken powers you can hold at once. 1 = vanilla. 2 = the second slot on SecondSlotKey. 3 works too but you must also set ThirdSlotKey."); _independentCooldowns = BindSynced("IndependentCooldowns", defaultValue: true, "True: every slot has its own cooldown. False: one shared vanilla cooldown, so using either power puts both on cooldown."); _cooldownMultiplier = BindSynced("CooldownMultiplier", 1f, "Multiplies the cooldown every guardian power starts, in every slot. 0.5 = half-length cooldowns, 1 = vanilla."); _secondSlotKey = BindLocal("SecondSlotKey", "G", "Machine-local. UnityEngine.KeyCode name for the second power slot (vanilla's F always stays slot 1). Examples: G, H, LeftAlt, Mouse3, JoystickButton5."); _thirdSlotKey = BindLocal("ThirdSlotKey", "None", "Machine-local. KeyCode for a third slot, only used when Slots = 3. 'None' disables it."); _showHud = BindLocal("ShowHud", defaultValue: true, "Machine-local. Clone the vanilla power icon so slot 2 gets its own icon, name and cooldown readout. Turn off if it clashes with another HUD mod."); _hudOffsetX = BindLocal("HudOffsetX", 0f, "Machine-local. Pixels to move the second power icon sideways from the vanilla one."); _hudOffsetY = BindLocal("HudOffsetY", -56f, "Machine-local. Pixels to move the second power icon vertically (negative = below)."); _selfTest = BindLocal("SelfTest", defaultValue: false, "Diagnostic, machine-local. Runs the module on a dedicated server too and logs a storage + recharge self test at world load. Leave false in normal use."); _inst = this; Push(); } private void Push() { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected I4, but got Unknown //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected I4, but got Unknown PowerSlots.SlotCount = Mathf.Clamp(_slots.Value, 1, 3); PowerSlots.IndependentCooldowns = _independentCooldowns.Value; PowerSlots.CooldownMultiplier = Mathf.Clamp(_cooldownMultiplier.Value, 0f, 100f); Keys[0] = (KeyCode)(int)ParseKey(_secondSlotKey.Value, (KeyCode)103, "SecondSlotKey"); if (Keys.Length > 1) { Keys[1] = (KeyCode)(int)ParseKey(_thirdSlotKey.Value, (KeyCode)0, "ThirdSlotKey"); } PowerHud.SetOffset(new Vector2(_hudOffsetX.Value, _hudOffsetY.Value)); } private unsafe static KeyCode ParseKey(string s, KeyCode fallback, string what) { //IL_0077: 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_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(s)) { return (KeyCode)0; } try { return (KeyCode)Enum.Parse(typeof(KeyCode), s.Trim(), ignoreCase: true); } catch { FeatureModule.Log.LogWarning((object)("[DualPowers] " + what + " = '" + s + "' is not a UnityEngine.KeyCode name, falling back to " + ((object)(*(KeyCode*)(&fallback))/*cast due to .constrained prefix*/).ToString() + ".")); return fallback; } } public override void OnConfigChanged(ConfigEntryBase entry) { Push(); if ((object)entry == _showHud && !_showHud.Value) { PowerHud.Destroy(); } if ((object)entry == _showHud && _showHud.Value) { PowerHud.Reset(); } if ((object)entry == EnabledCfg && !EnabledCfg.Value) { PowerHud.Destroy(); } if ((object)entry == _selfTest) { FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest=" + _selfTest.Value + " takes effect on the next game start (module side is decided at load).")); } } protected override void ApplyPatches() { //IL_0360: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Expected O, but got Unknown //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_038c: Expected O, but got Unknown //IL_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03aa: Expected O, but got Unknown //IL_03ba: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03d4: Expected O, but got Unknown //IL_03d4: Expected O, but got Unknown //IL_03e6: Unknown result type (might be due to invalid IL or missing references) //IL_03f3: Expected O, but got Unknown //IL_0404: Unknown result type (might be due to invalid IL or missing references) //IL_0412: Expected O, but got Unknown //IL_0424: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Expected O, but got Unknown //IL_0443: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Expected O, but got Unknown //IL_0462: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Expected O, but got Unknown //IL_0478: Unknown result type (might be due to invalid IL or missing references) //IL_047f: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ItemStand), "DelayedPowerActivation", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ItemStand.DelayedPowerActivation() not found"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(ItemStand), "IsGuardianPowerActive", new Type[1] { typeof(Humanoid) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("ItemStand.IsGuardianPowerActive(Humanoid) not found"); } MethodInfo methodInfo3 = AccessTools.Method(typeof(Player), "Update", (Type[])null, (Type[])null); if (methodInfo3 == null) { throw new Exception("Player.Update() not found"); } MethodInfo methodInfo4 = AccessTools.Method(typeof(Player), "ActivateGuardianPower", (Type[])null, (Type[])null); if (methodInfo4 == null) { throw new Exception("Player.ActivateGuardianPower() not found"); } MethodInfo methodInfo5 = AccessTools.Method(typeof(Player), "UpdateGuardianPower", new Type[1] { typeof(float) }, (Type[])null); if (methodInfo5 == null) { throw new Exception("Player.UpdateGuardianPower(float) not found"); } MethodInfo methodInfo6 = AccessTools.Method(typeof(Player), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null); if (methodInfo6 == null) { throw new Exception("Player.Save(ZPackage) not found"); } MethodInfo methodInfo7 = AccessTools.Method(typeof(Player), "Load", new Type[1] { typeof(ZPackage) }, (Type[])null); if (methodInfo7 == null) { throw new Exception("Player.Load(ZPackage) not found"); } MethodInfo methodInfo8 = AccessTools.Method(typeof(Player), "ResetCharacter", (Type[])null, (Type[])null); if (methodInfo8 == null) { throw new Exception("Player.ResetCharacter() not found"); } MethodInfo methodInfo9 = AccessTools.Method(typeof(Hud), "UpdateGuardianPower", new Type[1] { typeof(Player) }, (Type[])null); if (methodInfo9 == null) { throw new Exception("Hud.UpdateGuardianPower(Player) not found"); } MethodInfo methodInfo10 = AccessTools.Method(typeof(ObjectDB), "Awake", (Type[])null, (Type[])null); if (methodInfo10 == null) { throw new Exception("ObjectDB.Awake() not found"); } MethodInfo methodInfo11 = AccessTools.Method(typeof(ObjectDB), "CopyOtherDB", new Type[1] { typeof(ObjectDB) }, (Type[])null); if (methodInfo11 == null) { throw new Exception("ObjectDB.CopyOtherDB(ObjectDB) not found"); } if (AccessTools.Method(typeof(Player), "SetGuardianPower", new Type[1] { typeof(string) }, (Type[])null) == null) { throw new Exception("Player.SetGuardianPower(string) not found"); } if (AccessTools.Method(typeof(SEMan), "AddStatusEffect", new Type[4] { typeof(int), typeof(bool), typeof(int), typeof(float) }, (Type[])null) == null) { throw new Exception("SEMan.AddStatusEffect(int,bool,int,float) not found - slot 2 could not be applied"); } if (AccessTools.Method(typeof(Player), "GetPlayersInRange", new Type[3] { typeof(Vector3), typeof(float), typeof(List) }, (Type[])null) == null) { throw new Exception("Player.GetPlayersInRange(Vector3,float,List) not found"); } Type typeFromHandle = typeof(DualPowersModule); Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeFromHandle, "StandPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "StandActivePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "PlayerUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeFromHandle, "ActivatePrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "ActivatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "TickPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeFromHandle, "SavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo7, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "LoadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "ResetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo9, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "HudPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); HarmonyMethod val = new HarmonyMethod(typeFromHandle, "ObjectDBPostfix", (Type[])null); Harmony.Patch((MethodBase)methodInfo10, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo11, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); FeatureModule.Log.LogInfo((object)("[DualPowers] slots=" + PowerSlots.SlotCount + " key2=" + ((object)Unsafe.As(ref Keys[0])/*cast due to .constrained prefix*/).ToString() + ((PowerSlots.SlotCount > 2) ? (" key3=" + ((object)Unsafe.As(ref Keys[1])/*cast due to .constrained prefix*/).ToString()) : "") + " independentCooldowns=" + PowerSlots.IndependentCooldowns + " cooldownMultiplier=" + PowerSlots.CooldownMultiplier + " hud=" + _showHud.Value + " storage=" + PowerSlots.NameKey(1) + "/" + PowerSlots.CooldownKey(1) + " selfTest=" + _selfTest.Value)); } public override void Disable() { PowerHud.Destroy(); base.Disable(); } private static bool StandPrefix(ItemStand __instance) { if (_inst == null || !_inst.Active || !FeatureModule.ClientActive()) { return true; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)__instance == (Object)null || (Object)(object)__instance.m_guardianPower == (Object)null) { return true; } PowerSlots.Bind(localPlayer); string name = ((Object)__instance.m_guardianPower).name; string text = Localization.instance.Localize(__instance.m_guardianPower.m_name); int num = PowerSlots.FindSlot(localPlayer, name); if (num >= 0) { ((Character)localPlayer).Message((MessageType)2, text + " is already in power slot " + (num + 1), 0, (Sprite)null); return false; } int num2 = PowerSlots.FirstEmptySlot(localPlayer); if (num2 < 0) { num2 = PowerSlots.ExtraCount; } if (num2 == 0) { return true; } string name2 = PowerSlots.GetName(localPlayer, num2); PowerSlots.SetPower(localPlayer, num2, name); try { Game.instance.IncrementPlayerStat((PlayerStatType)87, 1f); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] stat increment failed: " + ex.Message)); } ((Character)localPlayer).Message((MessageType)2, text + " -> power slot " + (num2 + 1) + (string.IsNullOrEmpty(name2) ? "" : (" (replaced " + name2 + ")")), 0, (Sprite)null); FeatureModule.Log.LogInfo((object)("[DualPowers] altar granted '" + name + "' to slot " + (num2 + 1) + (string.IsNullOrEmpty(name2) ? "" : (", replacing '" + name2 + "'")))); return false; } catch (Exception ex2) { FeatureModule.Log.LogError((object)("[DualPowers] altar routing failed, falling back to vanilla: " + ex2)); return true; } } private static void StandActivePostfix(ItemStand __instance, Humanoid user, ref bool __result) { if (__result || _inst == null || !_inst.Active || !FeatureModule.ClientActive()) { return; } try { Player val = (Player)(object)((user is Player) ? user : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !((Object)(object)__instance.m_guardianPower == (Object)null) && PowerSlots.FindSlot(val, ((Object)__instance.m_guardianPower).name) >= 0) { __result = true; } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] IsGuardianPowerActive postfix: " + ex.Message)); } } private static void PlayerUpdatePostfix(Player __instance) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (_inst == null || !_inst.Active || !FeatureModule.ClientActive() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } PowerSlots.Bind(__instance); if (PowerSlots.ExtraCount <= 0) { return; } try { if (!InputAllowed(__instance)) { return; } for (int i = 1; i <= PowerSlots.ExtraCount; i++) { KeyCode val = Keys[i - 1]; if ((int)val != 0 && ZInput.GetKeyDown(val, false)) { _inst.TryActivate(__instance, i); } } } catch (Exception ex) { if (_tickErrors++ < 3) { FeatureModule.Log.LogError((object)("[DualPowers] input tick failed (" + _tickErrors + "/3): " + ex)); } } } private static bool InputAllowed(Player me) { if (!((Character)me).TakeInput()) { return false; } if (Hud.InRadial() || Hud.IsPieceSelectionVisible()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } if (Console.IsVisible() || TextInput.IsVisible()) { return false; } if (InventoryGui.IsVisible() || StoreGui.IsVisible() || Menu.IsVisible() || Minimap.IsOpen()) { return false; } return true; } private void TryActivate(Player me, int slot) { //IL_00db: Unknown result type (might be due to invalid IL or missing references) StatusEffect se = PowerSlots.GetSe(me, slot); if ((Object)(object)se == (Object)null) { string name = PowerSlots.GetName(me, slot); ((Character)me).Message((MessageType)2, string.IsNullOrEmpty(name) ? ("No power in slot " + (slot + 1)) : ("Power '" + name + "' in slot " + (slot + 1) + " is unknown to this game"), 0, (Sprite)null); } else if (PowerSlots.GetCooldown(me, slot) > 0f) { ((Character)me).Message((MessageType)2, "$hud_powernotready", 0, (Sprite)null); } else { if ((((Character)me).InAttack() && !((Humanoid)me).HaveQueuedChain()) || ((Character)me).InDodge() || !((Character)me).CanMove() || ((Character)me).IsKnockedBack() || ((Character)me).IsStaggering() || ((Character)me).InMinorAction()) { return; } List list = new List(); Player.GetPlayersInRange(((Component)me).transform.position, 10f, list); int num = se.NameHash(); foreach (Player item in list) { if (!((Object)(object)item == (Object)null)) { SEMan sEMan = ((Character)item).GetSEMan(); if (sEMan != null) { sEMan.AddStatusEffect(num, true, 0, 0f); } } } try { if (me.m_adrenalineGuardianPower != 0f) { ((Character)me).AddAdrenaline(me.m_adrenalineGuardianPower); } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] AddAdrenaline failed: " + ex.Message)); } PowerSlots.SetCooldown(me, slot, se.m_cooldown * PowerSlots.CooldownMultiplier); _activations++; try { Game.instance.IncrementPlayerStat((PlayerStatType)96, 1f); } catch (Exception ex2) { FeatureModule.Log.LogWarning((object)("[DualPowers] stat increment failed: " + ex2.Message)); } _suppressUntil = Time.time + 2f; try { ((Character)me).m_zanim.SetTrigger("gpower"); } catch (Exception ex3) { FeatureModule.Log.LogWarning((object)("[DualPowers] gpower animation trigger failed: " + ex3.Message)); } FeatureModule.Log.LogInfo((object)("[DualPowers] slot " + (slot + 1) + " '" + ((Object)se).name + "' used on " + list.Count + " player(s), cooldown " + (se.m_cooldown * PowerSlots.CooldownMultiplier).ToString("0") + "s")); } } private static bool ActivatePrefix(Player __instance, ref bool __result) { _skippedVanilla = false; if (_inst == null || !_inst.Active || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return true; } if (_suppressUntil > 0f && Time.time <= _suppressUntil) { _suppressUntil = 0f; _skippedVanilla = true; __result = false; return false; } _suppressUntil = 0f; _cdBeforeActivate = __instance.m_guardianPowerCooldown; return true; } private static void ActivatePostfix(Player __instance) { if (_skippedVanilla) { _skippedVanilla = false; } else { if (_inst == null || !_inst.Active || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { if (_cdBeforeActivate <= 0f && __instance.m_guardianPowerCooldown > 0f) { if (PowerSlots.CooldownMultiplier != 1f) { __instance.m_guardianPowerCooldown *= PowerSlots.CooldownMultiplier; } if (!PowerSlots.IndependentCooldowns) { PowerSlots.SetCooldown(__instance, 0, __instance.m_guardianPowerCooldown); } _activations++; } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] activate postfix: " + ex.Message)); } } } private static void TickPostfix(Player __instance, float dt) { if (_inst != null && _inst.Active && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { PowerSlots.Bind(__instance); PowerSlots.Tick(__instance, dt); } } private static void SavePrefix(Player __instance) { if (_inst == null || !_inst.Active || (Object)(object)__instance == (Object)null) { return; } try { if (PowerSlots.IsOwner(__instance)) { PowerSlots.SaveTo(__instance); } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] save flush failed: " + ex.Message)); } } private static void LoadPostfix(Player __instance) { if (_inst == null || !_inst.Active || (Object)(object)__instance == (Object)null) { return; } try { PowerSlots.LoadFrom(__instance); FeatureModule.Log.LogInfo((object)("[DualPowers] loaded " + PowerSlots.Describe(__instance))); } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[DualPowers] load failed: " + ex.Message)); } } private static void ResetPostfix(Player __instance) { if (_inst != null && _inst.Active) { PowerSlots.ResetCooldowns(); } } private static void HudPostfix(Hud __instance, Player player) { if (_inst != null && _inst.Active && FeatureModule.ClientActive() && _inst._showHud.Value && PowerSlots.ExtraCount > 0 && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer)) { PowerSlots.Bind(player); PowerHud.Refresh(__instance, player, 1); } } private static void ObjectDBPostfix(ObjectDB __instance) { if (_inst != null && _inst.Active) { PowerSlots.InvalidateStatusEffects(); if (_inst._selfTest.Value) { RunSelfTest(__instance); } } } public override string StatusDetail() { Player localPlayer = Player.m_localPlayer; string text = (((Object)(object)localPlayer != (Object)null) ? PowerSlots.Describe(localPlayer) : "slots=(no local player)"); return text + " key2=" + ((object)Unsafe.As(ref Keys[0])/*cast due to .constrained prefix*/).ToString() + ((PowerSlots.SlotCount > 2) ? (" key3=" + ((object)Unsafe.As(ref Keys[1])/*cast due to .constrained prefix*/).ToString()) : "") + " independent=" + PowerSlots.IndependentCooldowns + " cdx" + PowerSlots.CooldownMultiplier + " uses=" + _activations; } private static void RunSelfTest(ObjectDB odb) { if (_selfTestDone || (Object)(object)odb == (Object)null || odb.m_StatusEffects == null || odb.m_StatusEffects.Count < 10) { return; } _selfTestDone = true; FeatureModule.Log.LogInfo((object)"[DualPowers] SelfTest: --- begin ---"); int num = 0; if ((Object)(object)odb != (Object)null && odb.m_StatusEffects != null) { foreach (StatusEffect statusEffect in odb.m_StatusEffects) { if (!((Object)(object)statusEffect == (Object)null) && ((Object)statusEffect).name != null && ((Object)statusEffect).name.StartsWith("GP_", StringComparison.Ordinal)) { num++; FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: power '" + ((Object)statusEffect).name + "' m_name=" + statusEffect.m_name + " cooldown=" + statusEffect.m_cooldown + "s hash=" + statusEffect.NameHash())); } } } FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: " + num + " GP_* status effects in ObjectDB (" + (((Object)(object)odb != (Object)null && odb.m_StatusEffects != null) ? odb.m_StatusEffects.Count : 0) + " total)")); Dictionary dictionary = new Dictionary(); dictionary[PowerSlots.NameKey(1)] = "GP_Bonemass"; dictionary[PowerSlots.CooldownKey(1)] = PowerSlots.EncodeCooldown(123.456f); float num2 = PowerSlots.DecodeCooldown(dictionary[PowerSlots.CooldownKey(1)]); FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: storage keys '" + PowerSlots.NameKey(1) + "'='" + dictionary[PowerSlots.NameKey(1)] + "' '" + PowerSlots.CooldownKey(1) + "'='" + dictionary[PowerSlots.CooldownKey(1)] + "' -> decoded " + num2 + ((Mathf.Abs(num2 - 123.456f) < 0.01f) ? " OK" : " *** FAIL ***"))); FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: decode('') = " + PowerSlots.DecodeCooldown("") + ", decode('nonsense') = " + PowerSlots.DecodeCooldown("nonsense") + ", decode('-5') = " + PowerSlots.DecodeCooldown("-5") + ", encode(0) = '" + PowerSlots.EncodeCooldown(0f) + "', encode(-1) = '" + PowerSlots.EncodeCooldown(-1f) + "' (all must be 0)")); FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: slot keys 1.." + 2 + " = " + PowerSlots.NameKey(1) + "/" + PowerSlots.CooldownKey(1) + ", " + PowerSlots.NameKey(2) + "/" + PowerSlots.CooldownKey(2))); CombatRechargeModule.LogSelfTest(300f, 5, 2); FeatureModule.Log.LogInfo((object)("[DualPowers] SelfTest: config slots=" + PowerSlots.SlotCount + " independent=" + PowerSlots.IndependentCooldowns + " cdMultiplier=" + PowerSlots.CooldownMultiplier + " key2=" + ((object)Unsafe.As(ref Keys[0])/*cast due to .constrained prefix*/).ToString())); FeatureModule.Log.LogInfo((object)"[DualPowers] SelfTest: --- end ---"); } } internal static class PowerHud { public static Vector2 Offset = new Vector2(0f, -56f); private static Hud _hud; private static GameObject _clone; private static RectTransform _rt; private static TMP_Text _name; private static TMP_Text _cooldown; private static Image _icon; private static bool _failed; private static bool _built; public static void Refresh(Hud hud, Player player, int slot) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) if (_failed || (Object)(object)hud == (Object)null || (Object)(object)player == (Object)null) { return; } try { if (!Ensure(hud)) { return; } StatusEffect se = PowerSlots.GetSe(player, slot); if ((Object)(object)se == (Object)null) { if (_clone.activeSelf) { _clone.SetActive(false); } return; } float cooldown = PowerSlots.GetCooldown(player, slot); if (!_clone.activeSelf) { _clone.SetActive(true); } if ((Object)(object)_icon != (Object)null) { _icon.sprite = se.m_icon; ((Graphic)_icon).color = ((cooldown <= 0f) ? Color.white : Hud.s_colorRedBlueZeroAlpha); } if ((Object)(object)_name != (Object)null) { _name.text = Localization.instance.Localize(se.m_name); } if ((Object)(object)_cooldown != (Object)null) { _cooldown.text = ((cooldown > 0f) ? StatusEffect.GetTimeString(cooldown, false, false) : Localization.instance.Localize("$hud_ready")); } } catch (Exception ex) { _failed = true; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[DualPowers] second HUD element disabled after an error: " + ex.Message)); Destroy(); } } private unsafe static bool Ensure(Hud hud) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) if (_built && (Object)(object)_clone != (Object)null && _hud == hud) { return true; } if (_hud != hud) { Destroy(); } if ((Object)(object)hud.m_gpRoot == (Object)null || (Object)(object)hud.m_gpIcon == (Object)null || (Object)(object)hud.m_gpName == (Object)null || (Object)(object)hud.m_gpCooldown == (Object)null) { _failed = true; NoVikingLeftBehindPlugin.Log.LogWarning((object)"[DualPowers] Hud.m_gpRoot/m_gpIcon/m_gpName/m_gpCooldown missing - no second power icon (the feature still works, use the hotkey)."); return false; } RectTransform gpRoot = hud.m_gpRoot; TMP_Text[] componentsInChildren = ((Component)gpRoot).GetComponentsInChildren(true); Image[] componentsInChildren2 = ((Component)gpRoot).GetComponentsInChildren(true); int i = IndexOf(componentsInChildren, hud.m_gpName); int i2 = IndexOf(componentsInChildren, hud.m_gpCooldown); int i3 = IndexOf(componentsInChildren2, hud.m_gpIcon); _clone = Object.Instantiate(((Component)gpRoot).gameObject, ((Transform)gpRoot).parent); ((Object)_clone).name = "NVLB_GP2"; _rt = _clone.GetComponent(); if ((Object)(object)_rt == (Object)null) { _failed = true; NoVikingLeftBehindPlugin.Log.LogWarning((object)"[DualPowers] cloned HUD root has no RectTransform - no second power icon."); Destroy(); return false; } _rt.anchoredPosition = gpRoot.anchoredPosition + Offset; ((Transform)_rt).localScale = ((Transform)gpRoot).localScale; TMP_Text[] componentsInChildren3 = ((Component)_rt).GetComponentsInChildren(true); Image[] componentsInChildren4 = ((Component)_rt).GetComponentsInChildren(true); _name = At(componentsInChildren3, i); _cooldown = At(componentsInChildren3, i2); _icon = At(componentsInChildren4, i3); if ((Object)(object)_icon == (Object)null) { _failed = true; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[DualPowers] could not map the cloned power icon (" + componentsInChildren4.Length + " images, wanted index " + i3 + ") - no second power icon.")); Destroy(); return false; } if ((Object)(object)_name == (Object)null || (Object)(object)_cooldown == (Object)null) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[DualPowers] second HUD element has an icon but no " + (((Object)(object)_name == (Object)null) ? "name" : "cooldown") + " label.")); } _clone.SetActive(false); _hud = hud; _built = true; ManualLogSource log = NoVikingLeftBehindPlugin.Log; string[] obj = new string[11] { "[DualPowers] second power HUD element created under '", ((Object)(object)((Transform)gpRoot).parent != (Object)null) ? ((Object)((Transform)gpRoot).parent).name : "?", "' at offset ", null, null, null, null, null, null, null, null }; Vector2 offset = Offset; obj[3] = ((object)(*(Vector2*)(&offset))/*cast due to .constrained prefix*/).ToString(); obj[4] = " (icon="; obj[5] = i3.ToString(); obj[6] = " name="; obj[7] = i.ToString(); obj[8] = " cooldown="; obj[9] = i2.ToString(); obj[10] = ")"; log.LogInfo((object)string.Concat(obj)); return true; } private static int IndexOf(T[] arr, T item) where T : class { if (arr == null) { return -1; } for (int i = 0; i < arr.Length; i++) { if (arr[i] == item) { return i; } } return -1; } private static T At(T[] arr, int i) where T : class { if (arr == null || i < 0 || i >= arr.Length) { return null; } return arr[i]; } public static void SetOffset(Vector2 offset) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) Offset = offset; if ((Object)(object)_rt != (Object)null && (Object)(object)_hud != (Object)null && (Object)(object)_hud.m_gpRoot != (Object)null) { _rt.anchoredPosition = _hud.m_gpRoot.anchoredPosition + Offset; } } public static void Destroy() { try { if ((Object)(object)_clone != (Object)null) { Object.Destroy((Object)(object)_clone); } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[DualPowers] HUD teardown: " + ex.Message)); } _clone = null; _rt = null; _name = null; _cooldown = null; _icon = null; _hud = null; _built = false; } public static void Reset() { Destroy(); _failed = false; } } internal static class PowerSlots { private sealed class Extra { public string Name = ""; public int Hash; public StatusEffect Se; public float Cooldown; } public const int MaxSlots = 3; private const string KeyPrefix = "nvlb.gp"; public static int SlotCount = 2; public static bool IndependentCooldowns = true; public static float CooldownMultiplier = 1f; private static readonly Extra[] Extras = NewExtras(); private static Player _owner; public static int ExtraCount => Mathf.Clamp(SlotCount - 1, 0, 2); private static Extra[] NewExtras() { Extra[] array = new Extra[2]; for (int i = 0; i < array.Length; i++) { array[i] = new Extra(); } return array; } public static string NameKey(int slot) { return "nvlb.gp" + (slot + 1); } public static string CooldownKey(int slot) { return "nvlb.gp" + (slot + 1) + "cd"; } public static string EncodeCooldown(float seconds) { return Mathf.Max(0f, seconds).ToString("0.###", CultureInfo.InvariantCulture); } public static float DecodeCooldown(string s) { if (string.IsNullOrEmpty(s)) { return 0f; } if (!float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return 0f; } if (!(result > 0f) || float.IsNaN(result) || float.IsInfinity(result)) { return 0f; } return result; } public static void Bind(Player p) { if (!((Object)(object)p == (Object)null) && _owner != p) { _owner = p; LoadFrom(p); } } public static bool IsOwner(Player p) { if ((Object)(object)p != (Object)null) { return _owner == p; } return false; } public static void LoadFrom(Player p) { for (int i = 0; i < Extras.Length; i++) { Extras[i].Name = ""; Extras[i].Hash = 0; Extras[i].Se = null; Extras[i].Cooldown = 0f; } if ((Object)(object)p == (Object)null || p.m_customData == null) { return; } for (int j = 1; j <= Extras.Length; j++) { if (p.m_customData.TryGetValue(NameKey(j), out var value) && !string.IsNullOrEmpty(value)) { Extra obj = Extras[j - 1]; obj.Name = value; obj.Hash = StringExtensionMethods.GetStableHashCode(value); obj.Se = null; p.m_customData.TryGetValue(CooldownKey(j), out var value2); obj.Cooldown = DecodeCooldown(value2); } } } public static void SaveTo(Player p) { if ((Object)(object)p == (Object)null || p.m_customData == null) { return; } for (int i = 1; i <= Extras.Length; i++) { Extra extra = Extras[i - 1]; if (string.IsNullOrEmpty(extra.Name)) { p.m_customData.Remove(NameKey(i)); p.m_customData.Remove(CooldownKey(i)); } else { p.m_customData[NameKey(i)] = extra.Name; p.m_customData[CooldownKey(i)] = EncodeCooldown(GetCooldown(p, i)); } } } public static string GetName(Player p, int slot) { if (slot == 0) { if (!((Object)(object)p != (Object)null)) { return ""; } return p.m_guardianPower; } if ((Object)(object)p == (Object)null || slot < 1 || slot > Extras.Length) { return ""; } return Extras[slot - 1].Name ?? ""; } public static StatusEffect GetSe(Player p, int slot) { if (slot == 0) { if (!((Object)(object)p != (Object)null)) { return null; } return p.m_guardianSE; } if ((Object)(object)p == (Object)null || slot < 1 || slot > Extras.Length) { return null; } Extra extra = Extras[slot - 1]; if ((Object)(object)extra.Se == (Object)null && extra.Hash != 0 && (Object)(object)ObjectDB.instance != (Object)null) { extra.Se = ObjectDB.instance.GetStatusEffect(extra.Hash); } return extra.Se; } public static float GetCooldown(Player p, int slot) { if ((Object)(object)p == (Object)null) { return 0f; } if (slot == 0 || !IndependentCooldowns) { return p.m_guardianPowerCooldown; } if (slot < 1 || slot > Extras.Length) { return 0f; } return Extras[slot - 1].Cooldown; } public static void SetCooldown(Player p, int slot, float seconds) { if ((Object)(object)p == (Object)null) { return; } seconds = Mathf.Max(0f, seconds); if (!IndependentCooldowns) { p.m_guardianPowerCooldown = seconds; for (int i = 0; i < Extras.Length; i++) { Extras[i].Cooldown = seconds; } } else if (slot == 0) { p.m_guardianPowerCooldown = seconds; } else if (slot >= 1 && slot <= Extras.Length) { Extras[slot - 1].Cooldown = seconds; } } public static void SetPower(Player p, int slot, string name) { if ((Object)(object)p == (Object)null) { return; } if (slot == 0) { p.SetGuardianPower(name ?? ""); } else { if (slot < 1 || slot > Extras.Length) { return; } Bind(p); Extra extra = Extras[slot - 1]; extra.Name = name ?? ""; extra.Hash = ((!string.IsNullOrEmpty(extra.Name)) ? StringExtensionMethods.GetStableHashCode(extra.Name) : 0); extra.Se = ((extra.Hash != 0 && (Object)(object)ObjectDB.instance != (Object)null) ? ObjectDB.instance.GetStatusEffect(extra.Hash) : null); extra.Cooldown = 0f; try { if (extra.Hash != 0 && (Object)(object)ZoneSystem.instance != (Object)null) { ((Humanoid)p).AddUniqueKey(extra.Name); } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[DualPowers] AddUniqueKey failed: " + ex.Message)); } SaveTo(p); } } public static int FindSlot(Player p, string name) { if ((Object)(object)p == (Object)null || string.IsNullOrEmpty(name)) { return -1; } if (GetName(p, 0) == name) { return 0; } for (int i = 1; i <= ExtraCount; i++) { if (GetName(p, i) == name) { return i; } } return -1; } public static int FirstEmptySlot(Player p) { if ((Object)(object)p == (Object)null) { return -1; } if (string.IsNullOrEmpty(GetName(p, 0))) { return 0; } for (int i = 1; i <= ExtraCount; i++) { if (string.IsNullOrEmpty(GetName(p, i))) { return i; } } return -1; } public static void Tick(Player p, float dt) { if ((Object)(object)p == (Object)null) { return; } if (!IndependentCooldowns) { for (int i = 0; i < Extras.Length; i++) { Extras[i].Cooldown = p.m_guardianPowerCooldown; } return; } for (int j = 0; j < ExtraCount; j++) { if (!(Extras[j].Cooldown <= 0f)) { Extras[j].Cooldown -= dt; if (Extras[j].Cooldown < 0f) { Extras[j].Cooldown = 0f; } } } } public static void ReduceCooldowns(float seconds) { if (seconds <= 0f || !IndependentCooldowns) { return; } for (int i = 0; i < ExtraCount; i++) { if (!(Extras[i].Cooldown <= 0f)) { Extras[i].Cooldown = Mathf.Max(0f, Extras[i].Cooldown - seconds); } } } public static void ResetCooldowns() { for (int i = 0; i < Extras.Length; i++) { Extras[i].Cooldown = 0f; } } public static void InvalidateStatusEffects() { for (int i = 0; i < Extras.Length; i++) { Extras[i].Se = null; } } public static string Describe(Player p) { List list = new List(); for (int i = 0; i < Mathf.Clamp(SlotCount, 1, 3); i++) { string name = GetName(p, i); float cooldown = GetCooldown(p, i); list.Add("slot" + (i + 1) + "=" + (string.IsNullOrEmpty(name) ? "-" : name) + (string.IsNullOrEmpty(name) ? "" : ((cooldown > 0f) ? ("(" + cooldown.ToString("0") + "s)") : "(ready)"))); } return string.Join(" ", list.ToArray()); } } internal sealed class OreRegrowthModule : FeatureModule { private ConfigEntry _prefabs; private ConfigEntry _regrowDays; private ConfigEntry _checkIntervalSec; private ConfigEntry _minPlayerDistance; private ConfigEntry _maxPerTick; private ConfigEntry _dryRun; private ConfigEntry _selfTest; public const string DefaultPrefabs = "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4"; internal static OreRegrowthModule Instance; private readonly Dictionary _allow = new Dictionary(); private bool _allowResolved; private string _allowSummary = "(not resolved yet)"; private readonly List _pending = new List(); private bool _loaded; private bool _dirty; private GameObject _tickerGo; private bool _selfTestDone; public override string Name => "OreRegrowth"; public override ModuleSide Side => ModuleSide.Server; public override string Section => "Regrowth"; protected override string EnabledDescription => "Regrow mined-out ore nodes whose material tier is behind the frontier. Server only: recording and respawning both happen on the dedicated server."; internal float CheckIntervalSec { get { if (_checkIntervalSec != null) { return Mathf.Max(1f, _checkIntervalSec.Value); } return 60f; } } internal bool SelfTestWanted { get { if (_selfTest != null && _selfTest.Value) { return !_selfTestDone; } return false; } } internal string StorePath => Path.Combine(Path.Combine(Paths.ConfigPath, "nvlb"), "regrowth.json"); protected override void Bind() { _prefabs = BindSynced("Prefabs", "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4", "Ore node prefabs that regrow, as name:tier,name:tier. The tier is the material tier used against the frontier (see [Tiers]/[Frontier]). Names are resolved against ZNetScene's prefab list at runtime; unknown names are logged and ignored."); _regrowDays = BindSynced("RegrowDays", 7, "In-game days a mined-out node stays gone before it may regrow."); _checkIntervalSec = BindSynced("CheckIntervalSec", 60f, "Real seconds between respawn sweeps on the server."); _minPlayerDistance = BindSynced("MinPlayerDistance", 64f, "Never respawn a node with a player this close (metres) - nobody sees ore pop in."); _maxPerTick = BindSynced("MaxPerTick", 5, "Maximum nodes respawned per sweep, so a long backlog trickles back in."); _dryRun = BindLocal("DryRun", defaultValue: false, "Log what would be respawned without creating any ZDO. Machine-local."); _selfTest = BindLocal("SelfTest", defaultValue: false, "Headless proof: pick an existing copper node, fake a due destroy record for it, run one sweep and verify a new ZDO appeared. Machine-local, runs once per boot."); } protected override void ApplyPatches() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ZDOMan), "HandleDestroyedZDO", new Type[1] { typeof(ZDOID) }, (Type[])null); if (methodInfo == null) { throw new Exception("NoVikingLeftBehind OreRegrowth: ZDOMan.HandleDestroyedZDO(ZDOID) not found"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(OreRegrowthModule), "HandleDestroyedZDO_Prefix", (Type[])null, (Type[])null); if (methodInfo2 == null) { throw new Exception("NoVikingLeftBehind OreRegrowth: own prefix method not found"); } Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Instance = this; LoadStore(); StartTicker(); FeatureModule.Log.LogInfo((object)("[OreRegrowth] hooked ZDOMan.HandleDestroyedZDO; store=" + StorePath + " pending=" + _pending.Count + " regrowDays=" + _regrowDays.Value + " interval=" + CheckIntervalSec.ToString("0.#") + "s minDist=" + _minPlayerDistance.Value.ToString("0.#") + "m maxPerTick=" + _maxPerTick.Value + " dryRun=" + _dryRun.Value + " selfTest=" + _selfTest.Value)); } public override void Disable() { StopTicker(); base.Disable(); if (Instance == this) { Instance = null; } } public override void OnConfigChanged(ConfigEntryBase entry) { if (entry != null && entry.Definition.Key == "Prefabs") { _allowResolved = false; _allow.Clear(); FeatureModule.Log.LogInfo((object)"[OreRegrowth] Prefabs changed, allowlist will be re-resolved on the next sweep"); } } public override string StatusDetail() { if (_regrowDays == null) { return null; } return "pending=" + _pending.Count + " regrowDays=" + _regrowDays.Value + " interval=" + CheckIntervalSec.ToString("0.#") + "s minDist=" + _minPlayerDistance.Value.ToString("0.#") + "m allow=" + _allowSummary + (_dryRun.Value ? " DRYRUN" : ""); } private void StartTicker() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (!((Object)(object)_tickerGo != (Object)null)) { _tickerGo = new GameObject("NVLB_RegrowthTicker"); Object.DontDestroyOnLoad((Object)(object)_tickerGo); ((Object)_tickerGo).hideFlags = (HideFlags)61; _tickerGo.AddComponent(); } } private void StopTicker() { if (!((Object)(object)_tickerGo == (Object)null)) { try { Object.Destroy((Object)(object)_tickerGo); } catch { } _tickerGo = null; } } private static void HandleDestroyedZDO_Prefix(ZDOMan __instance, ZDOID uid) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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_0142: Unknown result type (might be due to invalid IL or missing references) OreRegrowthModule instance = Instance; if (instance == null || !instance.Active || !FeatureModule.ServerActive()) { return; } try { ZDO zDO = __instance.GetZDO(uid); if (zDO != null) { instance.EnsureAllowlist(); if (instance._allow.TryGetValue(zDO.GetPrefab(), out RegrowthPrefab value)) { RegrowthEntry regrowthEntry = new RegrowthEntry { prefabHash = value.Hash, name = value.Name, tier = value.Tier, day = CurrentDay(), x = zDO.GetPosition().x, y = zDO.GetPosition().y, z = zDO.GetPosition().z }; Quaternion rotation = zDO.GetRotation(); Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles; regrowthEntry.rx = eulerAngles.x; regrowthEntry.ry = eulerAngles.y; regrowthEntry.rz = eulerAngles.z; instance._pending.Add(regrowthEntry); instance._dirty = true; instance.SaveStore(); FeatureModule.Log.LogInfo((object)("[OreRegrowth] recorded destroyed " + value.Name + " tier=" + value.Tier + " at " + Fmt(regrowthEntry.Pos) + " day=" + regrowthEntry.day + " (pending=" + instance._pending.Count + ")")); } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[OreRegrowth] destroy hook failed: " + ex.Message)); } } internal void EnsureAllowlist() { if (_allowResolved || (Object)(object)ZNetScene.instance == (Object)null) { return; } _allow.Clear(); List list = new List(); List list2 = new List(); List list3 = new List(); string[] array = ((_prefabs == null) ? "rock4_copper:1,MineRock_Tin:1,silvervein:3,MineRock_Obsidian:3,MineRock_Meteorite:4" : _prefabs.Value).Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } string[] array2 = text.Split(new char[1] { ':' }); string text2 = array2[0].Trim(); if (text2.Length == 0) { continue; } int result = 1; if (array2.Length > 1 && !int.TryParse(array2[1].Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) { FeatureModule.Log.LogWarning((object)("[OreRegrowth] bad tier in Prefabs entry '" + text + "', using 1")); result = 1; } int stableHashCode = StringExtensionMethods.GetStableHashCode(text2); GameObject prefab = ZNetScene.instance.GetPrefab(stableHashCode); if ((Object)(object)prefab == (Object)null) { list2.Add(text2); continue; } bool flag = (Object)(object)prefab.GetComponentInChildren(true) != (Object)null || (Object)(object)prefab.GetComponentInChildren(true) != (Object)null; if (!flag) { MonoBehaviour[] componentsInChildren = prefab.GetComponentsInChildren(true); List list4 = new List(); for (int j = 0; j < componentsInChildren.Length; j++) { if (list4.Count >= 12) { break; } if ((Object)(object)componentsInChildren[j] != (Object)null) { list4.Add(((object)componentsInChildren[j]).GetType().Name); } } list3.Add(text2 + "[" + string.Join("+", list4.ToArray()) + ((componentsInChildren.Length > list4.Count) ? "+..." : "") + "]"); } _allow[stableHashCode] = new RegrowthPrefab { Hash = stableHashCode, Name = text2, Tier = result }; list.Add(text2 + ":" + result + "(" + stableHashCode + (flag ? "" : ",noMineRock") + ")"); } _allowResolved = true; _allowSummary = _allow.Count + "/" + (list.Count + list2.Count); FeatureModule.Log.LogInfo((object)("[OreRegrowth] prefab allowlist resolved: " + ((list.Count == 0) ? "(none)" : string.Join(", ", list.ToArray())))); if (list2.Count > 0) { FeatureModule.Log.LogWarning((object)("[OreRegrowth] prefab names NOT found in ZNetScene (ignored): " + string.Join(", ", list2.ToArray()))); } if (list3.Count > 0) { FeatureModule.Log.LogInfo((object)("[OreRegrowth] allowlisted prefabs that are Destructible rather than MineRock/MineRock5 (fine, same destroy funnel): " + string.Join(", ", list3.ToArray()))); } } internal unsafe int RunSweep() { //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: 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_0200: Unknown result type (might be due to invalid IL or missing references) if (!base.Active || !FeatureModule.ServerActive()) { return 0; } EnsureAllowlist(); if (_pending.Count == 0) { return 0; } int num = CurrentDay(); int num2 = Mathf.Max(0, _maxPerTick.Value); int num3 = 0; for (int num4 = _pending.Count - 1; num4 >= 0 && num3 < num2; num4--) { RegrowthEntry regrowthEntry = _pending[num4]; if (num - regrowthEntry.day < _regrowDays.Value || !Tiers.IsBehind(regrowthEntry.tier) || PlayerWithin(regrowthEntry.Pos, _minPlayerDistance.Value)) { continue; } if (_dryRun.Value) { FeatureModule.Log.LogInfo((object)("[OreRegrowth] DRYRUN would respawn " + regrowthEntry.name + " at " + Fmt(regrowthEntry.Pos) + " after " + (num - regrowthEntry.day) + " days")); continue; } ZDO val; try { val = Respawn(regrowthEntry); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[OreRegrowth] respawn of " + regrowthEntry.name + " at " + Fmt(regrowthEntry.Pos) + " failed: " + ex.Message)); continue; } if (val != null) { _pending.RemoveAt(num4); _dirty = true; num3++; ManualLogSource log = FeatureModule.Log; string[] obj = new string[9] { "[OreRegrowth] respawned ", regrowthEntry.name, " at ", Fmt(regrowthEntry.Pos), " after ", (num - regrowthEntry.day).ToString(), " days (zdo=", null, null }; ZDOID uid = val.m_uid; obj[7] = ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); obj[8] = ")"; log.LogInfo((object)string.Concat(obj)); } } if (_dirty) { SaveStore(); } return num3; } private static ZDO Respawn(RegrowthEntry e) { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNetScene.instance == (Object)null || ZDOMan.instance == null) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(e.prefabHash); if ((Object)(object)prefab == (Object)null) { throw new Exception("prefab hash " + e.prefabHash + " (" + e.name + ") not in ZNetScene"); } ZNetView component = prefab.GetComponent(); if ((Object)(object)component == (Object)null) { throw new Exception("prefab " + e.name + " has no ZNetView"); } ZDO obj = ZDOMan.instance.CreateNewZDO(e.Pos, e.prefabHash); obj.Persistent = component.m_persistent; obj.Type = component.m_type; obj.Distant = component.m_distant; obj.SetPrefab(e.prefabHash); obj.SetRotation(e.Rot); obj.SetOwner(0L); return obj; } internal static int CurrentDay() { if ((Object)(object)EnvMan.instance != (Object)null) { return EnvMan.instance.GetDay(); } if ((Object)(object)ZNet.instance != (Object)null) { return (int)(ZNet.instance.GetTimeSeconds() / 1800.0); } return 0; } internal static string DaySource() { if (!((Object)(object)EnvMan.instance != (Object)null)) { return "ZNet.GetTimeSeconds/1800"; } return "EnvMan.GetDay"; } private static bool PlayerWithin(Vector3 pos, float dist) { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { return true; } float num = dist * dist; List peers = instance.GetPeers(); Vector3 val2; if (peers != null) { for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val != null) { val2 = val.m_refPos - pos; if (((Vector3)(ref val2)).sqrMagnitude <= num) { return true; } } } } if (!instance.IsDedicated()) { val2 = instance.GetReferencePosition() - pos; if (((Vector3)(ref val2)).sqrMagnitude <= num) { return true; } } return false; } internal static string Fmt(Vector3 v) { return "(" + v.x.ToString("0.0", CultureInfo.InvariantCulture) + ", " + v.y.ToString("0.0", CultureInfo.InvariantCulture) + ", " + v.z.ToString("0.0", CultureInfo.InvariantCulture) + ")"; } private void LoadStore() { if (_loaded) { return; } _loaded = true; try { string storePath = StorePath; if (!File.Exists(storePath)) { FeatureModule.Log.LogInfo((object)("[OreRegrowth] no store at " + storePath + ", starting empty")); return; } List collection = RegrowthJson.Read(File.ReadAllText(storePath)); _pending.AddRange(collection); FeatureModule.Log.LogInfo((object)("[OreRegrowth] loaded " + _pending.Count + " pending node(s) from " + storePath)); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[OreRegrowth] could not read the store, starting empty: " + ex.Message)); _pending.Clear(); } } internal void SaveStore() { if (!_dirty) { return; } try { string storePath = StorePath; string directoryName = Path.GetDirectoryName(storePath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } string contents = RegrowthJson.Write(_pending); string text = storePath + ".tmp"; File.WriteAllText(text, contents); if (File.Exists(storePath)) { File.Delete(storePath); } File.Move(text, storePath); _dirty = false; } catch (Exception ex) { FeatureModule.Log.LogError((object)("[OreRegrowth] could not write the store: " + ex.Message)); } } internal unsafe void RunSelfTest() { //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0111: 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_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013f: 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_0175: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_018d: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: 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_032e: Unknown result type (might be due to invalid IL or missing references) //IL_032f: 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_0348: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_049e: Unknown result type (might be due to invalid IL or missing references) //IL_04c2: Unknown result type (might be due to invalid IL or missing references) //IL_04c7: Unknown result type (might be due to invalid IL or missing references) //IL_04fe: Unknown result type (might be due to invalid IL or missing references) //IL_054e: Unknown result type (might be due to invalid IL or missing references) //IL_0553: Unknown result type (might be due to invalid IL or missing references) //IL_0430: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_05bb: Unknown result type (might be due to invalid IL or missing references) //IL_05c0: Unknown result type (might be due to invalid IL or missing references) //IL_05c7: Unknown result type (might be due to invalid IL or missing references) //IL_05f6: Unknown result type (might be due to invalid IL or missing references) //IL_05f8: Unknown result type (might be due to invalid IL or missing references) //IL_0632: Unknown result type (might be due to invalid IL or missing references) _selfTestDone = true; try { EnsureAllowlist(); if (ZDOMan.instance == null || (Object)(object)ZNetScene.instance == (Object)null) { FeatureModule.Log.LogWarning((object)"[OreRegrowth][SelfTest] ZDOMan/ZNetScene not ready, skipped"); return; } int stableHashCode = StringExtensionMethods.GetStableHashCode("rock4_copper"); if (!_allow.TryGetValue(stableHashCode, out RegrowthPrefab value)) { FeatureModule.Log.LogWarning((object)"[OreRegrowth][SelfTest] rock4_copper is not in the allowlist, skipped"); return; } ZDO val = null; foreach (KeyValuePair item in ZDOMan.instance.m_objectsByID) { if (item.Value != null && item.Value.GetPrefab() == stableHashCode) { val = item.Value; break; } } if (val == null) { FeatureModule.Log.LogWarning((object)("[OreRegrowth][SelfTest] no existing rock4_copper ZDO in the world (" + ZDOMan.instance.m_objectsByID.Count + " ZDOs), skipped")); return; } Vector3 position = val.GetPosition(); ZDOID uid = val.m_uid; ManualLogSource log = FeatureModule.Log; ZDOID val2 = uid; log.LogInfo((object)("[OreRegrowth][SelfTest] step 1: found existing rock4_copper zdo=" + ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString() + " at " + Fmt(position))); Quaternion rotation = val.GetRotation(); Vector3 eulerAngles = ((Quaternion)(ref rotation)).eulerAngles; RegrowthEntry regrowthEntry = new RegrowthEntry { prefabHash = stableHashCode, name = "rock4_copper", tier = value.Tier, day = -999, x = position.x, y = position.y, z = position.z, rx = eulerAngles.x, ry = eulerAngles.y, rz = eulerAngles.z }; _pending.Add(regrowthEntry); _dirty = true; FeatureModule.Log.LogInfo((object)("[OreRegrowth][SelfTest] step 2: recorded fake entry tier=" + regrowthEntry.tier + " day=" + regrowthEntry.day + " today=" + CurrentDay() + " (" + DaySource() + ") frontier: " + Frontier.Describe() + " IsBehind(" + regrowthEntry.tier + ")=" + Tiers.IsBehind(regrowthEntry.tier))); if (!Tiers.IsBehind(regrowthEntry.tier)) { FeatureModule.Log.LogWarning((object)("[OreRegrowth][SelfTest] tier " + regrowthEntry.tier + " is NOT behind the frontier right now, so the sweep will (correctly) refuse. Set [Frontier] TierOverride >= " + (regrowthEntry.tier + 1) + " to exercise the respawn.")); } HashSet hashSet = new HashSet(); Vector3 val3; foreach (KeyValuePair item2 in ZDOMan.instance.m_objectsByID) { ZDO value2 = item2.Value; if (value2 != null && value2.GetPrefab() == stableHashCode) { val3 = value2.GetPosition() - position; if (((Vector3)(ref val3)).sqrMagnitude <= 1f) { hashSet.Add(value2.m_uid); } } } int num = RunSweep(); FeatureModule.Log.LogInfo((object)("[OreRegrowth][SelfTest] step 3: sweep respawned " + num + " node(s)")); ZDO val4 = null; foreach (KeyValuePair item3 in ZDOMan.instance.m_objectsByID) { ZDO value3 = item3.Value; if (value3 != null && value3.GetPrefab() == stableHashCode && !hashSet.Contains(value3.m_uid)) { val3 = value3.GetPosition() - position; if (!(((Vector3)(ref val3)).sqrMagnitude > 1f)) { val4 = value3; break; } } } if (val4 == null) { FeatureModule.Log.LogError((object)("[OreRegrowth][SelfTest] step 4: FAIL - the sweep created no new rock4_copper ZDO within 1 m of " + Fmt(position) + " (sweep respawned " + num + "; IsBehind(" + regrowthEntry.tier + ")=" + Tiers.IsBehind(regrowthEntry.tier) + ")")); } else { ZDO zDO = ZDOMan.instance.GetZDO(val4.m_uid); ManualLogSource log2 = FeatureModule.Log; string[] obj = new string[16] { "[OreRegrowth][SelfTest] step 4: PASS - new zdo=", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; val2 = val4.m_uid; obj[1] = ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(); obj[2] = " prefab="; obj[3] = val4.GetPrefab().ToString(); obj[4] = " at "; obj[5] = Fmt(val4.GetPosition()); obj[6] = " persistent="; obj[7] = val4.Persistent.ToString(); obj[8] = " distant="; obj[9] = val4.Distant.ToString(); obj[10] = " type="; obj[11] = ((object)val4.Type/*cast due to .constrained prefix*/).ToString(); obj[12] = " owner="; obj[13] = val4.GetOwner().ToString(); obj[14] = " GetZDO(round-trip)="; obj[15] = (zDO != null).ToString(); log2.LogInfo((object)string.Concat(obj)); } if (val4 != null) { int count = _pending.Count; ZDOID uid2 = val4.m_uid; ZDOMan.instance.HandleDestroyedZDO(uid2); bool flag = _pending.Count == count + 1; ManualLogSource log3 = FeatureModule.Log; string[] obj2 = new string[6] { "[OreRegrowth][SelfTest] step 5: HandleDestroyedZDO(", null, null, null, null, null }; val2 = uid2; obj2[1] = ((object)(*(ZDOID*)(&val2))/*cast due to .constrained prefix*/).ToString(); obj2[2] = ") -> "; obj2[3] = (flag ? "RECORDED" : "NOT RECORDED"); obj2[4] = ", zdo gone="; obj2[5] = (ZDOMan.instance.GetZDO(uid2) == null).ToString(); log3.LogInfo((object)string.Concat(obj2)); if (flag) { _pending.RemoveAt(_pending.Count - 1); } } _pending.Remove(regrowthEntry); _dirty = true; SaveStore(); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[OreRegrowth][SelfTest] threw: " + ex)); } } } internal sealed class RegrowthPrefab { public int Hash; public string Name; public int Tier; } [Serializable] internal sealed class RegrowthEntry { public int prefabHash; public string name; public int tier; public int day; public float x; public float y; public float z; public float rx; public float ry; public float rz; public Vector3 Pos => new Vector3(x, y, z); public Quaternion Rot => Quaternion.Euler(rx, ry, rz); } internal static class RegrowthJson { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; public static string Write(List entries) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n \"entries\": [\n"); for (int i = 0; i < entries.Count; i++) { RegrowthEntry regrowthEntry = entries[i]; stringBuilder.Append(" {"); stringBuilder.Append("\"prefabHash\":").Append(regrowthEntry.prefabHash.ToString(Inv)); stringBuilder.Append(",\"name\":\"").Append(Escape(regrowthEntry.name)).Append('"'); stringBuilder.Append(",\"tier\":").Append(regrowthEntry.tier.ToString(Inv)); stringBuilder.Append(",\"day\":").Append(regrowthEntry.day.ToString(Inv)); stringBuilder.Append(",\"x\":").Append(F(regrowthEntry.x)); stringBuilder.Append(",\"y\":").Append(F(regrowthEntry.y)); stringBuilder.Append(",\"z\":").Append(F(regrowthEntry.z)); stringBuilder.Append(",\"rx\":").Append(F(regrowthEntry.rx)); stringBuilder.Append(",\"ry\":").Append(F(regrowthEntry.ry)); stringBuilder.Append(",\"rz\":").Append(F(regrowthEntry.rz)); stringBuilder.Append('}'); if (i < entries.Count - 1) { stringBuilder.Append(','); } stringBuilder.Append('\n'); } stringBuilder.Append(" ]\n}\n"); return stringBuilder.ToString(); } public static List Read(string json) { List list = new List(); if (string.IsNullOrEmpty(json)) { return list; } int num = 0; while (num < json.Length) { int num2 = json.IndexOf('{', num); if (num2 < 0) { break; } int num3 = MatchBrace(json, num2); if (num3 < 0) { break; } string text = json.Substring(num2 + 1, num3 - num2 - 1); if (text.IndexOf("\"prefabHash\"", StringComparison.Ordinal) >= 0 && text.IndexOf('{') < 0) { RegrowthEntry regrowthEntry = ReadEntry(text); if (regrowthEntry != null) { list.Add(regrowthEntry); } num = num3 + 1; } else { num = num2 + 1; } } return list; } private static RegrowthEntry ReadEntry(string body) { try { RegrowthEntry regrowthEntry = new RegrowthEntry { prefabHash = (int)Num(body, "prefabHash"), name = Str(body, "name"), tier = (int)Num(body, "tier"), day = (int)Num(body, "day"), x = Num(body, "x"), y = Num(body, "y"), z = Num(body, "z"), rx = Num(body, "rx"), ry = Num(body, "ry"), rz = Num(body, "rz") }; return (regrowthEntry.prefabHash == 0) ? null : regrowthEntry; } catch { return null; } } private static int MatchBrace(string s, int open) { int num = 0; bool flag = false; for (int i = open; i < s.Length; i++) { char c = s[i]; if (flag) { switch (c) { case '\\': i++; break; case '"': flag = false; break; } continue; } switch (c) { case '"': flag = true; break; case '{': num++; break; case '}': num--; if (num == 0) { return i; } break; } } return -1; } private static float Num(string body, string key) { int num = ValueStart(body, key); if (num < 0) { return 0f; } int i; for (i = num; i < body.Length && (char.IsDigit(body[i]) || body[i] == '-' || body[i] == '+' || body[i] == '.' || body[i] == 'e' || body[i] == 'E'); i++) { } if (!float.TryParse(body.Substring(num, i - num), NumberStyles.Float, Inv, out var result)) { return 0f; } return result; } private static string Str(string body, string key) { int num = ValueStart(body, key); if (num < 0 || num >= body.Length || body[num] != '"') { return ""; } StringBuilder stringBuilder = new StringBuilder(); for (int i = num + 1; i < body.Length; i++) { char c = body[i]; if (c == '\\' && i + 1 < body.Length) { stringBuilder.Append(body[++i]); continue; } if (c == '"') { break; } stringBuilder.Append(c); } return stringBuilder.ToString(); } private static int ValueStart(string body, string key) { int num = body.IndexOf("\"" + key + "\"", StringComparison.Ordinal); if (num < 0) { return -1; } int num2 = body.IndexOf(':', num + key.Length + 2); if (num2 < 0) { return -1; } for (num2++; num2 < body.Length && char.IsWhiteSpace(body[num2]); num2++) { } return num2; } private static string F(float v) { return v.ToString("R", Inv); } private static string Escape(string s) { if (string.IsNullOrEmpty(s)) { return ""; } return s.Replace("\\", "\\\\").Replace("\"", "\\\""); } } internal sealed class RegrowthTicker : MonoBehaviour { private float _next; private bool _announced; private void Update() { OreRegrowthModule instance = OreRegrowthModule.Instance; if (instance == null || !instance.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer() || (Object)(object)ZNetScene.instance == (Object)null || ZDOMan.instance == null || Time.time < _next) { return; } _next = Time.time + instance.CheckIntervalSec; try { instance.EnsureAllowlist(); if (!_announced) { _announced = true; NoVikingLeftBehindPlugin.Log.LogInfo((object)("[OreRegrowth] ticker running: every " + instance.CheckIntervalSec.ToString("0.#") + "s, day=" + OreRegrowthModule.CurrentDay() + " (" + OreRegrowthModule.DaySource() + ")")); } if (instance.SelfTestWanted) { instance.RunSelfTest(); } else { instance.RunSweep(); } } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogError((object)("[OreRegrowth] ticker: " + ex)); } } } internal sealed class ExtraSlotsModule : FeatureModule { internal static ExtraSlotsModule Inst; private ConfigEntry _equipmentSlots; private ConfigEntry _utilitySlots; private ConfigEntry _foodSlots; private ConfigEntry _ammoSlots; private ConfigEntry _quickSlots; private ConfigEntry _genericSlots; private ConfigEntry _autoEat; private ConfigEntry _showUi; private ConfigEntry _quickKeys; private ConfigEntry _panelOffsetX; private ConfigEntry _panelOffsetY; private ConfigEntry _panelScale; private static KeyCode[] _keys = (KeyCode[])(object)new KeyCode[0]; private static readonly ItemData[] ExtraUtility = (ItemData[])(object)new ItemData[3]; private static bool _syncing; private static float _eatTimer; private static int _tickErrors; private static bool _commandRegistered; private static readonly MethodInfo SetupEquipmentMi = AccessTools.Method(typeof(Humanoid), "SetupEquipment", (Type[])null, (Type[])null); private static readonly FieldInfo EquipSeFi = AccessTools.Field(typeof(Humanoid), "m_equipmentStatusEffects"); private static readonly FieldInfo SemanFi = AccessTools.Field(typeof(Character), "m_seman"); public override string Name => "ExtraSlots"; public override ModuleSide Side => ModuleSide.Client; public override string Section => "Slots"; protected override void Bind() { _equipmentSlots = BindSynced("EquipmentSlots", defaultValue: true, "Server: give every player four dedicated equipment slots (head, chest, legs, cape). Off removes the four slots; anything in them is moved back into the bag first."); _utilitySlots = BindSynced("UtilitySlots", 2, "Server: how many utility slots (0-4). 2 lets a player wear Megingjord and the Wishbone at the same time. 0 disables the group."); _foodSlots = BindSynced("FoodSlots", 3, "Server: how many food slots (0-3). Only food goes in them."); _ammoSlots = BindSynced("AmmoSlots", 2, "Server: how many ammo slots (0-4). The equipped ammo stack lives here."); _quickSlots = BindSynced("QuickSlots", 0, "Server: how many quick slots (0-8). Anything can go in them; a hotkey uses it. 0 by default since 0.4.2 - the bottom row is GenericSlots plain storage instead. Set it above 0 to bring the hotkey row back; quick slots are drawn first, then the generic ones, on the same row."); _genericSlots = BindSynced("GenericSlots", 2, "Server: how many plain storage slots (0-8) on the bottom row. Any item fits, there is no hotkey and nothing is drawn on the cell - they are simply two more places to put things."); _autoEat = BindSynced("AutoEatFromFoodSlots", defaultValue: true, "Server: when a food buff runs out and the same food is sitting in a food slot, eat it automatically."); _quickKeys = BindLocal("QuickSlotKeys", "Z,X,C", "Local: comma-separated keys for the quick slots, in order. Unity KeyCode names (Z, X, C, F1, Keypad1 ...). Use None to leave a quick slot without a hotkey. Never synced, so each player picks their own."); _showUi = BindLocal("ShowUI", defaultValue: true, "Local: draw the extra slots in their own panel beside the inventory window. Turn off if a game update breaks the layout - the items stay exactly where they are and stay reachable, they just fall back to plain extra rows under the bag."); _panelOffsetX = BindLocal("PanelOffsetX", 0f, "Local: nudge the extra-slot panel right (negative moves it left, towards the inventory window). Pixels at 100% UI scale."); _panelOffsetY = BindLocal("PanelOffsetY", 0f, "Local: nudge the extra-slot panel down. Pixels at 100% UI scale."); _panelScale = BindLocal("PanelScale", 1f, "Local: size of the extra-slot panel relative to the inventory grid (0.4-2.5). 1 draws the slots exactly the size of the bag's own slots."); ParseKeys(); RebuildLayout(); } private void ParseKeys() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) string[] array = (_quickKeys.Value ?? "").Split(new char[1] { ',' }); List list = new List(); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { list.Add((KeyCode)0); continue; } try { list.Add((KeyCode)Enum.Parse(typeof(KeyCode), text, ignoreCase: true)); } catch { FeatureModule.Log.LogWarning((object)("[Slots] QuickSlotKeys: '" + text + "' is not a Unity KeyCode - that slot has no hotkey")); list.Add((KeyCode)0); } } _keys = list.ToArray(); } private void RebuildLayout() { SlotLayout.Rebuild(_equipmentSlots.Value, _utilitySlots.Value, _foodSlots.Value, _ammoSlots.Value, _quickSlots.Value, _genericSlots.Value); } public override void OnConfigChanged(ConfigEntryBase entry) { ParseKeys(); if ((object)entry == EnabledCfg) { if (!base.Enabled) { EvacuateAndShrink("module turned off"); } else if (SlotStore.Managed != null) { SlotStore.SetHeight(SlotStore.Managed, SlotLayout.TotalHeight); SlotStore.Changed(SlotStore.Managed); FeatureModule.Log.LogInfo((object)("[Slots] module turned on, grid back to " + SlotLayout.TotalHeight + " rows")); } SlotsUi.Invalidate(); } else { string text = SlotLayout.Describe(); Relayout(); if (text != SlotLayout.Describe()) { FeatureModule.Log.LogInfo((object)("[Slots] layout now " + SlotLayout.Describe())); } SlotsUi.Invalidate(); } } private static void EvacuateAndShrink(string why) { Inventory managed = SlotStore.Managed; if (managed == null) { return; } try { List list = SlotStore.ExtraItems(managed); SlotStore.Evacuate(Player.m_localPlayer, managed, list, why); SlotStore.SetHeight(managed, 4); SlotStore.Changed(managed); FeatureModule.Log.LogWarning((object)("[Slots] " + why + ": " + list.Count + " item(s) evacuated, grid back to " + 4 + " rows")); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] evacuation (" + why + ") failed: " + ex)); } } private void Relayout() { Inventory managed = SlotStore.Managed; if (managed == null) { RebuildLayout(); return; } Player localPlayer = Player.m_localPlayer; List entries = SlotStore.Collect(managed); List list = SlotStore.Orphans(managed); List list2 = SlotStore.Items(managed); for (int num = list2.Count - 1; num >= 0; num--) { if (list2[num].m_gridPos.y >= 4) { list2.RemoveAt(num); } } RebuildLayout(); SlotStore.SetHeight(managed, SlotLayout.TotalHeight); SlotStore.Inject(managed, entries, list); SlotStore.Evacuate(localPlayer, managed, list, "slot layout change"); SlotStore.Changed(managed); } protected override void ApplyPatches() { //IL_042e: Unknown result type (might be due to invalid IL or missing references) //IL_043a: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Expected O, but got Unknown //IL_0447: Expected O, but got Unknown //IL_045f: Unknown result type (might be due to invalid IL or missing references) //IL_046d: Expected O, but got Unknown //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_048a: Expected O, but got Unknown //IL_049b: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Expected O, but got Unknown //IL_04b9: Unknown result type (might be due to invalid IL or missing references) //IL_04c6: Expected O, but got Unknown //IL_04d6: Unknown result type (might be due to invalid IL or missing references) //IL_04e4: Expected O, but got Unknown //IL_04f4: Unknown result type (might be due to invalid IL or missing references) //IL_0500: Unknown result type (might be due to invalid IL or missing references) //IL_050d: Expected O, but got Unknown //IL_050d: Expected O, but got Unknown //IL_051e: Unknown result type (might be due to invalid IL or missing references) //IL_052b: Expected O, but got Unknown //IL_053c: Unknown result type (might be due to invalid IL or missing references) //IL_0549: Expected O, but got Unknown //IL_055a: Unknown result type (might be due to invalid IL or missing references) //IL_0567: Expected O, but got Unknown //IL_0577: Unknown result type (might be due to invalid IL or missing references) //IL_0585: Expected O, but got Unknown //IL_0596: Unknown result type (might be due to invalid IL or missing references) //IL_05a3: Expected O, but got Unknown //IL_05b4: Unknown result type (might be due to invalid IL or missing references) //IL_05c1: Expected O, but got Unknown //IL_05d1: Unknown result type (might be due to invalid IL or missing references) //IL_05df: Expected O, but got Unknown //IL_05f0: Unknown result type (might be due to invalid IL or missing references) //IL_05fd: Expected O, but got Unknown //IL_060e: Unknown result type (might be due to invalid IL or missing references) //IL_061b: Expected O, but got Unknown //IL_062c: Unknown result type (might be due to invalid IL or missing references) //IL_0639: Expected O, but got Unknown Inst = this; Type typeFromHandle = typeof(ExtraSlotsModule); MethodInfo methodInfo = AccessTools.Method(typeof(Inventory), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(Inventory), "AddItem", new Type[12] { typeof(string), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary), typeof(int), typeof(bool) }, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(typeof(Inventory), "FindEmptySlot", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(Inventory), "GetEmptySlots", (Type[])null, (Type[])null); MethodInfo methodInfo5 = AccessTools.Method(typeof(Inventory), "HaveEmptySlot", (Type[])null, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method(typeof(Player), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo7 = AccessTools.Method(typeof(Player), "Load", new Type[1] { typeof(ZPackage) }, (Type[])null); MethodInfo methodInfo8 = AccessTools.Method(typeof(Player), "OnSpawned", new Type[1] { typeof(bool) }, (Type[])null); MethodInfo methodInfo9 = AccessTools.Method(typeof(Player), "Update", (Type[])null, (Type[])null); MethodInfo methodInfo10 = AccessTools.Method(typeof(Player), "OnInventoryChanged", (Type[])null, (Type[])null); MethodInfo methodInfo11 = AccessTools.Method(typeof(InventoryGrid), "DropItem", new Type[4] { typeof(Inventory), typeof(ItemData), typeof(int), typeof(Vector2i) }, (Type[])null); MethodInfo methodInfo12 = AccessTools.Method(typeof(Humanoid), "UpdateEquipmentStatusEffects", (Type[])null, (Type[])null); MethodInfo methodInfo13 = AccessTools.Method(typeof(Humanoid), "IsItemEquiped", new Type[1] { typeof(ItemData) }, (Type[])null); MethodInfo methodInfo14 = AccessTools.Method(typeof(Humanoid), "UnequipItem", new Type[2] { typeof(ItemData), typeof(bool) }, (Type[])null); MethodInfo methodInfo15 = AccessTools.Method(typeof(Humanoid), "UnequipAllItems", (Type[])null, (Type[])null); MethodInfo methodInfo16 = AccessTools.Method(typeof(Container), "Awake", (Type[])null, (Type[])null); MethodInfo methodInfo17 = AccessTools.Method(typeof(Terminal), "InitTerminal", (Type[])null, (Type[])null); Require(methodInfo, "Inventory.Save(ZPackage)"); Require(methodInfo2, "Inventory.AddItem(string,int,float,Vector2i,bool,int,int,long,string,Dictionary,int,bool)"); Require(methodInfo3, "Inventory.FindEmptySlot(bool)"); Require(methodInfo4, "Inventory.GetEmptySlots()"); Require(methodInfo5, "Inventory.HaveEmptySlot()"); Require(methodInfo6, "Player.Save(ZPackage)"); Require(methodInfo7, "Player.Load(ZPackage)"); Require(methodInfo8, "Player.OnSpawned(bool)"); Require(methodInfo9, "Player.Update()"); Require(methodInfo10, "Player.OnInventoryChanged()"); Require(methodInfo11, "InventoryGrid.DropItem(Inventory,ItemData,int,Vector2i)"); Require(methodInfo12, "Humanoid.UpdateEquipmentStatusEffects()"); Require(methodInfo13, "Humanoid.IsItemEquiped(ItemData)"); Require(methodInfo14, "Humanoid.UnequipItem(ItemData,bool)"); Require(methodInfo15, "Humanoid.UnequipAllItems()"); Require(methodInfo16, "Container.Awake()"); Require(methodInfo17, "Terminal.InitTerminal()"); if (SetupEquipmentMi == null) { throw new Exception("Humanoid.SetupEquipment() not found"); } if (EquipSeFi == null) { throw new Exception("Humanoid.m_equipmentStatusEffects not found"); } if (SemanFi == null) { throw new Exception("Character.m_seman not found"); } Harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeFromHandle, "InvSavePrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "InvSavePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SlotsRescue), "AddItemLoadPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeFromHandle, "FindEmptySlotPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo4, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "GetEmptySlotsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo5, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "HaveEmptySlotPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo6, new HarmonyMethod(typeFromHandle, "PlayerSavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(typeFromHandle, "PlayerLoadPrefix", (Type[])null), new HarmonyMethod(typeFromHandle, "PlayerLoadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo8, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "PlayerSpawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo9, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "PlayerUpdatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo10, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "InventoryChangedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo11, new HarmonyMethod(typeFromHandle, "GridDropPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo12, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "UpdateEquipSePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo13, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "IsItemEquipedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo14, new HarmonyMethod(typeFromHandle, "UnequipItemPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo15, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "UnequipAllPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo16, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "ContainerAwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Harmony.Patch((MethodBase)methodInfo17, (HarmonyMethod)null, new HarmonyMethod(typeFromHandle, "RegisterCommand", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); SlotsUi.Install(Harmony, () => Inst != null && Inst.Active && Inst._showUi.Value, () => (Vector2)((Inst != null) ? new Vector2(Inst._panelOffsetX.Value, Inst._panelOffsetY.Value) : Vector2.zero), () => (Inst != null) ? Inst._panelScale.Value : 1f, QuickKeyLabel); } private unsafe static string QuickKeyLabel(int index) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (index < 0 || index >= _keys.Length) { return ""; } KeyCode val = _keys[index]; if ((int)val != 0) { return ((object)(*(KeyCode*)(&val))/*cast due to .constrained prefix*/).ToString(); } return ""; } private static void Require(MethodBase m, string what) { if (m == null) { throw new Exception(what + " not found"); } } public override void Disable() { SlotStore.Managed = null; base.Disable(); } private static bool Live() { if (Inst != null && Inst.Active) { return FeatureModule.ClientActive(); } return false; } private static bool IsManaged(Inventory inv) { if (inv != null) { return inv == SlotStore.Managed; } return false; } private static void PlayerSavePrefix(Player __instance) { if (!Live() || (Object)(object)__instance == (Object)null) { return; } try { if (IsManaged(((Humanoid)__instance).GetInventory())) { SlotStore.WriteBlob(__instance.m_customData, ((Humanoid)__instance).GetInventory()); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] writing the save blob failed: " + ex)); } } private static void InvSavePrefix(Inventory __instance) { if (!Live() || !IsManaged(__instance)) { return; } try { SlotStore.Stash(__instance); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] stash failed: " + ex)); } } private static void InvSavePostfix(Inventory __instance) { if (!Live() || !IsManaged(__instance)) { return; } try { SlotStore.Unstash(__instance); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] unstash failed - items may be missing until relog: " + ex)); } } private static void PlayerLoadPrefix(Player __instance) { if (!Live() || (Object)(object)__instance == (Object)null) { return; } try { Inventory inv = (SlotStore.Managed = ((Humanoid)__instance).GetInventory()); SlotStore.SetHeight(inv, 4); SlotsRescue.BeginCapture(inv); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] load prefix failed: " + ex)); } } private static void PlayerLoadPostfix(Player __instance) { if (!Live() || (Object)(object)__instance == (Object)null) { return; } try { SlotsRescue.EndCapture(); Inventory inv = (SlotStore.Managed = ((Humanoid)__instance).GetInventory()); SlotStore.SetHeight(inv, SlotLayout.TotalHeight); List list = SlotStore.ReadBlob(__instance.m_customData, 0); List list2 = new List(); int num = SlotStore.Inject(inv, list, list2); if (list != null) { FeatureModule.Log.LogInfo((object)("[Slots] loaded " + num + "/" + list.Count + " item(s) from nvlb.slots (" + SlotLayout.Describe() + ")")); } SlotStore.Evacuate(__instance, inv, list2, "slot no longer exists"); SlotsRescue.Place(__instance, inv); SlotStore.Changed(inv); SlotsUi.Invalidate(); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] load postfix failed: " + ex)); } } private static void PlayerSpawnedPostfix(Player __instance) { if (!Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Inventory inv = (SlotStore.Managed = ((Humanoid)__instance).GetInventory()); SlotStore.SetHeight(inv, SlotLayout.TotalHeight); List list = SlotStore.Orphans(inv); if (list.Count > 0) { SlotStore.Evacuate(__instance, inv, list, "no slot at that cell"); } SlotStore.Changed(inv); SlotsUi.Invalidate(); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] spawn setup failed: " + ex)); } } private static bool FindEmptySlotPrefix(Inventory __instance, bool topFirst, ref Vector2i __result) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_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_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) if (!Live() || !IsManaged(__instance)) { return true; } int width = __instance.GetWidth(); if (topFirst) { for (int i = 0; i < 4; i++) { for (int j = 0; j < width; j++) { if (__instance.GetItemAt(j, i) == null) { __result = new Vector2i(j, i); return false; } } } } else { for (int num = 3; num >= 0; num--) { for (int k = 0; k < width; k++) { if (__instance.GetItemAt(k, num) == null) { __result = new Vector2i(k, num); return false; } } } } __result = new Vector2i(-1, -1); return false; } private static int VanillaAreaUsed(Inventory inv) { int num = 0; List list = SlotStore.Items(inv); for (int i = 0; i < list.Count; i++) { if (list[i].m_gridPos.y < 4) { num++; } } return num; } private static void GetEmptySlotsPostfix(Inventory __instance, ref int __result) { if (Live() && IsManaged(__instance)) { __result = 32 - VanillaAreaUsed(__instance); } } private static void HaveEmptySlotPostfix(Inventory __instance, ref bool __result) { if (Live() && IsManaged(__instance)) { __result = VanillaAreaUsed(__instance) < 32; } } private static bool GridDropPrefix(InventoryGrid __instance, ItemData item, Vector2i pos, ref bool __result) { //IL_0019: 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) if (!Live() || item == null) { return true; } if (!IsManaged(__instance.GetInventory()) || !SlotLayout.IsExtra(pos)) { return true; } SlotDef slotDef = SlotLayout.At(pos); if (slotDef != null && SlotLayout.Accepts(slotDef, item)) { return true; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).Message((MessageType)2, (slotDef == null) ? "This slot is not in use" : ("Only " + slotDef.Label + " items fit here"), 0, (Sprite)null); } __result = false; return false; } private static void InventoryChangedPostfix(Player __instance) { if (Live() && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { SyncEquipment(__instance); } } internal static void SyncEquipment(Player p) { if (_syncing || (Object)(object)p == (Object)null) { return; } _syncing = true; try { Inventory inventory = ((Humanoid)p).GetInventory(); if (!IsManaged(inventory)) { return; } bool flag = false; for (int i = 0; i < ExtraUtility.Length; i++) { ItemData val = ExtraUtility[i]; if (val != null) { SlotDef slotDef = SlotLayout.ByKey("utility" + (i + 2)); if (slotDef == null || inventory.GetItemAt(slotDef.Pos.x, slotDef.Pos.y) != val) { val.m_equipped = false; ExtraUtility[i] = null; flag = true; } } } IList slots = SlotLayout.Slots; for (int j = 0; j < slots.Count; j++) { SlotDef slotDef2 = slots[j]; if (!SlotLayout.IsEquipmentKind(slotDef2.Kind)) { continue; } ItemData itemAt = inventory.GetItemAt(slotDef2.Pos.x, slotDef2.Pos.y); if (itemAt == null) { continue; } if (slotDef2.Kind == SlotKind.Utility && slotDef2.Index >= 2) { int num = slotDef2.Index - 2; if (num < ExtraUtility.Length && ExtraUtility[num] != itemAt) { ExtraUtility[num] = itemAt; itemAt.m_equipped = true; flag = true; } } else if (!((Humanoid)p).IsItemEquiped(itemAt)) { ((Humanoid)p).EquipItem(itemAt, false); } } if (flag && SetupEquipmentMi != null) { SetupEquipmentMi.Invoke(p, null); } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] equipment sync failed: " + ex)); } finally { _syncing = false; } } private static void UpdateEquipSePostfix(Humanoid __instance) { if (!Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { HashSet hashSet = EquipSeFi.GetValue(__instance) as HashSet; object? value = SemanFi.GetValue(__instance); SEMan val = (SEMan)((value is SEMan) ? value : null); if (hashSet == null || val == null) { return; } for (int i = 0; i < ExtraUtility.Length; i++) { ItemData val2 = ExtraUtility[i]; if (val2 != null && val2.m_shared != null) { StatusEffect equipStatusEffect = val2.m_shared.m_equipStatusEffect; if (!((Object)(object)equipStatusEffect == (Object)null) && !hashSet.Contains(equipStatusEffect)) { val.AddStatusEffect(equipStatusEffect, false, 0, 0f); hashSet.Add(equipStatusEffect); } } } } catch (Exception ex) { FeatureModule.Log.LogError((object)("[Slots] extra utility status effects failed: " + ex)); } } private static void IsItemEquipedPostfix(Humanoid __instance, ItemData item, ref bool __result) { if (__result || item == null || !Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } for (int i = 0; i < ExtraUtility.Length; i++) { if (ExtraUtility[i] == item) { __result = true; break; } } } private static void UnequipItemPrefix(Humanoid __instance, ItemData item) { if (item == null || !Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } for (int i = 0; i < ExtraUtility.Length; i++) { if (ExtraUtility[i] == item) { ExtraUtility[i] = null; item.m_equipped = false; } } } private static void UnequipAllPostfix(Humanoid __instance) { if (!Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } for (int i = 0; i < ExtraUtility.Length; i++) { if (ExtraUtility[i] != null) { ExtraUtility[i].m_equipped = false; } ExtraUtility[i] = null; } } private static void ContainerAwakePostfix(Container __instance) { if (!Live() || (Object)(object)__instance == (Object)null) { return; } try { if ((Object)(object)((Component)__instance).GetComponent() == (Object)null) { return; } Inventory inventory = __instance.GetInventory(); if (inventory != null) { int num = 8; if (SlotStore.GetHeight(inventory) < num) { SlotStore.SetHeight(inventory, num); } } } catch (Exception ex) { FeatureModule.Log.LogWarning((object)("[Slots] tombstone widening failed: " + ex.Message)); } } private static void PlayerUpdatePostfix(Player __instance) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) if (!Live() || (Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer) { return; } try { Inventory inventory = ((Humanoid)__instance).GetInventory(); if (!IsManaged(inventory)) { return; } if (InputAllowed(__instance)) { for (int i = 0; i < SlotLayout.QuickCount && i < _keys.Length; i++) { KeyCode val = _keys[i]; if ((int)val == 0 || !ZInput.GetKeyDown(val, false)) { continue; } SlotDef slotDef = SlotLayout.ByKey("quick" + (i + 1)); if (slotDef != null) { ItemData itemAt = inventory.GetItemAt(slotDef.Pos.x, slotDef.Pos.y); if (itemAt != null) { ((Humanoid)__instance).UseItem((Inventory)null, itemAt, false); } } } } if (Inst._autoEat.Value && SlotLayout.FoodCount > 0) { _eatTimer += Time.deltaTime; if (_eatTimer >= 1f) { _eatTimer = 0f; AutoEat(__instance, inventory); } } } catch (Exception ex) { if (_tickErrors++ < 3) { FeatureModule.Log.LogError((object)("[Slots] update tick failed (" + _tickErrors + "/3): " + ex)); } } } private static void AutoEat(Player p, Inventory inv) { if (((Character)p).IsDead() || ((Character)p).InCutscene()) { return; } for (int i = 1; i <= SlotLayout.FoodCount; i++) { SlotDef slotDef = SlotLayout.ByKey("food" + i); if (slotDef != null) { ItemData itemAt = inv.GetItemAt(slotDef.Pos.x, slotDef.Pos.y); if (itemAt != null && SlotLayout.IsFood(itemAt) && p.CanEat(itemAt, false)) { ((Humanoid)p).UseItem((Inventory)null, itemAt, false); break; } } } } private static bool InputAllowed(Player me) { if (!((Character)me).TakeInput()) { return false; } if (Hud.InRadial() || Hud.IsPieceSelectionVisible()) { return false; } if ((Object)(object)Chat.instance != (Object)null && Chat.instance.HasFocus()) { return false; } if (Console.IsVisible() || TextInput.IsVisible()) { return false; } if (InventoryGui.IsVisible() || StoreGui.IsVisible() || Menu.IsVisible() || Minimap.IsOpen()) { return false; } return true; } private static void RegisterCommand() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (_commandRegistered) { return; } _commandRegistered = true; try { new ConsoleCommand("nvlb.slots.restore", "Re-inject the extra-slot items from a backup: nvlb.slots.restore [1|2|3]", new ConsoleEvent(RestoreCommand), false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); FeatureModule.Log.LogInfo((object)"[Slots] console command 'nvlb.slots.restore' registered"); } catch (Exception ex) { _commandRegistered = false; FeatureModule.Log.LogError((object)("[Slots] could not register nvlb.slots.restore: " + ex)); } } private static void RestoreCommand(ConsoleEventArgs args) { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Say(args, "no local player"); return; } int result = 1; if (args.Length > 1 && !int.TryParse(args[1], out result)) { result = 1; } result = Mathf.Clamp(result, 0, 3); List list = SlotStore.ReadBlob(localPlayer.m_customData, result); if (list == null || list.Count == 0) { Say(args, "backup " + result + " is empty or unreadable (keys: nvlb.slots, " + SlotStore.BackupKey(1) + "..." + SlotStore.BackupKey(3) + ")"); } else { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); List list2 = new List(); int num = SlotStore.Inject(inventory, list, list2); SlotStore.Evacuate(localPlayer, inventory, list2, "nvlb.slots.restore"); SlotStore.Changed(inventory); Say(args, "restored " + num + "/" + list.Count + " item(s) from backup " + result); } } private static void Say(ConsoleEventArgs args, string s) { if ((Object)(object)args.Context != (Object)null) { args.Context.AddString("[Slots] " + s); } FeatureModule.Log.LogInfo((object)("[Slots] " + s)); } public override string StatusDetail() { string text = SlotLayout.Describe() + " autoEat=" + (_autoEat != null && _autoEat.Value) + " ui=" + (_showUi != null && _showUi.Value) + ((SlotLayout.QuickCount > 0) ? (" keys=" + ((_quickKeys != null) ? _quickKeys.Value : "")) : ""); if (SlotStore.Managed != null) { text = text + " live=" + SlotStore.ExtraItems(SlotStore.Managed).Count + " item(s)"; } return text; } } internal sealed class SlotEntry { public string SlotKey; public ItemData Item; } internal static class SlotBlob { public const int BlobVersion = 1; private const int ItemVersion = 106; public static string Encode(IList entries) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write(106); List list = new List(); for (int i = 0; i < entries.Count; i++) { SlotEntry slotEntry = entries[i]; if (slotEntry != null && slotEntry.Item != null && !string.IsNullOrEmpty(slotEntry.SlotKey)) { if (PrefabNameOf(slotEntry.Item) == null) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] cannot save item in slot " + slotEntry.SlotKey + " - no drop prefab; it stays in the live grid")); } else { list.Add(slotEntry); } } } val.Write(list.Count); for (int j = 0; j < list.Count; j++) { SlotEntry slotEntry2 = list[j]; ItemData item = slotEntry2.Item; val.Write(slotEntry2.SlotKey); val.Write(PrefabNameOf(item)); val.Write(item.m_stack); val.Write(item.m_durability); val.Write(item.m_equipped); val.Write(item.m_quality); val.Write(item.m_variant); val.Write(item.m_crafterID); val.Write(item.m_crafterName ?? ""); Dictionary customData = item.m_customData; val.Write(customData?.Count ?? 0); if (customData != null) { foreach (KeyValuePair item2 in customData) { val.Write(item2.Key); val.Write(item2.Value); } } val.Write(item.m_worldLevel); val.Write(item.m_pickedUp); } return 1 + "|" + list.Count + "|" + val.GetBase64(); } public static List Decode(string blob) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown if (string.IsNullOrEmpty(blob)) { return null; } try { int num = blob.IndexOf('|'); if (num <= 0) { return null; } if (!int.TryParse(blob.Substring(0, num), out var result) || result != 1) { return null; } int num2 = blob.IndexOf('|', num + 1); if (num2 < 0) { return null; } ZPackage val = new ZPackage(blob.Substring(num2 + 1)); int num3 = val.ReadInt(); if (num3 != 106) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] blob item version " + num3 + " != " + 106 + " - refusing to read it")); return null; } int num4 = val.ReadInt(); List list = new List(num4); for (int i = 0; i < num4; i++) { string text = val.ReadString(); string text2 = val.ReadString(); int stack = val.ReadInt(); float durability = val.ReadSingle(); bool equipped = val.ReadBool(); int quality = val.ReadInt(); int variant = val.ReadInt(); long crafterID = val.ReadLong(); string crafterName = val.ReadString(); Dictionary dictionary = new Dictionary(); int num5 = val.ReadInt(); for (int j = 0; j < num5; j++) { string key = val.ReadString(); dictionary[key] = val.ReadString(); } int worldLevel = val.ReadInt(); bool pickedUp = val.ReadBool(); ItemData val2 = MakeItem(text2, stack, durability, equipped, quality, variant, crafterID, crafterName, dictionary, worldLevel, pickedUp); if (val2 == null) { NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] blob item '" + text2 + "' not in ObjectDB - skipped (slot " + text + ")")); } else { list.Add(new SlotEntry { SlotKey = text, Item = val2 }); } } return list; } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] blob decode failed: " + ex.Message)); return null; } } public static ItemData MakeItem(string name, int stack, float durability, bool equipped, int quality, int variant, long crafterID, string crafterName, Dictionary customData, int worldLevel, bool pickedUp) { if (string.IsNullOrEmpty(name)) { return null; } if ((Object)(object)ObjectDB.instance == (Object)null) { return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(name); if ((Object)(object)itemPrefab == (Object)null) { return null; } GameObject val = null; try { ZNetView.m_forceDisableInit = true; val = Object.Instantiate(itemPrefab); } finally { ZNetView.m_forceDisableInit = false; } try { ItemDrop component = val.GetComponent(); if ((Object)(object)component == (Object)null) { return null; } ItemData itemData = component.m_itemData; itemData.m_stack = Mathf.Min(Mathf.Max(1, stack), itemData.m_shared.m_maxStackSize); itemData.m_durability = durability; itemData.m_equipped = equipped; component.SetQuality(quality); itemData.m_variant = variant; itemData.m_crafterID = crafterID; itemData.m_crafterName = crafterName ?? ""; if (customData != null) { itemData.m_customData = customData; } itemData.m_worldLevel = (byte)worldLevel; itemData.m_pickedUp = pickedUp; ItemData obj = itemData.Clone(); obj.m_dropPrefab = itemPrefab; return obj; } finally { Object.Destroy((Object)(object)val); } } public static string PrefabNameOf(ItemData item) { if (item == null) { return null; } if ((Object)(object)item.m_dropPrefab != (Object)null) { return ((Object)item.m_dropPrefab).name; } return null; } public static string Describe(ItemData item) { if (item == null) { return "null"; } return (PrefabNameOf(item) ?? ((item.m_shared != null) ? item.m_shared.m_name : "?")) + " x" + item.m_stack + ((item.m_quality > 1) ? (" q" + item.m_quality) : ""); } } internal enum SlotKind { Blocked, Helmet, Chest, Legs, Cape, Utility, Food, Ammo, Quick, Generic } internal sealed class SlotDef { public string Key; public string Label; public string PanelLabel; public SlotKind Kind; public int Index; public Vector2i Pos; public Vector2 PanelTile; } internal static class SlotLayout { public const int VanillaWidth = 8; public const int VanillaHeight = 4; public const int MaxExtraRows = 4; private static readonly List _slots; private static readonly Dictionary _byKey; private static SlotDef[,] _grid; public static int Rows { get; private set; } public static int UtilityCount { get; private set; } public static int FoodCount { get; private set; } public static int AmmoCount { get; private set; } public static int QuickCount { get; private set; } public static int GenericCount { get; private set; } public static bool EquipmentOn { get; private set; } public static float PanelTilesWide { get; private set; } public static float PanelTilesHigh { get; private set; } public static int TotalHeight => 4 + Rows; public static IList Slots => _slots; static SlotLayout() { _slots = new List(); _byKey = new Dictionary(); _grid = new SlotDef[8, 4]; Rebuild(equipment: true, 2, 3, 2, 0, 2); } public static void Rebuild(bool equipment, int utility, int food, int ammo, int quick, int generic) { //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) EquipmentOn = equipment; UtilityCount = Mathf.Clamp(utility, 0, 4); FoodCount = Mathf.Clamp(food, 0, 3); AmmoCount = Mathf.Clamp(ammo, 0, 4); QuickCount = Mathf.Clamp(quick, 0, 8); GenericCount = Mathf.Clamp(generic, 0, 8); _slots.Clear(); _byKey.Clear(); if (equipment) { Add(SlotKind.Helmet, 1, "helmet", "Helmet", "Head"); Add(SlotKind.Chest, 1, "chest", "Chest", "Chest"); Add(SlotKind.Legs, 1, "legs", "Legs", "Legs"); Add(SlotKind.Cape, 1, "cape", "Cape", "Back"); } for (int i = 1; i <= UtilityCount; i++) { Add(SlotKind.Utility, i, "utility" + i, "Utility", "Utility"); } for (int j = 1; j <= FoodCount; j++) { Add(SlotKind.Food, j, "food" + j, "Food", ""); } for (int k = 1; k <= AmmoCount; k++) { Add(SlotKind.Ammo, k, "ammo" + k, "Ammo", ""); } for (int l = 1; l <= QuickCount; l++) { Add(SlotKind.Quick, l, "quick" + l, "Quick", ""); } for (int m = 1; m <= GenericCount; m++) { Add(SlotKind.Generic, m, "generic" + m, "Storage", ""); } int num = (_slots.Count + 8 - 1) / 8; if (num > 4) { num = 4; if (_slots.Count > num * 8) { _slots.RemoveRange(num * 8, _slots.Count - num * 8); } } Rows = num; _grid = new SlotDef[8, 4]; for (int n = 0; n < _slots.Count; n++) { SlotDef slotDef = _slots[n]; slotDef.Pos = new Vector2i(n % 8, 4 + n / 8); _grid[slotDef.Pos.x, slotDef.Pos.y - 4] = slotDef; _byKey[slotDef.Key] = slotDef; } ComputePanel(); } private static void Add(SlotKind kind, int index, string key, string label, string panelLabel) { _slots.Add(new SlotDef { Key = key, Label = label, PanelLabel = panelLabel, Kind = kind, Index = index }); } private static void ComputePanel() { //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) List list = new List(); List list2 = new List(); List list3 = new List(); List list4 = new List(); for (int i = 0; i < _slots.Count; i++) { SlotDef slotDef = _slots[i]; if (IsEquipmentKind(slotDef.Kind)) { list.Add(slotDef); } else if (slotDef.Kind == SlotKind.Food) { list2.Add(slotDef); } else if (slotDef.Kind == SlotKind.Ammo) { list3.Add(slotDef); } else if (slotDef.Kind == SlotKind.Quick || slotDef.Kind == SlotKind.Generic) { list4.Add(slotDef); } } int num = ((list.Count > 0) ? ((list.Count - 1) / 3) : (-1)); int num2 = ((list.Count > 3 || list2.Count > 0 || list3.Count > 0) ? 3 : list.Count); int num3 = Math.Max(list4.Count - 1 - num, 0) * 2; for (int j = 0; j < list.Count; j++) { list[j].PanelTile = new Vector2((float)(j / 3 * 4 + num3), (float)(j % 3 * 4)); } int num4 = Math.Max(num + 1, list4.Count); for (int k = 0; k < list2.Count; k++) { list2[k].PanelTile = new Vector2((float)(num4 * 4 + 1), (float)(k * 4)); } for (int l = 0; l < list3.Count; l++) { list3[l].PanelTile = new Vector2((float)(num4 * 4 + 1 + ((list2.Count > 0) ? 4 : 0)), (float)(l * 4)); } int num5 = Math.Max(num + 1 - list4.Count, 0) * 2; for (int m = 0; m < list4.Count; m++) { list4[m].PanelTile = new Vector2((float)(m * 4 + num5), (float)(num2 * 4 + 1)); } float num6 = ((list2.Count > 0 || list3.Count > 0) ? 0.25f : 0f) + ((list2.Count > 0) ? 1f : 0f) + ((list3.Count > 0) ? 1f : 0f); PanelTilesWide = (float)Math.Max(list4.Count, num + 1) + num6; PanelTilesHigh = ((list4.Count > 0) ? 1.25f : 0f) + (float)num2; } public static SlotDef At(int x, int y) { if (y < 4 || y >= TotalHeight) { return null; } if (x < 0 || x >= 8) { return null; } return _grid[x, y - 4]; } public static SlotDef At(Vector2i p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) return At(p.x, p.y); } public static SlotDef LegacyKey(string key) { if (string.IsNullOrEmpty(key)) { return null; } if (key.StartsWith("quick", StringComparison.Ordinal) && int.TryParse(key.Substring(5), out var result) && result >= 1) { SlotDef slotDef = ByKey("generic" + result); if (slotDef != null) { return slotDef; } } return null; } public static SlotDef ByKey(string key) { if (key == null || !_byKey.TryGetValue(key, out SlotDef value)) { return null; } return value; } public static bool IsExtra(Vector2i p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) if (p.y >= 4 && p.y < TotalHeight && p.x >= 0) { return p.x < 8; } return false; } public static bool IsFood(ItemData item) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 if (item == null || item.m_shared == null) { return false; } if ((int)item.m_shared.m_itemType != 2) { return false; } if (!(item.m_shared.m_food > 0f) && !(item.m_shared.m_foodStamina > 0f)) { return item.m_shared.m_foodEitr > 0f; } return true; } public static bool Accepts(SlotDef slot, ItemData item) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Invalid comparison between Unknown and I4 //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Invalid comparison between Unknown and I4 //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Invalid comparison between Unknown and I4 //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Invalid comparison between Unknown and I4 //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Invalid comparison between Unknown and I4 if (slot == null || item == null || item.m_shared == null) { return false; } ItemType itemType = item.m_shared.m_itemType; switch (slot.Kind) { case SlotKind.Helmet: return (int)itemType == 6; case SlotKind.Chest: return (int)itemType == 7; case SlotKind.Legs: return (int)itemType == 11; case SlotKind.Cape: return (int)itemType == 17; case SlotKind.Utility: return (int)itemType == 18; case SlotKind.Food: return IsFood(item); case SlotKind.Ammo: if ((int)itemType != 9) { return (int)itemType == 23; } return true; case SlotKind.Quick: return true; case SlotKind.Generic: return true; default: return false; } } public static bool IsEquipmentKind(SlotKind k) { if (k != SlotKind.Helmet && k != SlotKind.Chest && k != SlotKind.Legs && k != SlotKind.Cape) { return k == SlotKind.Utility; } return true; } public static SlotDef FindFreeFor(Inventory inv, ItemData item) { if (inv == null) { return null; } for (int i = 0; i < _slots.Count; i++) { SlotDef slotDef = _slots[i]; if (Accepts(slotDef, item) && inv.GetItemAt(slotDef.Pos.x, slotDef.Pos.y) == null) { return slotDef; } } return null; } public static string Describe() { return "equipment=" + (EquipmentOn ? "on" : "off") + " utility=" + UtilityCount + " food=" + FoodCount + " ammo=" + AmmoCount + " quick=" + QuickCount + " generic=" + GenericCount + " -> " + _slots.Count + " slots in " + Rows + " row(s)"; } } internal static class SlotsRescue { private static Inventory _captureInv; private static readonly List _captured = new List(); private static readonly List _capturedPos = new List(); public static bool Capturing => _captureInv != null; public static int CapturedCount => _captured.Count; public static void BeginCapture(Inventory inv) { _captureInv = inv; _captured.Clear(); _capturedPos.Clear(); } public static void EndCapture() { _captureInv = null; } public static bool AddItemLoadPrefix(Inventory __instance, string name, int stack, float durability, Vector2i pos, bool equipped, int quality, int variant, long crafterID, string crafterName, Dictionary customData, int worldLevel, bool pickedUp, ref bool __result) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (_captureInv == null || __instance != _captureInv) { return true; } if (pos.x >= 0 && pos.y >= 0 && pos.x < __instance.GetWidth() && pos.y < SlotStore.GetHeight(__instance)) { return true; } try { ItemData val = SlotBlob.MakeItem(name, stack, durability, equipped, quality, variant, crafterID, crafterName, customData, worldLevel, pickedUp); if (val == null) { NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] RESCUE: '" + name + "' at grid " + pos.x + "," + pos.y + " is out of bounds and its prefab is missing - cannot save it")); __result = false; return false; } _captured.Add(val); _capturedPos.Add(pos); NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] RESCUE: captured " + SlotBlob.Describe(val) + " from out-of-bounds grid position " + pos.x + "," + pos.y + " (vanilla would have deleted it)")); __result = true; return false; } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] RESCUE failed for '" + name + "': " + ex)); return true; } } public static string Place(Player player, Inventory inv) { //IL_003f: 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_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) if (_captured.Count == 0) { return null; } int num = 0; List list = new List(); for (int i = 0; i < _captured.Count; i++) { ItemData item = _captured[i]; SlotDef slotDef = SlotLayout.FindFreeFor(inv, item); if (slotDef != null && SlotStore.PlaceRaw(inv, item, slotDef.Pos)) { num++; NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Slots] RESCUE: " + SlotBlob.Describe(item) + " -> extra slot '" + slotDef.Key + "'")); } else { list.Add(item); } } int num2 = 0; for (int num3 = list.Count - 1; num3 >= 0; num3--) { Vector2i val = SlotStore.FindFreeVanillaCell(inv); if (val.x < 0 || !SlotStore.PlaceRaw(inv, list[num3], val)) { break; } NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Slots] RESCUE: " + SlotBlob.Describe(list[num3]) + " -> inventory cell " + val.x + "," + val.y)); list.RemoveAt(num3); num2++; } int count = list.Count; if (count > 0) { SlotStore.Evacuate(player, inv, list, "migration rescue"); } SlotStore.Changed(inv); string text = "[Slots] RESCUE: recovered " + _captured.Count + " item(s) that vanilla would have deleted - " + num + " into extra slots, " + num2 + " into the bag, " + count + " evacuated"; NoVikingLeftBehindPlugin.Log.LogWarning((object)text); if ((Object)(object)player != (Object)null) { ((Character)player).Message((MessageType)2, "NVLB recovered " + _captured.Count + " item(s) from your old extra slots", 0, (Sprite)null); } _captured.Clear(); _capturedPos.Clear(); return text; } } internal sealed class SlotsSelfTestModule : FeatureModule { private static ConfigEntry _selfTest; private static bool _ran; private static int _pass; private static int _fail; public override string Name => "SlotsSelfTest"; public override ModuleSide Side => ModuleSide.Both; public override string Section => "SlotsSelfTest"; protected override void Bind() { _selfTest = BindLocal("Slots", "SelfTest", defaultValue: false, "Diagnostic. Once per world load, prove the extra-slot storage layer headlessly: save lift, blob round trip, migration rescue and loadout encoding. Machine-local, never synced. Leave it false in normal use."); } protected override void ApplyPatches() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0050: 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 MethodInfo methodInfo = AccessTools.Method(typeof(ZoneSystem), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { throw new Exception("ZoneSystem.Start() not found"); } Harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SlotsSelfTestModule), "WorldReady", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); if (_selfTest != null && _selfTest.Value) { MethodInfo methodInfo2 = AccessTools.Method(typeof(Inventory), "AddItem", new Type[12] { typeof(string), typeof(int), typeof(float), typeof(Vector2i), typeof(bool), typeof(int), typeof(int), typeof(long), typeof(string), typeof(Dictionary), typeof(int), typeof(bool) }, (Type[])null); if (methodInfo2 == null) { throw new Exception("Inventory.AddItem(string,int,float,Vector2i,...) not found"); } Harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SlotsRescue), "AddItemLoadPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void WorldReady() { if (!_ran && _selfTest != null && _selfTest.Value) { _ran = true; _pass = (_fail = 0); FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] --- begin --- " + SlotLayout.Describe())); try { TestSaveLoad(); TestGenericSlots(); TestMigrationRescue(); TestLoadoutRoundTrip(); } catch (Exception ex) { FeatureModule.Log.LogError((object)("[SlotsSelfTest] threw: " + ex)); _fail++; } FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] --- end --- " + _pass + " passed, " + _fail + " FAILED")); } } private static void Check(bool ok, string what) { if (ok) { _pass++; FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] PASS " + what)); } else { _fail++; FeatureModule.Log.LogError((object)("[SlotsSelfTest] FAIL " + what)); } } private static ItemData Make(string prefab, int stack) { return SlotBlob.MakeItem(prefab, stack, 100f, equipped: false, 1, 0, 0L, "", null, 0, pickedUp: false); } private static string NamesIn(Inventory inv) { StringBuilder stringBuilder = new StringBuilder(); List allItems = inv.GetAllItems(); for (int i = 0; i < allItems.Count; i++) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(SlotBlob.PrefabNameOf(allItems[i]) ?? "?").Append("@").Append(allItems[i].m_gridPos.x) .Append(",") .Append(allItems[i].m_gridPos.y); } return stringBuilder.ToString(); } private static bool Has(Inventory inv, string prefab) { List allItems = inv.GetAllItems(); for (int i = 0; i < allItems.Count; i++) { if (SlotBlob.PrefabNameOf(allItems[i]) == prefab) { return true; } } return false; } private static void TestSaveLoad() { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_035d: Expected O, but got Unknown //IL_03b5: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Expected O, but got Unknown //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_05f1: Unknown result type (might be due to invalid IL or missing references) //IL_05f8: Expected O, but got Unknown if ((Object)(object)ObjectDB.instance == (Object)null) { FeatureModule.Log.LogWarning((object)"[SlotsSelfTest] ObjectDB not ready - test 1 skipped"); return; } List> list = new List> { new KeyValuePair("helmet", "HelmetBronze"), new KeyValuePair("chest", "ArmorBronzeChest"), new KeyValuePair("utility1", "BeltStrength"), new KeyValuePair("food1", "CookedMeat"), new KeyValuePair("ammo1", "ArrowWood"), new KeyValuePair("generic1", "Stone") }; Inventory val = new Inventory("nvlb-selftest", (Sprite)null, 8, 4); GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("Wood"); if ((Object)(object)itemPrefab != (Object)null) { val.AddItem(itemPrefab, 12); } int count = val.GetAllItems().Count; SlotStore.SetHeight(val, SlotLayout.TotalHeight); List> list2 = new List>(); for (int i = 0; i < list.Count; i++) { SlotDef slotDef = SlotLayout.ByKey(list[i].Key); if (slotDef == null) { FeatureModule.Log.LogWarning((object)("[SlotsSelfTest] no slot '" + list[i].Key + "' in this layout - skipped")); continue; } int stack = ((list[i].Value == "ArrowWood") ? 20 : ((!(list[i].Value == "Stone")) ? 1 : 5)); ItemData val2 = Make(list[i].Value, stack); if (val2 == null) { FeatureModule.Log.LogWarning((object)("[SlotsSelfTest] prefab '" + list[i].Value + "' not in ObjectDB - skipped")); continue; } if (!SlotLayout.Accepts(slotDef, val2)) { Check(ok: false, list[i].Value + " should be accepted by slot " + slotDef.Key); continue; } Check(SlotStore.PlaceRaw(val, val2, slotDef.Pos), "placed " + list[i].Value + " into slot " + slotDef.Key + " at " + slotDef.Pos.x + "," + slotDef.Pos.y); list2.Add(list[i]); } Check(list2.Count > 0, "at least one extra-slot item placed (" + list2.Count + ")"); FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] live grid: " + NamesIn(val))); List list3 = SlotStore.Collect(val); Check(list3.Count == list2.Count, "Collect() saw all " + list2.Count + " extra item(s), got " + list3.Count); string text = SlotBlob.Encode(list3); SlotStore.Stash(val); ZPackage val3 = new ZPackage(); val.Save(val3); SlotStore.Unstash(val); Check(val.GetAllItems().Count == count + list2.Count, "unstash restored the grid (" + val.GetAllItems().Count + " items)"); Inventory val4 = new Inventory("nvlb-verify", (Sprite)null, 8, SlotLayout.TotalHeight); val3.SetPos(0); val4.Load(val3); bool flag = false; for (int j = 0; j < list2.Count; j++) { if (Has(val4, list2[j].Value)) { flag = true; } } Check(!flag, "the vanilla ZPackage contains NONE of the extra-slot items"); Check(val4.GetAllItems().Count == count, "the vanilla ZPackage still contains the " + count + " ordinary item(s)"); List list4 = SlotBlob.Decode(text); Check(list4 != null && list4.Count == list2.Count, "the blob decodes to all " + list2.Count + " item(s), got " + (list4?.Count ?? (-1))); if (list4 != null) { for (int k = 0; k < list2.Count; k++) { bool ok = false; for (int l = 0; l < list4.Count; l++) { if (list4[l].SlotKey == list2[k].Key && SlotBlob.PrefabNameOf(list4[l].Item) == list2[k].Value) { ok = true; } } Check(ok, "blob holds " + list2[k].Value + " keyed to slot '" + list2[k].Key + "'"); } } FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] blob is " + text.Length + " chars: " + text.Substring(0, Math.Min(72, text.Length)) + ((text.Length > 72) ? "..." : ""))); Inventory val5 = new Inventory("nvlb-reload", (Sprite)null, 8, 4); val3.SetPos(0); val5.Load(val3); SlotStore.SetHeight(val5, SlotLayout.TotalHeight); List list5 = new List(); int num = SlotStore.Inject(val5, SlotBlob.Decode(text), list5); Check(num == list2.Count && list5.Count == 0, "re-injected " + num + "/" + list2.Count + " item(s), " + list5.Count + " leftover"); for (int m = 0; m < list2.Count; m++) { SlotDef slotDef2 = SlotLayout.ByKey(list2[m].Key); ItemData itemAt = val5.GetItemAt(slotDef2.Pos.x, slotDef2.Pos.y); Check(itemAt != null && SlotBlob.PrefabNameOf(itemAt) == list2[m].Value, list2[m].Value + " is back in slot '" + list2[m].Key + "'"); } FeatureModule.Log.LogInfo((object)("[SlotsSelfTest] reloaded grid: " + NamesIn(val5))); } private static void TestGenericSlots() { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Expected O, but got Unknown //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Expected O, but got Unknown if ((Object)(object)ObjectDB.instance == (Object)null) { FeatureModule.Log.LogWarning((object)"[SlotsSelfTest] ObjectDB not ready - test 1b skipped"); return; } SlotDef slotDef = SlotLayout.ByKey("generic1"); Check(slotDef != null, "the layout has a generic1 slot (GenericSlots=" + SlotLayout.GenericCount + ")"); if (slotDef != null) { ItemData val = Make("HelmetBronze", 1); ItemData val2 = Make("ArrowWood", 10); Check(val == null || SlotLayout.Accepts(slotDef, val), "a helmet fits in generic1"); Check(val2 == null || SlotLayout.Accepts(slotDef, val2), "an arrow stack fits in generic1"); Check(SlotLayout.QuickCount > 0 || SlotLayout.ByKey("quick1") == null, "no quick slot exists at the default QuickSlots=0 (QuickCount=" + SlotLayout.QuickCount + ")"); SlotDef slotDef2 = SlotLayout.LegacyKey("quick1"); Check(slotDef2 != null && slotDef2.Key == "generic1", "a 0.4.1 blob key 'quick1' maps onto '" + ((slotDef2 == null) ? "null" : slotDef2.Key) + "'"); if (val2 != null) { Inventory val3 = new Inventory("nvlb-migrate-quick", (Sprite)null, 8, 4); SlotStore.SetHeight(val3, SlotLayout.TotalHeight); string blob = SlotBlob.Encode(new List { new SlotEntry { SlotKey = "quick1", Item = val2 } }); List list = new List(); int num = SlotStore.Inject(val3, SlotBlob.Decode(blob), list); ItemData itemAt = val3.GetItemAt(slotDef.Pos.x, slotDef.Pos.y); Check(num == 1 && list.Count == 0 && itemAt != null && SlotBlob.PrefabNameOf(itemAt) == "ArrowWood" && itemAt.m_stack == 10, "a legacy 'quick1' item is migrated into generic1 with its stack intact"); } } } private static void TestMigrationRescue() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: 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_0101: Expected O, but got Unknown if ((Object)(object)ObjectDB.instance == (Object)null) { FeatureModule.Log.LogWarning((object)"[SlotsSelfTest] ObjectDB not ready - test 2 skipped"); return; } if ((Object)(object)ObjectDB.instance.GetItemPrefab("ArrowWood") == (Object)null) { FeatureModule.Log.LogWarning((object)"[SlotsSelfTest] no ArrowWood - test 2 skipped"); return; } ZPackage val = new ZPackage(); val.Write(106); val.Write(1); val.Write("ArrowWood"); val.Write(42); val.Write(1f); val.Write(new Vector2i(0, 7)); val.Write(false); val.Write(1); val.Write(0); val.Write(0L); val.Write(""); val.Write(0); val.Write(0); val.Write(false); Inventory val2 = new Inventory("nvlb-vanilla", (Sprite)null, 8, 4); val.SetPos(0); val2.Load(val); Check(val2.GetAllItems().Count == 0, "vanilla Inventory.Load silently DELETES the out-of-bounds item (the bug we fix)"); Inventory val3 = new Inventory("nvlb-migrate", (Sprite)null, 8, 4); SlotsRescue.BeginCapture(val3); val.SetPos(0); val3.Load(val); SlotsRescue.EndCapture(); Check(SlotsRescue.CapturedCount == 1, "rescue captured " + SlotsRescue.CapturedCount + " item(s), expected 1"); SlotStore.SetHeight(val3, SlotLayout.TotalHeight); string text = SlotsRescue.Place(null, val3); Check(text != null, "rescue reported: " + text); Check(Has(val3, "ArrowWood"), "the rescued ArrowWood is in the inventory: " + NamesIn(val3)); SlotDef slotDef = SlotLayout.ByKey("ammo1"); if (slotDef != null) { ItemData itemAt = val3.GetItemAt(slotDef.Pos.x, slotDef.Pos.y); Check(itemAt != null && SlotBlob.PrefabNameOf(itemAt) == "ArrowWood" && itemAt.m_stack == 42, "it landed in the ammo1 slot with its stack of 42 intact"); } } private static void TestLoadoutRoundTrip() { LoadoutSpec loadoutSpec = new LoadoutSpec { RightPrefab = "SwordIron", RightQuality = 3, RightVariant = 0, LeftPrefab = "ShieldBronzeBuckler", LeftQuality = 2, LeftVariant = 1 }; string text = LoadoutsModule.Encode(loadoutSpec); LoadoutSpec loadoutSpec2 = LoadoutsModule.Decode(text); Check(loadoutSpec2 != null && loadoutSpec2.RightPrefab == loadoutSpec.RightPrefab && loadoutSpec2.RightQuality == loadoutSpec.RightQuality && loadoutSpec2.RightVariant == loadoutSpec.RightVariant && loadoutSpec2.LeftPrefab == loadoutSpec.LeftPrefab && loadoutSpec2.LeftQuality == loadoutSpec.LeftQuality && loadoutSpec2.LeftVariant == loadoutSpec.LeftVariant, "loadout round trip: '" + text + "' -> " + ((loadoutSpec2 == null) ? "null" : loadoutSpec2.ToString())); string text2 = LoadoutsModule.Encode(new LoadoutSpec { RightPrefab = "AtgeirBronze", RightQuality = 1, RightVariant = 0 }); LoadoutSpec loadoutSpec3 = LoadoutsModule.Decode(text2); Check(loadoutSpec3 != null && loadoutSpec3.RightPrefab == "AtgeirBronze" && string.IsNullOrEmpty(loadoutSpec3.LeftPrefab), "empty off-hand round trip: '" + text2 + "'"); Check(LoadoutsModule.Decode("garbage") == null, "a corrupt loadout string decodes to null, not an exception"); Check(LoadoutsModule.Decode(null) == null, "a missing loadout string decodes to null"); } public override string StatusDetail() { return "SelfTest=" + (_selfTest != null && _selfTest.Value) + (_ran ? (" (ran: " + _pass + " pass, " + _fail + " fail)") : ""); } } internal static class SlotStore { public const string BlobKey = "nvlb.slots"; public const int Backups = 3; private static readonly FieldRef> ItemsRef = AccessTools.FieldRefAccess>("m_inventory"); private static readonly FieldRef HeightRef = AccessTools.FieldRefAccess("m_height"); private static readonly MethodInfo ChangedMi = AccessTools.Method(typeof(Inventory), "Changed", (Type[])null, (Type[])null); public static Inventory Managed; private static readonly List _stashed = new List(); private static int _stashDepth; public static bool Ready => Managed != null; public static int StashedCount => _stashed.Count; public static string BackupKey(int n) { return "nvlb.slots.bak" + n; } public static List Items(Inventory inv) { return ItemsRef.Invoke(inv); } public static void Changed(Inventory inv) { if (ChangedMi != null) { ChangedMi.Invoke(inv, null); } } public static int GetHeight(Inventory inv) { return HeightRef.Invoke(inv); } public static void SetHeight(Inventory inv, int h) { HeightRef.Invoke(inv) = h; } public static bool PlaceRaw(Inventory inv, ItemData item, Vector2i pos) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (inv == null || item == null) { return false; } if (pos.x < 0 || pos.y < 0 || pos.x >= inv.GetWidth() || pos.y >= GetHeight(inv)) { return false; } if (inv.GetItemAt(pos.x, pos.y) != null) { return false; } item.m_gridPos = pos; Items(inv).Add(item); return true; } public static Vector2i FindFreeVanillaCell(Inventory inv) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < 4; i++) { for (int j = 0; j < 8; j++) { if (inv.GetItemAt(j, i) == null) { return new Vector2i(j, i); } } } return new Vector2i(-1, -1); } public static List ExtraItems(Inventory inv) { List list = new List(); if (inv == null) { return list; } List list2 = Items(inv); for (int i = 0; i < list2.Count; i++) { if (list2[i].m_gridPos.y >= 4) { list.Add(list2[i]); } } return list; } public static void Stash(Inventory inv) { if (inv == null || _stashDepth++ > 0) { return; } _stashed.Clear(); List list = Items(inv); for (int num = list.Count - 1; num >= 0; num--) { if (list[num].m_gridPos.y >= 4) { _stashed.Add(list[num]); list.RemoveAt(num); } } } public static void Unstash(Inventory inv) { if (inv == null || --_stashDepth > 0) { return; } _stashDepth = 0; if (_stashed.Count != 0) { List list = Items(inv); for (int num = _stashed.Count - 1; num >= 0; num--) { list.Add(_stashed[num]); } _stashed.Clear(); } } public static List Collect(Inventory inv) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (inv == null) { return list; } List list2 = ExtraItems(inv); for (int i = 0; i < list2.Count; i++) { SlotDef slotDef = SlotLayout.At(list2[i].m_gridPos); if (slotDef != null) { list.Add(new SlotEntry { SlotKey = slotDef.Key, Item = list2[i] }); } } return list; } public static void WriteBlob(Dictionary data, Inventory inv) { if (data == null) { return; } List list = Collect(inv); string text = SlotBlob.Encode(list); data.TryGetValue("nvlb.slots", out string value); if (!string.IsNullOrEmpty(value) && value != text) { for (int num = 3; num > 1; num--) { if (data.TryGetValue(BackupKey(num - 1), out string value2)) { data[BackupKey(num)] = value2; } } data[BackupKey(1)] = value; } if (list.Count == 0 && string.IsNullOrEmpty(value)) { data.Remove("nvlb.slots"); } else { data["nvlb.slots"] = text; } } public static List ReadBlob(Dictionary data, int backup) { if (data == null) { return null; } if (!data.TryGetValue((backup <= 0) ? "nvlb.slots" : BackupKey(backup), out string value)) { return null; } return SlotBlob.Decode(value); } public static int Inject(Inventory inv, List entries, List leftovers) { //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) int num = 0; if (inv == null || entries == null) { return 0; } for (int i = 0; i < entries.Count; i++) { SlotEntry slotEntry = entries[i]; SlotDef slotDef = SlotLayout.ByKey(slotEntry.SlotKey); if (slotDef == null) { SlotDef slotDef2 = SlotLayout.LegacyKey(slotEntry.SlotKey); if (slotDef2 != null && inv.GetItemAt(slotDef2.Pos.x, slotDef2.Pos.y) == null && PlaceRaw(inv, slotEntry.Item, slotDef2.Pos)) { num++; NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Slots] migrated " + SlotBlob.Describe(slotEntry.Item) + " from legacy slot '" + slotEntry.SlotKey + "' into '" + slotDef2.Key + "'")); continue; } } if (slotDef != null && PlaceRaw(inv, slotEntry.Item, slotDef.Pos)) { num++; continue; } SlotDef slotDef3 = SlotLayout.FindFreeFor(inv, slotEntry.Item); if (slotDef3 != null && PlaceRaw(inv, slotEntry.Item, slotDef3.Pos)) { num++; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] " + SlotBlob.Describe(slotEntry.Item) + " could not go back into slot '" + slotEntry.SlotKey + "' - moved to '" + slotDef3.Key + "'")); } else { leftovers?.Add(slotEntry.Item); } } if (num > 0) { Changed(inv); } return num; } public static void Evacuate(Player player, Inventory inv, List items, string why) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0110: 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_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) if (items == null || items.Count == 0) { return; } int num = 0; int num2 = 0; List list = ((inv != null) ? Items(inv) : null); for (int i = 0; i < items.Count; i++) { ItemData val = items[i]; if (val == null) { continue; } list?.Remove(val); Vector2i val2 = (Vector2i)((inv != null) ? FindFreeVanillaCell(inv) : new Vector2i(-1, -1)); if (val2.x >= 0 && PlaceRaw(inv, val, val2)) { num++; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] evacuate (" + why + "): " + SlotBlob.Describe(val) + " -> inventory cell " + val2.x + "," + val2.y)); continue; } if ((Object)(object)player != (Object)null && SlotBlob.PrefabNameOf(val) != null) { try { ItemDrop.DropItem(val, val.m_stack, ((Component)player).transform.position + ((Component)player).transform.forward * 0.6f + Vector3.up * 0.5f, Quaternion.identity); num2++; NoVikingLeftBehindPlugin.Log.LogWarning((object)("[Slots] evacuate (" + why + "): " + SlotBlob.Describe(val) + " -> DROPPED at the player's feet")); } catch (Exception ex) { NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] evacuate: could not drop " + SlotBlob.Describe(val) + ": " + ex.Message)); goto IL_01a5; } continue; } goto IL_01a5; IL_01a5: NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] evacuate (" + why + "): NOWHERE to put " + SlotBlob.Describe(val) + " - it stays in the save blob, use nvlb.slots.restore")); } if (inv != null) { Changed(inv); } if ((Object)(object)player != (Object)null && num + num2 > 0) { ((Character)player).Message((MessageType)2, "Extra slots: " + num + " item(s) moved to your bag, " + num2 + " dropped at your feet", 0, (Sprite)null); } } public static List Orphans(Inventory inv) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) List list = new List(); if (inv == null) { return list; } List list2 = ExtraItems(inv); for (int i = 0; i < list2.Count; i++) { if (SlotLayout.At(list2[i].m_gridPos) == null) { list.Add(list2[i]); } } return list; } } internal static class SlotsUi { private const string PanelName = "NVLB extra slots"; private const float TileSpace = 6f; private const float TileSize = 70f; private const float WindowGap = 100f; private static Func _enabled; private static Func _offset; private static Func _scale; private static Func _quickLabel; private static bool _installed; private static int _errors; private static RectTransform _panel; private static Image _panelImage; private static RectTransform _selectedFrame; private static RectTransform _invBkg; private static Image _invBkgImage; private static Sprite _ammoIcon; private static bool _ammoIconTried; private static Material _iconMaterial; private static Vector3 _iconScale = Vector3.zero; private static Color _normal = Color.clear; private static Color _highlighted = Color.clear; private static Color _normalUnfit = Color.clear; private static Color _highlightedUnfit = Color.clear; private static bool _vanillaPositions = true; public static bool Installed => _installed; public static void Install(Harmony harmony, Func enabled, Func offset, Func scale, Func quickLabel) { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown _enabled = enabled; _offset = offset; _scale = scale; _quickLabel = quickLabel; try { MethodInfo methodInfo = AccessTools.Method(typeof(InventoryGrid), "UpdateGui", new Type[2] { typeof(Player), typeof(ItemData) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(InventoryGui), "OnDestroy", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { throw new Exception("InventoryGrid.UpdateGui / InventoryGui.OnDestroy not found"); } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(SlotsUi), "DrawPanel", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(SlotsUi), "Forget", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _installed = true; NoVikingLeftBehindPlugin.Log.LogInfo((object)"[Slots] side-panel UI patches installed"); } catch (Exception ex) { _installed = false; NoVikingLeftBehindPlugin.Log.LogError((object)("[Slots] UI could not be installed - the extra slots still work and your items are safe, they are just not drawn: " + ex.Message)); } } public static void Invalidate() { if ((Object)(object)_panel != (Object)null) { try { Object.Destroy((Object)(object)((Component)_panel).gameObject); } catch { } _panel = null; _panelImage = null; } if ((Object)(object)_selectedFrame != (Object)null) { try { Object.Destroy((Object)(object)((Component)_selectedFrame).gameObject); } catch { } _selectedFrame = null; } } public static void Forget() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) _panel = null; _panelImage = null; _selectedFrame = null; _invBkg = null; _invBkgImage = null; _iconMaterial = null; _iconScale = Vector3.zero; _normal = (_highlighted = (_normalUnfit = (_highlightedUnfit = Color.clear))); _vanillaPositions = true; } private static bool On() { if (_installed && _enabled != null && _enabled()) { return SlotStore.Managed != null; } return false; } private static void DrawPanel(InventoryGrid __instance) { //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_020b: 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_021d: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) if (!_installed || (Object)(object)__instance == (Object)null) { return; } try { InventoryGui instance = InventoryGui.instance; if ((Object)(object)instance == (Object)null || __instance != instance.m_playerGrid) { return; } List elements = __instance.m_elements; if (elements == null || elements.Count == 0) { return; } if ((Object)(object)_iconMaterial == (Object)null && (Object)(object)elements[0].m_icon != (Object)null && (Object)(object)((Graphic)elements[0].m_icon).material != (Object)null) { _iconMaterial = ((Graphic)elements[0].m_icon).material; _iconScale = ((Component)elements[0].m_icon).transform.localScale; } if (!On() || __instance.GetInventory() != SlotStore.Managed) { RestoreVanilla(__instance); return; } float num = ((__instance.m_elementSpace > 1f) ? __instance.m_elementSpace : 70f); if ((Object)(object)__instance.m_gridRoot != (Object)null) { __instance.m_gridRoot.SetSizeWithCurrentAnchors((Axis)1, 4f * num); } float num2 = Mathf.Clamp((_scale != null) ? _scale() : 1f, 0.4f, 2.5f); float num3 = 70f * num2; Vector2 val = PanelOrigin(instance); Vector2 size = default(Vector2); ((Vector2)(ref size))..ctor(SlotLayout.PanelTilesWide * num3 + 6f * num2 * 0.5f, SlotLayout.PanelTilesHigh * num3 + 6f * num2 * 0.5f); EnsurePanel(instance, val, size); ItemData dragItem = instance.m_dragItem; for (int i = 32; i < elements.Count; i++) { Element val2 = elements[i]; if (val2 == null || (Object)(object)val2.m_go == (Object)null) { continue; } int x = i % 8; int y = i / 8; SlotDef slotDef = SlotLayout.At(x, y); if (slotDef == null) { val2.m_go.SetActive(false); continue; } val2.m_go.SetActive(true); Transform transform = val2.m_go.transform; RectTransform val3 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if ((Object)(object)val3 != (Object)null) { ((Transform)val3).localScale = Vector3.one * num2; val3.anchoredPosition = val + new Vector2(slotDef.PanelTile.x * num3 * 0.25f, (0f - slotDef.PanelTile.y) * num3 * 0.25f); } Label(val2, slotDef); Decorate(__instance, val2, slotDef); Tint(val2, dragItem != null && !SlotLayout.Accepts(slotDef, dragItem)); } _vanillaPositions = false; } catch (Exception e) { Complain("panel draw", e); } } private static Vector2 PanelOrigin(InventoryGui gui) { //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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) float num; if (!((Object)(object)gui.m_player != (Object)null)) { num = 0f; } else { Rect rect = gui.m_player.rect; num = ((Rect)(ref rect)).width; } Vector2 val = ((_offset != null) ? _offset() : Vector2.zero); return new Vector2(num + 100f + val.x, 0f - val.y); } private static void EnsurePanel(InventoryGui gui, Vector2 origin, Vector2 size) { //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_0378: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0388: Unknown result type (might be due to invalid IL or missing references) //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_035c: 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_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gui.m_player == (Object)null) { return; } if ((Object)(object)_invBkg == (Object)null) { Transform val = ((Transform)gui.m_player).Find("Bkg"); if ((Object)(object)val != (Object)null) { _invBkg = (RectTransform)(object)((val is RectTransform) ? val : null); } if ((Object)(object)_invBkg != (Object)null) { _invBkgImage = ((Component)_invBkg).GetComponent(); } } if ((Object)(object)_panel == (Object)null) { if ((Object)(object)_invBkg == (Object)null) { return; } Transform obj = ((Transform)gui.m_player).Find("Darken"); RectTransform val2 = (RectTransform)(object)((obj is RectTransform) ? obj : null); UIGroupHandler component = ((Component)gui.m_player).GetComponent(); Transform val3 = (((Object)(object)component != (Object)null && (Object)(object)component.m_enableWhenActiveAndGamepad != (Object)null) ? component.m_enableWhenActiveAndGamepad.transform : null); _panel = new GameObject("NVLB extra slots", new Type[1] { typeof(RectTransform) }).GetComponent(); ((Component)_panel).gameObject.layer = ((Component)_invBkg).gameObject.layer; ((Transform)_panel).SetParent((Transform)(object)gui.m_player, false); int num = (((Object)(object)val3 != (Object)null) ? val3.GetSiblingIndex() : (((Object)(object)val2 != (Object)null) ? ((Transform)val2).GetSiblingIndex() : 0)); ((Transform)_panel).SetSiblingIndex(num + 1); _panel.anchorMin = new Vector2(0f, 1f); _panel.anchorMax = new Vector2(0f, 1f); _panel.offsetMin = Vector2.zero; _panel.offsetMax = Vector2.zero; if ((Object)(object)val2 != (Object)null) { RectTransform obj2 = Object.Instantiate(val2, (Transform)(object)_panel); ((Object)obj2).name = "Darken"; obj2.sizeDelta = Vector2.one * 70f; } Transform obj3 = Object.Instantiate(((Component)_invBkg).transform, (Transform)(object)_panel); ((Object)obj3).name = "Bkg"; _panelImage = ((Component)obj3).GetComponent(); if ((Object)(object)val3 != (Object)null && val3.childCount > 0) { Transform child = val3.GetChild(0); RectTransform val4 = (RectTransform)(object)((child is RectTransform) ? child : null); if (val4 != null) { _selectedFrame = Object.Instantiate(val4, val3); ((Object)_selectedFrame).name = "selected (NVLB slots)"; _selectedFrame.anchorMin = _panel.anchorMin; _selectedFrame.anchorMax = _panel.anchorMax; _selectedFrame.offsetMin = Vector2.zero; _selectedFrame.offsetMax = Vector2.zero; } } NoVikingLeftBehindPlugin.Log.LogInfo((object)("[Slots] side panel built (" + SlotLayout.PanelTilesWide.ToString("0.##") + " x " + SlotLayout.PanelTilesHigh.ToString("0.##") + " tiles)")); } _panel.sizeDelta = size; _panel.anchoredPosition = origin + new Vector2(size.x * 0.5f, (0f - size.y) * 0.5f); if ((Object)(object)_panelImage != (Object)null && (Object)(object)_invBkgImage != (Object)null) { _panelImage.sprite = _invBkgImage.sprite; _panelImage.overrideSprite = _invBkgImage.overrideSprite; ((Graphic)_panelImage).color = ((Graphic)_invBkgImage).color; } if ((Object)(object)_selectedFrame != (Object)null) { _selectedFrame.sizeDelta = size + Vector2.one * 26f; _selectedFrame.anchoredPosition = _panel.anchoredPosition; } } private static void Label(Element el, SlotDef slot) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006e: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) Transform val = el.m_go.transform.Find("binding"); if ((Object)(object)val == (Object)null) { return; } TMP_Text component = ((Component)val).GetComponent(); RectTransform val2 = (RectTransform)(object)((val is RectTransform) ? val : null); if (!((Object)(object)component == (Object)null) && !((Object)(object)val2 == (Object)null)) { val2.anchorMin = Vector2.zero; val2.anchorMax = Vector2.one; val2.offsetMin = Vector2.zero; val2.offsetMax = Vector2.zero; val2.sizeDelta = Vector2.zero; val2.anchoredPosition = Vector2.zero; ((Transform)val2).localScale = Vector3.one; string text = ((slot.Kind != SlotKind.Quick) ? slot.PanelLabel : ((_quickLabel != null) ? _quickLabel(slot.Index - 1) : "")); if (text == null) { text = ""; } component.text = text; ((Behaviour)component).enabled = text.Length > 0; component.enableAutoSizing = true; component.fontSizeMin = 9f; component.fontSizeMax = 15f; component.overflowMode = (TextOverflowModes)0; component.horizontalAlignment = (HorizontalAlignmentOptions)2; component.verticalAlignment = (VerticalAlignmentOptions)256; component.margin = new Vector4(0f, 2f, 0f, 0f); ((Graphic)component).color = ((slot.Kind == SlotKind.Quick) ? new Color(1f, 0.86f, 0.45f, 0.95f) : new Color(0.88f, 0.84f, 0.72f, 0.85f)); } } private static void Decorate(InventoryGrid grid, Element el, SlotDef slot) { //IL_0096: 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_00b4: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) if (el.m_used) { if ((Object)(object)el.m_icon != (Object)null) { if ((Object)(object)((Graphic)el.m_icon).material == (Object)null && (Object)(object)_iconMaterial != (Object)null) { ((Graphic)el.m_icon).material = _iconMaterial; } if (_iconScale != Vector3.zero) { ((Component)el.m_icon).transform.localScale = _iconScale; } } return; } if (slot.Kind == SlotKind.Food && (Object)(object)el.m_food != (Object)null) { ((Behaviour)el.m_food).enabled = true; ((Graphic)el.m_food).color = Color.grey - new Color(0f, 0f, 0f, 0.5f); } else if (slot.Kind == SlotKind.Ammo && (Object)(object)el.m_icon != (Object)null) { Sprite val = AmmoIcon(); if ((Object)(object)val != (Object)null) { ((Behaviour)el.m_icon).enabled = true; ((Graphic)el.m_icon).material = null; el.m_icon.sprite = val; ((Component)el.m_icon).transform.localScale = Vector3.one * 0.8f; ((Graphic)el.m_icon).color = Color.grey - new Color(0f, 0f, 0f, 0.35f); } } if ((Object)(object)el.m_tooltip != (Object)null) { el.m_tooltip.Set(slot.Label + " slot", TooltipFor(slot), grid.m_tooltipAnchor, default(Vector2)); } } private static string TooltipFor(SlotDef slot) { return slot.Kind switch { SlotKind.Food => "Only food fits here. It is eaten automatically when the buff runs out.", SlotKind.Ammo => "Only arrows and bolts fit here.", SlotKind.Quick => "Anything fits here. Press its key to use it.", SlotKind.Generic => "Anything fits here. Plain storage - no hotkey.", SlotKind.Utility => "Only utility items fit here - a belt, the Wishbone.", _ => "Only " + slot.Label.ToLowerInvariant() + " armour fits here. It is worn while it sits in the slot.", }; } private static Sprite AmmoIcon() { if (_ammoIconTried) { return _ammoIcon; } _ammoIconTried = true; try { if ((Object)(object)ObjectDB.instance == (Object)null) { _ammoIconTried = false; return null; } GameObject itemPrefab = ObjectDB.instance.GetItemPrefab("ArrowWood"); if ((Object)(object)itemPrefab == (Object)null) { return null; } ItemDrop component = itemPrefab.GetComponent(); if ((Object)(object)component == (Object)null || component.m_itemData == null) { return null; } _ammoIcon = component.m_itemData.GetIcon(); } catch { _ammoIcon = null; } return _ammoIcon; } private static void Tint(Element el, bool unfit) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: 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_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: 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_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) Button component = el.m_go.GetComponent