Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ServerRates v1.7.0
plugins/ServerRates/ServerRates.dll
Decompiled 3 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; 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 Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ServerRates")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.7.0.0")] [assembly: AssemblyInformationalVersion("1.7.0+c69a267d1e72e69b997e6c0141bc3b68a2ce29b3")] [assembly: AssemblyProduct("ServerRates")] [assembly: AssemblyTitle("ServerRates")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.7.0.0")] [module: UnverifiableCode] namespace ServerRates; internal static class CategoryGroundAmp { private sealed class ItemInfo { public int Hash; public GameObject Prefab; public ItemDrop Template; public DropClassifier.Kind Kind; public int MaxStack; } private sealed class Pending { public ItemInfo Info; public Vector3 Pos; public float Since; public int Still; } private sealed class OwnSpawn { public int Hash; public Vector3 Pos; public float Until; public int Left; public long SentTicks; } private const float ScanInterval = 0.25f; private const float PruneInterval = 30f; private const float MaxWait = 8f; private const int StillScansNeeded = 2; private const float MoveEpsilonSqr = 0.0004f; private static readonly int CatFlag = StringExtensionMethods.GetStableHashCode("SR.Cat"); private static readonly Dictionary<int, ItemInfo> Items = new Dictionary<int, ItemInfo>(); private static readonly HashSet<int> Ignored = new HashSet<int>(); private static readonly Dictionary<ZDOID, Pending> Waiting = new Dictionary<ZDOID, Pending>(); private static readonly HashSet<ZDOID> Done = new HashSet<ZDOID>(); private static readonly List<ZDO> Near = new List<ZDO>(512); private static readonly List<ZDO> Distant = new List<ZDO>(512); private static readonly List<ZDOID> Scratch = new List<ZDOID>(); private static float _nextScan; private static float _nextPrune; private static int _amplified; private static readonly List<OwnSpawn> OwnSpawns = new List<OwnSpawn>(); private const long SpawnTimeSlackTicks = 1000000L; public static void LogStatus() { if (Plugin.Log != null && Rates.GroundAmp != null) { Plugin.Log.LogInfo((object)("ServerRates loot: GroundAmp=" + Rates.GroundAmp.Display() + " MaxPiles=" + Rates.MaxPiles.Display() + " (rates changed: " + CountNonDefaultLoot() + " of " + Rates.InGroup("loot").Count + " categories)")); } } private static int CountNonDefaultLoot() { int num = 0; foreach (Rate item in Rates.InGroup("loot")) { if (!item.IsDefault()) { num++; } } return num; } public static void Tick() { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) if (!Rates.Ready || ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return; } float time = Time.time; if (time < _nextScan) { return; } _nextScan = time + 0.25f; List<ZNetPeer> peers = ZNet.instance.GetPeers(); if (peers != null && peers.Count > 0) { Near.Clear(); Distant.Clear(); SimulationDistance syncedSimulationDistance = ZNet.instance.GetSyncedSimulationDistance(); for (int i = 0; i < peers.Count; i++) { ZNetPeer val = peers[i]; if (val != null && val.IsReady()) { ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(val.GetRefPos()), syncedSimulationDistance, Near, Distant); } } bool flag = Rates.GroundAmp.Flag; for (int j = 0; j < Near.Count; j++) { StationBoost.Observe(Near[j], time); StationBoost.Info(Near[j], time); if (flag) { Observe(Near[j], time); } } for (int k = 0; k < Distant.Count; k++) { StationBoost.Observe(Distant[k], time); StationBoost.Info(Distant[k], time); if (flag) { Observe(Distant[k], time); } } } ProcessWaiting(time); if (time >= _nextPrune) { _nextPrune = time + 30f; Prune(); StationBoost.Prune(); } } private static ItemInfo GetInfo(int hash) { if (Items.TryGetValue(hash, out var value)) { return value; } if (Ignored.Contains(hash)) { return null; } GameObject prefab = ZNetScene.instance.GetPrefab(hash); ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null || val.m_itemData == null || (Object)(object)prefab.GetComponent<Fish>() != (Object)null) { Ignored.Add(hash); return null; } value = new ItemInfo { Hash = hash, Prefab = prefab, Template = val, Kind = DropClassifier.Classify(prefab), MaxStack = ((val.m_itemData.m_shared == null) ? 1 : Mathf.Max(1, val.m_itemData.m_shared.m_maxStackSize)) }; Items[hash] = value; return value; } private static void Observe(ZDO zdo, float now) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_0048: 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) //IL_0076: 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_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: 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_0108: Unknown result type (might be due to invalid IL or missing references) if (zdo == null) { return; } int prefab = zdo.GetPrefab(); if (Ignored.Contains(prefab)) { return; } ZDOID uid = zdo.m_uid; if (Done.Contains(uid) || Waiting.ContainsKey(uid)) { return; } ItemInfo info = GetInfo(prefab); if (info == null) { return; } if (IsOwnSpawn(prefab, zdo.GetPosition(), zdo, now)) { Done.Add(uid); return; } if (zdo.GetInt(CatFlag, 0) != 0) { Done.Add(uid); return; } ItemData val = info.Template.m_itemData.Clone(); ItemDrop.LoadFromZDO(val, zdo, 0); if (val.m_pickedUp || val.m_crafterID != 0L) { Done.Add(uid); return; } if (Rates.DropMultiplier(info.Kind) <= 1.001f) { Done.Add(uid); return; } Pending pending = new Pending { Info = info, Pos = zdo.GetPosition(), Since = now }; if (ViaClient()) { Amplify(zdo, pending); Done.Add(uid); } else { Waiting[uid] = pending; } } private static bool ViaClient() { return string.Equals(Rates.LootSpawn.Text, "Client", StringComparison.OrdinalIgnoreCase); } private static void ProcessWaiting(float now) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: 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_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_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) if (Waiting.Count == 0) { return; } Scratch.Clear(); foreach (KeyValuePair<ZDOID, Pending> item in Waiting) { ZDO zDO = ZDOMan.instance.GetZDO(item.Key); if (zDO == null) { Scratch.Add(item.Key); continue; } Pending value = item.Value; Vector3 position = zDO.GetPosition(); Vector3 val = position - value.Pos; bool flag = ((Vector3)(ref val)).sqrMagnitude > 0.0004f; value.Pos = position; value.Still = ((!flag) ? (value.Still + 1) : 0); if (value.Still >= 2 || now - value.Since > 8f) { Amplify(zDO, value); Scratch.Add(item.Key); } } for (int i = 0; i < Scratch.Count; i++) { Waiting.Remove(Scratch[i]); Done.Add(Scratch[i]); } } private static void Amplify(ZDO zdo, Pending p) { //IL_0080: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) ItemInfo info = p.Info; float num = Mathf.Max(0f, Rates.DropMultiplier(info.Kind)); ItemData val = info.Template.m_itemData.Clone(); ItemDrop.LoadFromZDO(val, zdo, 0); int num2 = Mathf.Max(1, val.m_stack); int num3 = Mathf.RoundToInt((float)num2 * num) - num2; try { zdo.Set(CatFlag, 1, false); } catch { } if (num3 <= 0) { return; } int num4 = Mathf.Max(1, Mathf.RoundToInt(Rates.MaxPiles.Value)); Quaternion rotation = zdo.GetRotation(); bool flag = ViaClient(); int num5 = 0; int num6 = 0; if (flag) { int count = Mathf.Min(num3, num4); num5 = SpawnViaClient(info, p.Pos, rotation, count); num6 = num5; } else { while (num3 > 0 && num5 < num4) { int num7 = Mathf.Min(num3, info.MaxStack); num3 -= num7; num6 += num7; num5++; SpawnPile(info, p.Pos, rotation, val, num7); } } if (_amplified++ < 10) { Plugin.Log.LogInfo((object)("ServerRates loot: " + ((Object)info.Prefab).name + " (" + info.Kind.ToString() + ") x" + Rate.Fmt(num) + ", " + num2 + " -> " + (num2 + num6) + " via " + (flag ? "player client" : "server object") + ", " + num5 + " spawn(s)")); } } private static bool IsOwnSpawn(int hash, Vector3 pos, ZDO zdo, float now) { //IL_0046: 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_0052: 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) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0066: 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) for (int num = OwnSpawns.Count - 1; num >= 0; num--) { OwnSpawn ownSpawn = OwnSpawns[num]; if (now > ownSpawn.Until || ownSpawn.Left <= 0) { OwnSpawns.RemoveAt(num); } else if (ownSpawn.Hash == hash) { Vector3 val = pos - ownSpawn.Pos; if (!(val.x * val.x + val.z * val.z > 1f) && !(Mathf.Abs(val.y) > 3f)) { long num2 = zdo.GetLong(ZDOVars.s_spawnTime, 0L); if (num2 == 0L || num2 >= ownSpawn.SentTicks - 1000000) { ownSpawn.Left--; return true; } } } } return false; } private static ZNetPeer NearestPeer(Vector3 at) { //IL_002f: 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_0035: 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) ZNetPeer result = null; float num = float.MaxValue; List<ZNetPeer> peers = ZNet.instance.GetPeers(); int num2 = 0; while (peers != null && num2 < peers.Count) { ZNetPeer val = peers[num2]; if (val != null && val.IsReady()) { Vector3 val2 = val.GetRefPos() - at; float sqrMagnitude = ((Vector3)(ref val2)).sqrMagnitude; if (sqrMagnitude < num) { num = sqrMagnitude; result = val; } } num2++; } return result; } private static int SpawnViaClient(ItemInfo info, Vector3 at, Quaternion rot, int count) { //IL_0000: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: 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_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) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0069: 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_00a3: Unknown result type (might be due to invalid IL or missing references) ZNetPeer val = NearestPeer(at); if (val == null || ZRoutedRpc.instance == null || count <= 0) { return 0; } for (int i = 0; i < count; i++) { Vector2 val2 = Random.insideUnitCircle * 0.5f; Vector3 val3 = at + new Vector3(val2.x, 0.5f, val2.y); ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "SpawnObject", new object[3] { val3, rot, info.Hash }); } OwnSpawns.Add(new OwnSpawn { Hash = info.Hash, Pos = at, Until = Time.time + 6f, Left = count, SentTicks = ZNet.instance.GetTime().Ticks }); return count; } private static void SpawnPile(ItemInfo info, Vector3 at, Quaternion rot, ItemData template, int stack) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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_0010: 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_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: 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_00a7: 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) Vector2 val = Random.insideUnitCircle * 0.4f; Vector3 val2 = at + new Vector3(val.x, 0.2f, val.y); ZDO val3 = ZDOMan.instance.CreateNewZDO(val2, info.Hash); val3.SetRotation(rot); ZNetView component = info.Prefab.GetComponent<ZNetView>(); if ((Object)(object)component != (Object)null) { val3.Persistent = component.m_persistent; val3.Distant = component.m_distant; val3.SetType(component.m_type); } val3.Set(CatFlag, 1, false); ItemData obj = template.Clone(); obj.m_stack = stack; ItemDrop.SaveToZDO(obj, val3, 0); Done.Add(val3.m_uid); } private static void Prune() { //IL_002b: 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_0036: 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_0071: Unknown result type (might be due to invalid IL or missing references) if (Done.Count < 500) { return; } Scratch.Clear(); foreach (ZDOID item in Done) { if (ZDOMan.instance.GetZDO(item) == null) { Scratch.Add(item); } } for (int i = 0; i < Scratch.Count; i++) { Done.Remove(Scratch[i]); } } } internal static class ChatBridge { [HarmonyPatch(typeof(ZNet), "WritePlayerInfo")] private static class WritePlayerInfoPatch { private static void Postfix(ZNet __instance, ref ZPackage __result) { if (__result == null || !Plugin.Active || !Rates.Ready || !Rates.ChatBridge.Flag || !__instance.IsServer()) { return; } try { string text = ServerUserId(__instance); if (text != null) { int realPlayers = Traverse.Create((object)__instance).Field("m_players").GetValue<ICollection>()?.Count ?? 0; AppendServerPlayer(__result, realPlayers, ZDOMan.GetSessionID(), text, "ServerRates"); } } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates: chat bridge failed: " + ex.Message)); } } } } internal const string DisplayName = "ServerRates"; private static string _userId; private static PlatformUserID? _serverUser; private static bool _failed; internal static PlatformUserID? ServerUser() { if ((Object)(object)ZNet.instance != (Object)null) { ServerUserId(ZNet.instance); } return _serverUser; } private unsafe static string ServerUserId(ZNet net) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (_userId != null) { return _userId; } if (_failed) { return null; } try { Type type = Type.GetType("Steamworks.SteamGameServer, com.rlabrecque.steamworks.net", throwOnError: false); MethodInfo methodInfo = ((type != null) ? type.GetMethod("GetSteamID") : null); string text = ((methodInfo != null) ? methodInfo.Invoke(null, null) : null)?.ToString(); if (string.IsNullOrEmpty(text) || text == "0") { throw new InvalidOperationException("server Steam id not available"); } PlatformUserID value = default(PlatformUserID); ((PlatformUserID)(ref value))..ctor(new Platform("Steam"), text); string text2 = ((object)(*(PlatformUserID*)(&value))/*cast due to .constrained prefix*/).ToString(); if (!((PlatformUserID)(ref value)).IsValid || string.IsNullOrEmpty(text2)) { throw new InvalidOperationException("server user id '" + text + "' is not valid"); } _userId = text2; _serverUser = value; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("ServerRates: chat bridge lists the server as 'ServerRates' (" + _userId + ").")); } return _userId; } catch (Exception ex) { _failed = true; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("ServerRates: chat bridge disabled, cannot build the server user id: " + ex.Message)); } return null; } } internal static bool AppendServerPlayer(ZPackage pkg, int realPlayers, long sessionId, string userId, string name) { //IL_000d: 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) int pos = pkg.GetPos(); int num = new ZPackage(pkg.GetArray()).ReadInt(); if (num > realPlayers) { return false; } pkg.SetPos(0); pkg.Write(num + 1); pkg.SetPos(pos); pkg.Write(name); pkg.Write(new ZDOID(sessionId, uint.MaxValue)); pkg.Write(userId); pkg.Write(name); pkg.Write(name); pkg.Write(""); pkg.Write(false); return true; } } internal static class ConfigWatch { private const float PollSeconds = 1f; private const float SettleSeconds = 0.8f; private const int MaxRetries = 5; private static string _path; private static DateTime _lastWrite; private static float _nextPoll; private static float _reloadAt = -1f; private static int _retries; public static void Start() { _path = (((Object)(object)Plugin.Instance != (Object)null) ? ((BaseUnityPlugin)Plugin.Instance).Config.ConfigFilePath : null); _lastWrite = Stamp(); if (_path != null) { Plugin.Log.LogInfo((object)("ServerRates watching config: " + _path)); } } public static void NotifySaved() { _lastWrite = Stamp(); _reloadAt = -1f; } public static void Tick() { if (_path == null) { return; } float unscaledTime = Time.unscaledTime; if (_reloadAt < 0f) { if (!(unscaledTime < _nextPoll)) { _nextPoll = unscaledTime + 1f; DateTime dateTime = Stamp(); if (!(dateTime == _lastWrite)) { _lastWrite = dateTime; _reloadAt = unscaledTime + 0.8f; _retries = 0; } } } else if (!(unscaledTime < _reloadAt)) { DateTime dateTime2 = Stamp(); if (dateTime2 != _lastWrite) { _lastWrite = dateTime2; _reloadAt = unscaledTime + 0.8f; } else { _reloadAt = -1f; Reload(); } } } private static DateTime Stamp() { try { return (_path != null && File.Exists(_path)) ? File.GetLastWriteTimeUtc(_path) : DateTime.MinValue; } catch { return DateTime.MinValue; } } private static void Reload() { try { ((BaseUnityPlugin)Plugin.Instance).Config.Reload(); } catch (Exception ex) { if (++_retries <= 5) { _reloadAt = Time.unscaledTime + 1f; } else { Plugin.Log.LogWarning((object)("ServerRates: config reload failed: " + ex.Message)); } return; } List<Rate> list = Rates.TakeChanges(); RateApplier.ApplyGlobalKeys(); CategoryGroundAmp.LogStatus(); if (list.Count == 0) { Plugin.Log.LogInfo((object)"ServerRates: config reloaded, nothing changed."); return; } Plugin.Log.LogInfo((object)("ServerRates: config reloaded, " + list.Count + " rate(s) changed.")); if (Plugin.Active) { RatesFeedback.AnnounceChanges(list, "config"); } } } internal static class DropClassifier { public enum Kind { Other, Wood, FineWood, CoreWood, SpecialWood, Ore, Scrap, Stone, Flint, Crystal, Fuel, Gems, BossMats, Crops, Seeds, Mushrooms, BerriesHoney, Fish, Potions, Hide, Trophy, Meat, Parts, Feathers, SpecialParts } private static readonly Dictionary<string, Kind> Exact; static DropClassifier() { Exact = new Dictionary<string, Kind>(); Add(Kind.FineWood, "finewood"); Add(Kind.CoreWood, "roundlog"); Add(Kind.SpecialWood, "elderbark", "yggdrasilwood", "ashwood", "blackwood"); Add(Kind.Wood, "wood"); Add(Kind.Ore, "copperore", "tinore", "ironore", "silverore", "flametalore", "flametalorenew"); Add(Kind.Scrap, "ironscrap", "blackmetalscrap", "copperscrap"); Add(Kind.Flint, "flint"); Add(Kind.Crystal, "crystal", "obsidian"); Add(Kind.Stone, "stone", "grausten", "sharpeningstone", "thunderstone"); Add(Kind.Fuel, "coal", "resin", "tar"); Add(Kind.Gems, "coins", "amber", "amberpearl", "ruby", "silvernecklace"); Add(Kind.BossMats, "surtlingcore", "dragontear", "ancientseed", "ymirremains", "queenbee", "yagluthdrop", "vegvisirshardbonemass", "blackcore", "refinedeitr", "mechanicalspring", "dvergrkeyfragment", "trophybonemass", "trophyeikthyr", "trophytheelder", "trophydragonqueen", "trophygoblinking", "trophyseekerqueen", "trophyfader"); Add(Kind.Seeds, "carrotseeds", "onionseeds", "turnipseeds", "beechseeds", "birchseeds", "fircone", "pinecone", "acorn", "oakseeds"); Add(Kind.Mushrooms, "mushroom", "mushroomyellow", "mushroomblue", "magecap"); Add(Kind.BerriesHoney, "raspberry", "blueberry", "cloudberry", "honey", "bukeperries"); Add(Kind.Meat, "rawmeat", "necktail", "boarmeat", "deer", "deermeat", "wolfmeat", "loxmeat", "serpentmeat", "chickenmeat", "haremeat", "asksvinmeat", "bugmeat", "meatrotten", "necktailgrilled", "cookedmeat", "cookedbackmeat", "serpentmeatcooked", "cookeddeer", "cookedwolf", "loxpie", "fishcooked"); Add(Kind.Fish, "fishraw", "fish", "perch", "pike", "tuna", "trollfish", "pufferfish", "anglerfish", "coralcod", "northernsalmon", "magmapander", "voltureegg"); Add(Kind.Potions, "meadbasehealth", "meadbasehealthminor", "meadbasestamina", "meadbasetasty", "meadbasepoisonresist", "meadbasefrostresist", "meadbaseeitr", "meadbaselight", "potionhealthminor", "potionhealthmedium", "potionstaminaminor", "potionstaminamedium", "potioneitrminor", "poisonbomb"); Add(Kind.Crops, "barley", "barleyflour", "flax", "carrot", "onion", "turnip", "thistle", "dandelion", "sap", "royaljelly", "jotunpuffs", "smokepuffs"); Add(Kind.Hide, "deerhide", "leatherscraps", "trollhide", "wolfpelt", "loxpelt", "serpentscale", "chitin", "scalehide", "asksvinhide", "harepelt", "wolfhairbundle"); Add(Kind.Feathers, "feathers"); Add(Kind.SpecialParts, "guck", "ooze", "softtissue", "bilebag", "seekerchitin", "carapace", "moltencore", "charredskull", "flametal", "proustitepowder"); Add(Kind.Parts, "bonefragments", "witheredbone", "entrails", "bloodbag", "needle", "greydwarfeye", "freezegland", "hardantler", "wolffang", "root", "mandible", "chain"); } private static void Add(Kind kind, params string[] names) { foreach (string key in names) { if (!Exact.ContainsKey(key)) { Exact[key] = kind; } } } public static Kind Classify(GameObject prefab) { if ((Object)(object)prefab == (Object)null) { return Kind.Other; } ItemDrop component = prefab.GetComponent<ItemDrop>(); if ((Object)(object)component != (Object)null && component.m_itemData != null && component.m_itemData.m_shared != null) { Kind kind = ClassifyKey(Normalize(component.m_itemData.m_shared.m_name)); if (kind != Kind.Other) { return kind; } } return ClassifyKey(Normalize(((Object)prefab).name)); } private static Kind ClassifyKey(string key) { if (string.IsNullOrEmpty(key)) { return Kind.Other; } if (Exact.TryGetValue(key, out var value)) { return value; } if (key.EndsWith("wood") && !key.Contains("yggdrasil")) { return Kind.Wood; } if (key.EndsWith("ore") && !key.EndsWith("core")) { return Kind.Ore; } if (key.EndsWith("scrap")) { return Kind.Scrap; } if (key.EndsWith("seeds") || key.EndsWith("cone")) { return Kind.Seeds; } if (key.Contains("mushroom")) { return Kind.Mushrooms; } if (key.Contains("berry")) { return Kind.BerriesHoney; } if (key.StartsWith("fish") || key.Contains("salmon") || key.Contains("puffer")) { return Kind.Fish; } if (key.Contains("mead") || key.Contains("potion")) { return Kind.Potions; } if (key.Contains("trophy")) { return Kind.Trophy; } if (key.Contains("hide") || key.Contains("pelt") || key.Contains("leather") || key.Contains("scale")) { return Kind.Hide; } if (key.Contains("meat") || key.Contains("necktail")) { return Kind.Meat; } if (key.Contains("feather")) { return Kind.Feathers; } if (key.Contains("bone") || key.Contains("entrails") || key.Contains("gland") || key.Contains("fang") || key.Contains("eye")) { return Kind.Parts; } return Kind.Other; } private static string Normalize(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } string text = raw.Trim().ToLowerInvariant(); int num = text.IndexOf("(clone)"); if (num >= 0) { text = text.Substring(0, num).Trim(); } if (text.StartsWith("$item_")) { text = text.Substring(6); } return text.Replace(" ", "").Replace("-", "").Replace("_", ""); } } internal enum RateKind { Percent, Multiplier, Number, Toggle, Switch, Choice } internal sealed class Rate { public string Short; public string Key; public string Section; public string Group; public string Label; public string Description; public RateKind Kind; public GlobalKeys? GlobalKey; public DropClassifier.Kind? Drop; public float Min; public float Max = 10000f; public bool Integer; public float DefaultValue; public bool DefaultFlag; public string DefaultText = "Unchanged"; public string[] Options; private ConfigEntry<float> _num; private ConfigEntry<string> _text; private ConfigEntry<bool> _flag; public bool IsNumeric { get { if (Kind != RateKind.Percent && Kind != RateKind.Multiplier) { return Kind == RateKind.Number; } return true; } } public float Value { get { if (_num == null) { return 0f; } return _num.Value; } } public bool Flag { get { if (_flag != null) { return _flag.Value; } return false; } } public string Text { get { if (_text == null) { return ""; } return _text.Value; } } internal void Bind(ConfigFile file) { //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown switch (Kind) { case RateKind.Percent: case RateKind.Multiplier: case RateKind.Number: { string text = ((Kind == RateKind.Percent) ? " 100 = vanilla." : ((Kind == RateKind.Multiplier) ? " 1 = vanilla." : "")); _num = file.Bind<float>(Section, Key, DefaultValue, new ConfigDescription(Description + text, (AcceptableValueBase)(object)new AcceptableValueRange<float>(Min, Max), Array.Empty<object>())); break; } case RateKind.Switch: _flag = file.Bind<bool>(Section, Key, DefaultFlag, Description); break; case RateKind.Toggle: Options = new string[3] { "Unchanged", "On", "Off" }; _text = file.Bind<string>(Section, Key, "Unchanged", new ConfigDescription(Description + " Values: Unchanged | On | Off", (AcceptableValueBase)(object)new AcceptableValueList<string>(Options), Array.Empty<object>())); break; default: _text = file.Bind<string>(Section, Key, DefaultText, new ConfigDescription(Description + " Values: " + string.Join(" | ", Options), (AcceptableValueBase)(object)new AcceptableValueList<string>(Options), Array.Empty<object>())); break; } } public static string Fmt(float v) { return v.ToString("0.###", CultureInfo.InvariantCulture); } public string Display() { switch (Kind) { case RateKind.Percent: return Fmt(Value) + "%"; case RateKind.Multiplier: return "x" + Fmt(Value); case RateKind.Number: return Fmt(Value); case RateKind.Switch: if (!Flag) { return "Off"; } return "On"; default: return Text; } } public bool IsDefault() { switch (Kind) { case RateKind.Switch: return Flag == DefaultFlag; case RateKind.Toggle: case RateKind.Choice: return string.Equals(Text, DefaultText, StringComparison.OrdinalIgnoreCase); default: return Math.Abs(Value - DefaultValue) < 0.0001f; } } public void Reset() { switch (Kind) { case RateKind.Switch: _flag.Value = DefaultFlag; break; case RateKind.Toggle: case RateKind.Choice: _text.Value = DefaultText; break; default: _num.Value = DefaultValue; break; } } public string RangeHint() { return Kind switch { RateKind.Switch => "on/off", RateKind.Toggle => "on/off/unchanged", RateKind.Choice => string.Join("/", Options).ToLowerInvariant(), _ => Fmt(Min) + " - " + Fmt(Max), }; } public bool TrySet(string raw, out string error) { error = null; string text = (raw ?? "").Trim().ToLowerInvariant(); if (IsNumeric) { text = text.TrimStart('x', '*').TrimEnd('x', '%').Replace(',', '.'); if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { error = Short + ": number expected (" + RangeHint() + ")"; return false; } if (Integer) { result = (float)Math.Round(result); } if (result < Min || result > Max) { error = Short + ": allowed " + RangeHint(); return false; } _num.Value = result; return true; } int num; switch (text) { default: num = ((text == "1") ? 1 : 0); break; case "on": case "true": case "yes": num = 1; break; } bool flag = (byte)num != 0; int num2; switch (text) { default: num2 = ((text == "0") ? 1 : 0); break; case "off": case "false": case "no": num2 = 1; break; } bool flag2 = (byte)num2 != 0; if (Kind == RateKind.Switch) { if (!flag && !flag2) { error = Short + ": use on or off"; return false; } _flag.Value = flag; return true; } if (Kind == RateKind.Toggle) { if (flag) { _text.Value = "On"; return true; } if (flag2) { _text.Value = "Off"; return true; } switch (text) { case "unchanged": case "default": case "-": _text.Value = "Unchanged"; return true; default: error = Short + ": use on, off or unchanged"; return false; } } string[] options = Options; foreach (string text2 in options) { if (string.Equals(text2, text, StringComparison.OrdinalIgnoreCase)) { _text.Value = text2; return true; } } error = Short + ": use " + RangeHint(); return false; } } internal static class Rates { public static readonly List<Rate> All = new List<Rate>(); private static readonly Dictionary<string, Rate> ByShort = new Dictionary<string, Rate>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, Rate> ByKey = new Dictionary<string, Rate>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<DropClassifier.Kind, Rate> ByDrop = new Dictionary<DropClassifier.Kind, Rate>(); public static readonly Dictionary<string, string[]> Groups = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase) { { "general", new string[1] { "general" } }, { "skills", new string[1] { "skills" } }, { "combat", new string[1] { "combat" } }, { "survival", new string[1] { "survival" } }, { "resources", new string[1] { "resources" } }, { "loot", new string[3] { "materials", "consumables", "mobs" } }, { "materials", new string[1] { "materials" } }, { "consumables", new string[1] { "consumables" } }, { "mobs", new string[1] { "mobs" } }, { "death", new string[1] { "death" } }, { "build", new string[1] { "build" } }, { "map", new string[1] { "map" } }, { "world", new string[1] { "world" } }, { "stations", new string[1] { "stations" } } }; public static Rate Enabled; public static Rate ChatEnabled; public static Rate ChatAccess; public static Rate ChatBridge; public static Rate StationInfo; public static Rate GroundAmp; public static Rate MaxPiles; public static Rate LootSpawn; public static Rate Smelter; public static Rate Fermenter; public static Rate Cooking; public static Rate Plants; private static ConfigFile _file; private static Dictionary<string, string> _applied = new Dictionary<string, string>(); public static bool Ready { get { if (Plugin.Active && Enabled != null) { return Enabled.Flag; } return false; } } public static Rate Find(string name) { if (string.IsNullOrEmpty(name)) { return null; } if (ByShort.TryGetValue(name, out var value)) { return value; } if (!ByKey.TryGetValue(name, out value)) { return null; } return value; } public static float DropMultiplier(DropClassifier.Kind kind) { if (!ByDrop.TryGetValue(kind, out var value)) { return 1f; } return value.Value; } public static List<Rate> InGroup(string group) { List<Rate> list = new List<Rate>(); if (!Groups.TryGetValue(group, out var value)) { return list; } foreach (Rate item in All) { if (Array.IndexOf(value, item.Group) >= 0) { list.Add(item); } } return list; } public static void Save() { if (_file != null) { _file.Save(); } } public static Dictionary<string, string> Snapshot() { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (Rate item in All) { dictionary[item.Short] = item.Display(); } return dictionary; } public static List<Rate> TakeChanges() { Dictionary<string, string> dictionary = Snapshot(); List<Rate> list = new List<Rate>(); foreach (Rate item in All) { if (!_applied.TryGetValue(item.Short, out var value) || value != dictionary[item.Short]) { list.Add(item); } } _applied = dictionary; return list; } public static void Init(ConfigFile file) { _file = file; file.SaveOnConfigSet = false; Enabled = Sw("enabled", "Enabled", "1 - General", "general", "ServerRates", def: true, "Master switch. Dedicated server only; vanilla clients need no install."); ChatEnabled = Sw("chatcmds", "ChatCommandsEnabled", "1 - General", "general", "Chat commands", def: true, "Allow rate commands in chat (sr_wood 9 / rates status). Console players type WITHOUT a slash."); ChatAccess = Ch("chataccess", "ChatAccess", "1 - General", "general", "Chat access", "Everyone", new string[2] { "Everyone", "AdminOnly" }, "Who may CHANGE rates in chat. Everyone = no adminlist needed (console players). AdminOnly = sender must be in the server adminlist. Reading values is always allowed."); ChatBridge = Sw("chatbridge", "ChatBridge", "1 - General", "general", "Chat bridge", def: true, "Lists the server as a pseudo player so that clients send their chat to it. Needed for chat commands: without it the server never sees chat while only one player is online."); StationInfo = Sw("stationinfo", "StationInfo", "1 - General", "general", "Station info", def: true, "Shows a short message (top left) with the time per item when you stand next to a smelter, kiln, cooking station or fermenter. Turn off with sr_stationinfo off."); Pct("skillgain", "SkillGainPercent", "2 - Skills", "skills", "Skill Gain", (GlobalKeys)12, "XP for ALL skills. 200 = x2, 1000 = x10."); Pct("skillloss", "SkillReductionPercent", "2 - Skills", "skills", "Skill Loss", (GlobalKeys)13, "Skill loss on death. 0 = none."); Pct("playerdmg", "PlayerDamagePercent", "3 - Combat", "combat", "Player Damage", (GlobalKeys)0, "Damage players deal."); Pct("enemydmg", "EnemyDamagePercent", "3 - Combat", "combat", "Enemy Damage", (GlobalKeys)1, "Damage enemies deal."); Pct("enemyspeed", "EnemySpeedSizePercent", "3 - Combat", "combat", "Enemy Speed", (GlobalKeys)14, "Enemy move speed / size."); Pct("enemystars", "EnemyLevelUpPercent", "3 - Combat", "combat", "Enemy Stars", (GlobalKeys)15, "How fast enemies star-up."); Pct("events", "EventRatePercent", "3 - Combat", "combat", "Raid Rate", (GlobalKeys)3, "Raid / event frequency. 0 = none."); Add(new Rate { Short = "worldlevel", Key = "WorldLevel", Section = "3 - Combat", Group = "combat", Label = "World Level", Kind = RateKind.Number, Min = 0f, Max = 10f, Integer = true, DefaultValue = 0f, GlobalKey = (GlobalKeys)2, Description = "World level 0-10. 0 = vanilla start." }); Tg("passivemobs", "PassiveMobs", "3 - Combat", "combat", "Passive Mobs", (GlobalKeys)30, "On = mobs do not attack."); Tg("playerevents", "PlayerEvents", "3 - Combat", "combat", "Player Events", (GlobalKeys)17, "Player-based raid events."); Pct("stamina", "StaminaPercent", "4 - Survival", "survival", "Stamina", (GlobalKeys)5, "Stamina drain. Lower = less drain."); Pct("movestamina", "MoveStaminaPercent", "4 - Survival", "survival", "Move Stamina", (GlobalKeys)10, "Movement stamina drain."); Pct("staminaregen", "StaminaRegenPercent", "4 - Survival", "survival", "Stamina Regen", (GlobalKeys)11, "Stamina regen. Higher = faster."); Pct("eitr", "EitrPercent", "4 - Survival", "survival", "Eitr", (GlobalKeys)7, "Eitr rate."); Pct("adrenaline", "AdrenalinePercent", "4 - Survival", "survival", "Adrenaline", (GlobalKeys)6, "Adrenaline rate."); Pct("food", "FoodPercent", "4 - Survival", "survival", "Food Duration", (GlobalKeys)9, "Food duration scaling."); Pct("durability", "DurabilityPercent", "4 - Survival", "survival", "Durability", (GlobalKeys)8, "Tool/weapon durability loss. Lower = lasts longer."); Pct("carry", "CarryWeightPercent", "4 - Survival", "survival", "Carry Weight", (GlobalKeys)16, "Carry weight. 200 = x2 capacity."); Pct("resourcerate", "ResourceRatePercent", "5 - Resources", "resources", "Resource Rate", (GlobalKeys)4, "GLOBAL drop rate (vanilla world modifier ResourceRate). Synced to all clients. Category multipliers stack on top."); GroundAmp = Sw("groundamp", "CategoryGroundAmp", "6 - Loot", "resources", "Ground Amp", def: true, "Server multiplies categorized ground loot after it dropped (works for vanilla/PS5/Xbox clients). Player-dropped items are skipped."); MaxPiles = new Rate { Short = "maxpiles", Key = "MaxExtraPiles", Section = "6 - Loot", Group = "resources", Label = "Max Piles", Kind = RateKind.Number, Min = 1f, Max = 200f, Integer = true, DefaultValue = 20f, Description = "Safety cap: max extra items (LootSpawn=Client) or piles (LootSpawn=Server) created per single drop." }; Add(MaxPiles); LootSpawn = Ch("lootspawn", "LootSpawn", "6 - Loot", "resources", "Loot spawn", "Client", new string[2] { "Client", "Server" }, "How extra loot is created. Client = the server asks the nearest player's game to spawn it (real ground and physics, recommended). Server = the server creates the item objects itself."); Drp("other", "OtherDropMultiplier", "6 - Loot", "resources", "Other Loot", DropClassifier.Kind.Other, "Anything not matched below. Keep at 1 unless you want everything amplified."); Drp("wood", "WoodDropMultiplier", "6a - Materials", "materials", "Wood", DropClassifier.Kind.Wood, "Basic wood."); Drp("finewood", "FineWoodDropMultiplier", "6a - Materials", "materials", "Fine Wood", DropClassifier.Kind.FineWood, "Fine wood."); Drp("corewood", "CoreWoodDropMultiplier", "6a - Materials", "materials", "Core Wood", DropClassifier.Kind.CoreWood, "Core wood (RoundLog)."); Drp("specialwood", "SpecialWoodDropMultiplier", "6a - Materials", "materials", "Special Wood", DropClassifier.Kind.SpecialWood, "Elder bark, Yggdrasil, Ash, Black wood."); Drp("ore", "OreDropMultiplier", "6a - Materials", "materials", "Ore", DropClassifier.Kind.Ore, "Raw ores."); Drp("scrap", "ScrapDropMultiplier", "6a - Materials", "materials", "Scrap", DropClassifier.Kind.Scrap, "Iron/black metal/copper scrap."); Drp("stone", "StoneDropMultiplier", "6a - Materials", "materials", "Stone", DropClassifier.Kind.Stone, "Stone and similar."); Drp("flint", "FlintDropMultiplier", "6a - Materials", "materials", "Flint", DropClassifier.Kind.Flint, "Flint."); Drp("crystal", "CrystalDropMultiplier", "6a - Materials", "materials", "Crystal", DropClassifier.Kind.Crystal, "Crystal, obsidian."); Drp("fuel", "FuelDropMultiplier", "6a - Materials", "materials", "Fuel", DropClassifier.Kind.Fuel, "Coal, resin, tar."); Drp("gems", "GemDropMultiplier", "6a - Materials", "materials", "Gems", DropClassifier.Kind.Gems, "Coins, amber, rubies, necklace."); Drp("boss", "BossDropMultiplier", "6a - Materials", "materials", "Boss Mats", DropClassifier.Kind.BossMats, "Boss materials and boss trophies."); Drp("crops", "CropDropMultiplier", "6b - Consumables", "consumables", "Crops", DropClassifier.Kind.Crops, "Crops / plants that spawn as ground items."); Drp("seeds", "SeedDropMultiplier", "6b - Consumables", "consumables", "Seeds", DropClassifier.Kind.Seeds, "Seeds and cones."); Drp("mushrooms", "MushroomDropMultiplier", "6b - Consumables", "consumables", "Mushrooms", DropClassifier.Kind.Mushrooms, "Mushrooms / magecap."); Drp("berries", "BerryHoneyDropMultiplier", "6b - Consumables", "consumables", "Berries", DropClassifier.Kind.BerriesHoney, "Berries, honey."); Drp("fish", "FishDropMultiplier", "6b - Consumables", "consumables", "Fish", DropClassifier.Kind.Fish, "Fish and similar."); Drp("potions", "PotionDropMultiplier", "6b - Consumables", "consumables", "Potions", DropClassifier.Kind.Potions, "Meads / potions on the ground."); Drp("hide", "HideDropMultiplier", "6c - Mob Drops", "mobs", "Hide", DropClassifier.Kind.Hide, "Hides, leather, pelts, scales."); Drp("trophy", "TrophyDropMultiplier", "6c - Mob Drops", "mobs", "Trophy", DropClassifier.Kind.Trophy, "Normal mob trophies."); Drp("meat", "MeatDropMultiplier", "6c - Mob Drops", "mobs", "Meat", DropClassifier.Kind.Meat, "Raw / cooked meat."); Drp("parts", "PartsDropMultiplier", "6c - Mob Drops", "mobs", "Parts", DropClassifier.Kind.Parts, "Bones, entrails, eyes, glands, fangs."); Drp("feathers", "FeatherDropMultiplier", "6c - Mob Drops", "mobs", "Feathers", DropClassifier.Kind.Feathers, "Feathers."); Drp("specialparts", "SpecialPartsDropMultiplier", "6c - Mob Drops", "mobs", "Special Parts", DropClassifier.Kind.SpecialParts, "Guck, ooze, soft tissue, carapace, bile, ..."); Tg("keepequip", "DeathKeepEquip", "7 - Death", "death", "Keep Equipment", (GlobalKeys)19, "Keep equipped gear on death."); Tg("keepinv", "DeathKeepInventory", "7 - Death", "death", "Keep Inventory", (GlobalKeys)23, "Keep full inventory on death."); Tg("deleteitems", "DeathDeleteItems", "7 - Death", "death", "Delete Items", (GlobalKeys)20, "Delete items on death."); Tg("deleteunequipped", "DeathDeleteUnequipped", "7 - Death", "death", "Delete Unequipped", (GlobalKeys)21, "Delete unequipped items on death."); Tg("skillreset", "DeathSkillsReset", "7 - Death", "death", "Skill Reset", (GlobalKeys)22, "Reset skills on death."); Tg("nobuildcost", "NoBuildCost", "8 - Build Craft", "build", "Free Building", (GlobalKeys)24, "Building costs nothing."); Tg("nocraftcost", "NoCraftCost", "8 - Build Craft", "build", "Free Crafting", (GlobalKeys)25, "Crafting costs nothing."); Tg("allpieces", "AllPiecesUnlocked", "8 - Build Craft", "build", "All Pieces", (GlobalKeys)26, "All build pieces unlocked."); Tg("allrecipes", "AllRecipesUnlocked", "8 - Build Craft", "build", "All Recipes", (GlobalKeys)28, "All recipes unlocked."); Tg("noworkbench", "NoWorkbench", "8 - Build Craft", "build", "No Workbench", (GlobalKeys)27, "No workbench range required."); Tg("toollocks", "WorldLevelLockedTools", "8 - Build Craft", "build", "Tool Locks", (GlobalKeys)29, "World-level tool locks."); Tg("nomap", "NoMap", "9 - Map Portals", "map", "No Map", (GlobalKeys)31, "Disable map."); Tg("noportals", "NoPortals", "9 - Map Portals", "map", "No Portals", (GlobalKeys)32, "Disable portals."); Tg("nobossportals", "NoBossPortals", "9 - Map Portals", "map", "No Boss Portals", (GlobalKeys)33, "Disable boss portals."); Tg("teleportall", "TeleportAll", "9 - Map Portals", "map", "Teleport All", (GlobalKeys)35, "Teleport with ores/metals."); Tg("dungeonbuild", "DungeonBuild", "9 - Map Portals", "map", "Dungeon Build", (GlobalKeys)34, "Allow building in dungeons."); Tg("nopseudo", "NoPseudoDrops", "10 - World Flags", "world", "No Pseudo Drops", (GlobalKeys)36, "Disable pseudo drops."); Tg("nobuildfall", "NoBuildingFall", "10 - World Flags", "world", "No Building Fall", (GlobalKeys)37, "Buildings do not fall."); Tg("noheavysnow", "NoHeavySnow", "10 - World Flags", "world", "No Heavy Snow", (GlobalKeys)38, "No heavy snow."); Tg("allheavysnow", "AllHeavySnow", "10 - World Flags", "world", "All Heavy Snow", (GlobalKeys)39, "Force heavy snow rules."); Tg("fire", "Fire", "10 - World Flags", "world", "Fire", (GlobalKeys)18, "Fire-related world flag."); Smelter = Mul("smelter", "SmelterSpeedMultiplier", "11 - Stations", "stations", "Smelter", "Smelter / kiln / furnace speed. 2 = twice as fast. Values below 1 are ignored."); Cooking = Mul("cooking", "CookingSpeedMultiplier", "11 - Stations", "stations", "Cooking", "Cooking station speed. 2 = twice as fast. Values below 1 are ignored."); Fermenter = Mul("fermenter", "FermenterSpeedMultiplier", "11 - Stations", "stations", "Fermenter", "Fermenter speed. 2 = twice as fast, 0.5 = half as fast. Applies to batches started after the change."); Plants = Mul("plants", "PlantGrowSpeedMultiplier", "11 - Stations", "stations", "Plant Grow", "Plant grow speed. 2 = twice as fast, 0.5 = half as fast. Applies to plants planted after the change."); foreach (Rate item in All) { item.Bind(file); } file.Save(); _applied = Snapshot(); } private static Rate Add(Rate r) { All.Add(r); ByShort[r.Short] = r; ByKey[r.Key] = r; if (r.Drop.HasValue) { ByDrop[r.Drop.Value] = r; } return r; } private static Rate Pct(string sh, string key, string sec, string grp, string label, GlobalKeys gk, string desc) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Percent, Min = 0f, Max = 10000f, DefaultValue = 100f, GlobalKey = gk }); } private static Rate Mul(string sh, string key, string sec, string grp, string label, string desc) { return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Multiplier, Min = 0f, Max = 100f, DefaultValue = 1f }); } private static Rate Drp(string sh, string key, string sec, string grp, string label, DropClassifier.Kind kind, string desc) { return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Multiplier, Min = 0f, Max = 100f, DefaultValue = 1f, Drop = kind }); } private static Rate Tg(string sh, string key, string sec, string grp, string label, GlobalKeys gk, string desc) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Toggle, GlobalKey = gk }); } private static Rate Sw(string sh, string key, string sec, string grp, string label, bool def, string desc) { return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Switch, DefaultFlag = def }); } private static Rate Ch(string sh, string key, string sec, string grp, string label, string def, string[] options, string desc) { return Add(new Rate { Short = sh, Key = key, Section = sec, Group = grp, Label = label, Description = desc, Kind = RateKind.Choice, DefaultText = def, Options = options }); } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class ZNetAwakePatch { private static void Postfix(ZNet __instance) { Plugin.TryActivate(__instance); } } [HarmonyPatch(typeof(ZoneSystem), "Start")] internal static class ZoneSystemStartPatch { private static void Postfix() { if ((Object)(object)ZNet.instance != (Object)null) { Plugin.TryActivate(ZNet.instance); } RateApplier.ApplyGlobalKeys(); } } [HarmonyPatch(typeof(Game), "UpdateWorldRates")] internal static class UpdateWorldRatesPatch { private static void Postfix() { RateApplier.ApplyGlobalKeys(); } } [BepInPlugin("com.morda.serverrates", "ServerRates", "1.7.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.morda.serverrates"; public const string ModName = "ServerRates"; public const string ModVersion = "1.7.0"; public const string ModAuthor = "Morda"; private Harmony _harmony; private float _nextActivationCheck; private static bool _warned; internal static Plugin Instance { get; private set; } internal static ManualLogSource Log { get; private set; } internal static bool Active { get; private set; } private void Awake() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Rates.Init(((BaseUnityPlugin)this).Config); _harmony = new Harmony("com.morda.serverrates"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); LogPatchOwners(typeof(Chat), "RPC_ChatMessage"); LogPatchOwners(typeof(ZRoutedRpc), "RPC_RoutedRPC"); LogPatchOwners(typeof(ZRoutedRpc), "RouteRPC"); LogPatchOwners(typeof(ZRoutedRpc), "HandleRoutedRPC"); ConfigWatch.Start(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"ServerRates v1.7.0 by Morda loaded (dedicated server only, vanilla clients OK)."); } private static void LogPatchOwners(Type type, string method) { try { Patches patchInfo = Harmony.GetPatchInfo((MethodBase)AccessTools.Method(type, method, (Type[])null, (Type[])null)); if (patchInfo == null) { Log.LogWarning((object)("ServerRates: " + type.Name + "." + method + " is not patched!")); return; } List<string> list = new List<string>(); foreach (Patch prefix in patchInfo.Prefixes) { string owner = prefix.owner; int priority = prefix.priority; list.Add(owner + "(prio " + priority + ")"); } Log.LogInfo((object)("ServerRates: " + type.Name + "." + method + " prefixes: " + string.Join(", ", list.ToArray()))); } catch (Exception ex) { Log.LogWarning((object)("ServerRates: patch check failed for " + method + ": " + ex.Message)); } } private void Update() { if (!Active && (Object)(object)ZNet.instance != (Object)null && Time.unscaledTime >= _nextActivationCheck) { _nextActivationCheck = Time.unscaledTime + 2f; TryActivate(ZNet.instance); } ConfigWatch.Tick(); if (Active) { CategoryGroundAmp.Tick(); } } private void OnDestroy() { Harmony harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } Active = false; } internal static void TryActivate(ZNet net) { RatesCommands.Register(); if ((Object)(object)net == (Object)null || !net.IsDedicated()) { Active = false; if (!_warned && (Object)(object)net != (Object)null) { _warned = true; Log.LogWarning((object)("ServerRates: not a dedicated server (IsServer=" + net.IsServer() + ", IsDedicated=" + net.IsDedicated() + "). Rates are inactive.")); } } else { if (!Active) { Log.LogInfo((object)"ServerRates: dedicated server detected, rates enabled."); } Active = true; RateApplier.ApplyGlobalKeys(); CategoryGroundAmp.LogStatus(); } } } internal static class RateApplier { private static bool _busy; private static float _lastLog = -100f; public static void ApplyGlobalKeys() { //IL_006d: 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) if (_busy || !Rates.Ready || (Object)(object)ZoneSystem.instance == (Object)null) { return; } _busy = true; try { int num = 0; foreach (Rate item in Rates.All) { if (item.GlobalKey.HasValue && ((item.Kind == RateKind.Toggle) ? SetToggle(item.GlobalKey.Value, item.Text) : SetScalar(item.GlobalKey.Value, item.Value))) { num++; } } if (num > 0) { string text = "ServerRates: " + num + " world modifier(s) applied."; if (Time.unscaledTime - _lastLog > 10f) { Plugin.Log.LogInfo((object)text); } else { Plugin.Log.LogDebug((object)text); } _lastLog = Time.unscaledTime; } } finally { _busy = false; } } private static bool SetScalar(GlobalKeys key, float value) { //IL_0012: 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) value = Mathf.Max(0f, value); float num = default(float); if (ZoneSystem.instance.GetGlobalKey(key, ref num) && Mathf.Abs(num - value) < 0.001f) { return false; } ZoneSystem.instance.SetGlobalKey(key, value); return true; } private static bool SetToggle(GlobalKeys key, string mode) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) bool globalKey = ZoneSystem.instance.GetGlobalKey(key); if (string.Equals(mode, "On", StringComparison.OrdinalIgnoreCase)) { if (globalKey) { return false; } ZoneSystem.instance.SetGlobalKey(key); return true; } if (string.Equals(mode, "Off", StringComparison.OrdinalIgnoreCase)) { if (!globalKey) { return false; } ZoneSystem.instance.RemoveGlobalKey(key); return true; } return false; } } internal static class RatesChat { [HarmonyPatch(typeof(Chat), "InputText")] private static class ChatInputPatch { private static bool Prefix(Chat __instance) { if ((Object)(object)__instance == (Object)null) { return true; } object value = Traverse.Create((object)__instance).Field("m_input").GetValue(); if (value == null) { return true; } string value2 = Traverse.Create(value).Property("text", (object[])null).GetValue<string>(); if (string.IsNullOrWhiteSpace(value2)) { return true; } string text = value2.Trim(); if (text[0] != '/' || !TryGetRatesBody(text, out var _)) { return true; } if ((Object)(object)Player.m_localPlayer != (Object)null) { __instance.SendText((Type)1, text); } Traverse.Create(value).Property("text", (object[])null).SetValue((object)""); GameObject value3 = Traverse.Create(value).Property("gameObject", (object[])null).GetValue<GameObject>(); if ((Object)(object)value3 != (Object)null) { value3.SetActive(false); } return false; } } [HarmonyPriority(800)] [HarmonyPatch(typeof(ZRoutedRpc), "RPC_RoutedRPC")] private static class RoutedRpcEntryPatch { private static bool Prefix(ZRpc rpc, ZPackage pkg) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_0074: 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_0068: Unknown result type (might be due to invalid IL or missing references) if (pkg == null || !Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } try { RoutedRPCData val = new RoutedRPCData(); val.Deserialize(new ZPackage(pkg.GetArray())); if (!TryParse(val, out var position, out var user, out var text)) { return true; } if (val.m_methodHash == SayHash) { position = PositionOf(val.m_senderPeerID); } return !Intercept("RPC_RoutedRPC", val.m_senderPeerID, position, user, text); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates: could not inspect routed RPC: " + ex.Message)); } return true; } } } [HarmonyPriority(800)] [HarmonyPatch(typeof(ZRoutedRpc), "RouteRPC")] private static class RouteRpcPatch { private static bool Prefix(RoutedRPCData rpcData) { //IL_0056: 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_004a: Unknown result type (might be due to invalid IL or missing references) if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } if (!TryParse(rpcData, out var position, out var user, out var text)) { return true; } if (rpcData.m_methodHash == SayHash) { position = PositionOf(rpcData.m_senderPeerID); } return !Intercept("RouteRPC", rpcData.m_senderPeerID, position, user, text); } } [HarmonyPriority(800)] [HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")] private static class HandleRoutedRpcPatch { private static bool Prefix(RoutedRPCData data) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (data == null || data.m_methodHash != ChatMessageHash) { return true; } if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return true; } if (!TryParse(data, out var position, out var user, out var text)) { return true; } return !Intercept("HandleRoutedRPC", data.m_senderPeerID, position, user, text); } } [HarmonyPatch(typeof(Chat), "RPC_ChatMessage")] private static class ChatRpcPatch { private static bool Prefix(long sender, Vector3 position, int type, UserInfo userInfo, string text) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return !Intercept("RPC_ChatMessage", sender, position, userInfo, text); } } private static readonly int SayHash = StringExtensionMethods.GetStableHashCode("Say"); private static readonly int ChatMessageHash = StringExtensionMethods.GetStableHashCode("ChatMessage"); private static long _lastSender; private static string _lastText; private static float _lastTime = -10f; private static bool _lastHandled; private static int _seen; internal static bool TryGetRatesBody(string text, out string body) { body = null; if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = text.Trim(); if (text2[0] == '/' || text2[0] == '!') { text2 = text2.Substring(1).Trim(); } if (text2.Length == 0) { return false; } string text3 = text2.ToLowerInvariant(); if (!text3.StartsWith("sr_") && !text3.StartsWith("sr ") && !(text3 == "rates") && !text3.StartsWith("rates ")) { return false; } body = text2; return true; } private static bool CanChange(UserInfo user) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!string.Equals(Rates.ChatAccess.Text, "AdminOnly", StringComparison.OrdinalIgnoreCase)) { return true; } try { return user != null && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.PlayerIsAdmin(user.UserId); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates admin check failed: " + ex.Message)); } return false; } } internal static bool TryHandle(long sender, Vector3 position, UserInfo userInfo, string text) { //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_016b: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) if (!TryGetRatesBody(text, out var body)) { return false; } if (!Rates.Ready) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates: chat command ignored, mod inactive (Active=" + Plugin.Active + ", Enabled=" + Rates.Enabled.Display() + ").")); } return false; } if (!Rates.ChatEnabled.Flag) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)"ServerRates: chat command ignored, ChatCommandsEnabled=false in the config."); } RatesFeedback.ReplyChat(sender, "ServerRates: chat commands are switched off (ChatCommandsEnabled=false).", position); return false; } string text2 = ((userInfo != null && !string.IsNullOrEmpty(userInfo.Name)) ? userInfo.Name : "player"); ManualLogSource log3 = Plugin.Log; if (log3 != null) { log3.LogInfo((object)("ServerRates chat command from " + text2 + ": " + body)); } try { RatesCommands.Process(body, new CommandContext { Who = text2, CanWrite = CanChange(userInfo), Position = position, Reply = delegate(string line) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) RatesFeedback.ReplyChat(sender, line, position); } }); } catch (Exception ex) { ManualLogSource log4 = Plugin.Log; if (log4 != null) { log4.LogError((object)("ServerRates command failed: " + ex)); } RatesFeedback.ReplyChat(sender, "ServerRates: command failed, see server log.", position); } return true; } private static bool IsFromServer(long sender) { if ((Object)(object)ZNet.instance != (Object)null) { if (sender != ZNet.GetUID()) { if (ZDOMan.instance != null) { return sender == ZDOMan.GetSessionID(); } return false; } return true; } return false; } private static bool TryParse(RoutedRPCData data, out Vector3 position, out UserInfo user, out string text) { //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_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Expected O, but got Unknown //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) position = Vector3.zero; user = null; text = null; if (data == null || data.m_parameters == null) { return false; } if (data.m_methodHash != SayHash && data.m_methodHash != ChatMessageHash) { return false; } if (IsFromServer(data.m_senderPeerID)) { return false; } try { ZPackage val = new ZPackage(data.m_parameters.GetArray()); if (data.m_methodHash == ChatMessageHash) { position = val.ReadVector3(); } val.ReadInt(); user = new UserInfo(); user.Deserialize(ref val); text = val.ReadString(); return true; } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates: could not read chat RPC: " + ex.Message)); } return false; } } internal static bool Intercept(string via, long sender, Vector3 position, UserInfo user, string text) { //IL_00d7: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance != (Object)null && (sender == ZNet.GetUID() || (ZDOMan.instance != null && sender == ZDOMan.GetSessionID()))) { return false; } if (_seen < 8) { _seen++; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)("ServerRates saw chat via " + via + " from " + sender + ": " + text)); } } if (!TryGetRatesBody(text, out var _)) { return false; } float unscaledTime = Time.unscaledTime; if (sender == _lastSender && text == _lastText && unscaledTime - _lastTime < 0.5f) { _lastTime = unscaledTime; return _lastHandled; } _lastSender = sender; _lastText = text; _lastTime = unscaledTime; _lastHandled = TryHandle(sender, position, user, text); return _lastHandled; } private static Vector3 PositionOf(long peerUid) { //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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) try { ZNetPeer val = (((Object)(object)ZNet.instance != (Object)null) ? ZNet.instance.GetPeer(peerUid) : null); return (val != null) ? val.GetRefPos() : Vector3.zero; } catch { return Vector3.zero; } } } internal sealed class CommandContext { public string Who = "console"; public bool CanWrite = true; public Action<string> Reply; public Vector3? Position; } internal static class RatesCommands { private static readonly Dictionary<string, string> GroupAliases = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { { "mob", "mobs" }, { "mobdrops", "mobs" }, { "drops", "loot" }, { "mats", "materials" }, { "station", "stations" }, { "resource", "resources" }, { "res", "resources" } }; private static bool _registered; public static void Process(string line, CommandContext ctx) { if (!Plugin.Active || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsDedicated()) { ctx.Reply("ServerRates: commands only work on the dedicated server."); return; } string[] array = Tokens(line); if (array.Length == 0) { Help(ctx); return; } string text = array[0].ToLowerInvariant(); if (text.StartsWith("sr_")) { Target(text.Substring(3), array, 1, ctx); return; } if (text != "rates" && text != "sr") { Help(ctx); return; } if (array.Length == 1) { Help(ctx); return; } string text2 = array[1].ToLowerInvariant(); switch (text2) { case "help": case "?": Help(ctx); break; case "status": Status(ctx); break; case "list": case "keys": ListRates(ctx, (array.Length > 2) ? array[2] : null); break; case "cmds": case "commands": Commands(ctx); break; case "get": if (array.Length < 3) { ctx.Reply("Usage: rates get <name>"); } else { Target(array[2].ToLowerInvariant(), array, 3, ctx, readOnly: true); } break; case "set": case "apply": if (array.Length < 4) { ctx.Reply("Usage: rates set <name> <value>"); } else { Target(array[2].ToLowerInvariant(), array, 3, ctx); } break; case "reset": Reset(ctx, (array.Length > 2) ? array[2].ToLowerInvariant() : null); break; case "inspect": StationBoost.Inspect(ctx, repair: false); break; case "repair": if (!ctx.CanWrite) { ctx.Reply("Only server admins can do this (ChatAccess=AdminOnly)."); } else { StationBoost.Inspect(ctx, repair: true); } break; case "preset": Preset(ctx, (array.Length > 2) ? array[2].ToLowerInvariant() : null); break; default: Target(text2, array, 2, ctx); break; } } private static string[] Tokens(string line) { if (string.IsNullOrWhiteSpace(line)) { return new string[0]; } return line.Replace('=', ' ').Split(new char[2] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); } private static string Join(string[] t, int from) { if (from < t.Length) { return string.Join(" ", t, from, t.Length - from); } return ""; } private static bool Resolve(string name, CommandContext ctx, out List<Rate> rates, out string group) { rates = null; group = null; name = name.ToLowerInvariant(); if (name.StartsWith("sr_")) { name = name.Substring(3); } Rate rate = Rates.Find(name); if (rate != null) { rates = new List<Rate> { rate }; return true; } string text = name; if (GroupAliases.TryGetValue(name, out var value)) { text = value; } if (Rates.Groups.ContainsKey(text)) { group = text.ToLowerInvariant(); rates = Rates.InGroup(group); return true; } List<Rate> list = new List<Rate>(); List<Rate> list2 = new List<Rate>(); foreach (Rate item in Rates.All) { if (item.Short.StartsWith(name, StringComparison.OrdinalIgnoreCase)) { list.Add(item); } else if (item.Short.IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0) { list2.Add(item); } } List<Rate> list3 = ((list.Count > 0) ? list : list2); if (list3.Count == 1) { rates = list3; return true; } if (list3.Count > 1) { List<string> list4 = new List<string>(); for (int i = 0; i < list3.Count && i < 8; i++) { list4.Add(list3[i].Short); } ctx.Reply("'" + name + "' could be: " + string.Join(", ", list4.ToArray())); return false; } ctx.Reply("Unknown '" + name + "'. Try: rates cmds"); return false; } private static void Target(string name, string[] t, int valueFrom, CommandContext ctx, bool readOnly = false) { if (!Resolve(name, ctx, out var rates, out var group)) { return; } string text = Join(t, valueFrom); if (readOnly || text.Length == 0) { Show(ctx, rates, group); return; } if (!ctx.CanWrite) { ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly)."); return; } string text2 = null; int num = 0; foreach (Rate item in rates) { if (item.TrySet(text, out var error)) { num++; } else if (text2 == null) { text2 = error; } } if (num == 0) { ctx.Reply(text2 ?? "Nothing changed."); return; } Commit(ctx, (group != null) ? Cap(group) : null); if (text2 != null && rates.Count > 1) { ctx.Reply("Some rates were skipped: " + text2); } } private static void Show(CommandContext ctx, List<Rate> rates, string group) { if (rates.Count == 1) { Rate rate = rates[0]; ctx.Reply(rate.Label + ": " + rate.Display() + " (sr_" + rate.Short + " <" + rate.RangeHint() + ">)"); } else { Reply(ctx, Cap(group ?? "rates"), rates); } } private static void Commit(CommandContext ctx, string label) { Rates.Save(); ConfigWatch.NotifySaved(); List<Rate> list = Rates.TakeChanges(); RateApplier.ApplyGlobalKeys(); CategoryGroundAmp.LogStatus(); if (list.Count == 0) { ctx.Reply("Already set, nothing changed."); } else { RatesFeedback.AnnounceChanges(list, ctx.Who, label); } } private static void Reset(CommandContext ctx, string name) { if (string.IsNullOrEmpty(name)) { ctx.Reply("Usage: rates reset <name|group|all>"); } else if (!ctx.CanWrite) { ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly)."); } else if (name == "all") { ResetAll(); Commit(ctx, "All rates"); } else { if (!Resolve(name, ctx, out var rates, out var group)) { return; } foreach (Rate item in rates) { item.Reset(); } Commit(ctx, (group != null) ? Cap(group) : null); } } private static void ResetAll() { foreach (Rate item in Rates.All) { if (item.Group != "general") { item.Reset(); } } } private static void Preset(CommandContext ctx, string name) { if (string.IsNullOrEmpty(name)) { ctx.Reply("Usage: rates preset vanilla | x2 | x3 | x5 | x10 (any number 1-100)"); return; } if (!ctx.CanWrite) { ctx.Reply("Only server admins can change rates here (ChatAccess=AdminOnly)."); return; } switch (name) { case "vanilla": case "default": case "off": ResetAll(); Commit(ctx, "Vanilla"); return; } string text = name.TrimStart(new char[1] { 'x' }); if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || result < 1f || result > 100f) { ctx.Reply("Unknown preset '" + name + "'. Try vanilla, x2, x5, x10"); return; } string error; foreach (Rate item in Rates.InGroup("loot")) { item.TrySet(text, out error); } Rates.Find("skillgain").TrySet((result * 100f).ToString(CultureInfo.InvariantCulture), out error); foreach (Rate item2 in Rates.InGroup("stations")) { item2.TrySet(text, out error); } Commit(ctx, "Preset x" + Rate.Fmt(result)); } private static void Help(CommandContext ctx) { ctx.Reply("ServerRates (type without a slash):"); ctx.Reply("sr_wood 5 = set | sr_wood = show | sr_loot 5 = all loot"); ctx.Reply("rates status | rates list loot | rates cmds"); ctx.Reply("rates preset x5 | rates reset all | rates reset wood"); ctx.Reply("rates inspect | rates repair (station next to you)"); } private static void Status(CommandContext ctx) { ctx.Reply("ServerRates v1.7.0 " + Rates.Enabled.Display() + " | chat: " + (Rates.ChatEnabled.Flag ? Rates.ChatAccess.Display() : "off")); List<Rate> list = new List<Rate>(); foreach (Rate item in Rates.All) { if (item.Group != "general" && !item.IsDefault()) { list.Add(item); } } if (list.Count == 0) { ctx.Reply("All rates are vanilla."); } else { Reply(ctx, "Changed", list); } } private static void ListRates(CommandContext ctx, string name) { List<Rate> rates; string group; if (string.IsNullOrEmpty(name)) { ctx.Reply("Groups: " + string.Join(" ", GroupNames())); ctx.Reply("Example: rates list loot"); } else if (Resolve(name, ctx, out rates, out group)) { Show(ctx, rates, group); } } private static void Commands(CommandContext ctx) { ctx.Reply("Groups (set all at once): " + string.Join(" ", GroupNames())); foreach (string item in GroupNames()) { if (item == "loot") { continue; } List<string> list = new List<string>(); foreach (Rate item2 in Rates.InGroup(item)) { list.Add(item2.Short); } Pack(ctx, item, list); } } private static List<string> GroupNames() { List<string> list = new List<string>(); foreach (string key in Rates.Groups.Keys) { list.Add(key); } return list; } private static void Reply(CommandContext ctx, string title, List<Rate> rates) { List<string> list = new List<string>(); foreach (Rate rate in rates) { list.Add(rate.Short + " " + rate.Display()); } Pack(ctx, title, list); } private static void Pack(CommandContext ctx, string title, List<string> items) { StringBuilder stringBuilder = new StringBuilder(title + ":"); foreach (string item in items) { if (stringBuilder.Length + item.Length + 3 > 90) { ctx.Reply(stringBuilder.ToString()); stringBuilder.Clear(); stringBuilder.Append(" "); } stringBuilder.Append((stringBuilder.Length > 2 && stringBuilder[stringBuilder.Length - 1] != ':') ? " | " : " ").Append(item); } if (stringBuilder.Length > 2) { ctx.Reply(stringBuilder.ToString()); } } private static string Cap(string s) { if (!string.IsNullOrEmpty(s)) { return char.ToUpperInvariant(s[0]) + s.Substring(1); } return s; } public static void Register() { //IL_004c: 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_006b: Expected O, but got Unknown //IL_006b: Expected O, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) try { IDictionary value = Traverse.Create(typeof(Terminal)).Field("commands").GetValue<IDictionary>(); if (_registered && value != null && value.Contains("rates")) { return; } new ConsoleCommand("rates", "ServerRates: status | list | cmds | preset | reset | help", new ConsoleEvent(OnConsole), false, true, true, false, true, false, new ConsoleOptionsFetcher(FetchOptions), true, true, false); foreach (Rate item in Rates.All) { RegisterOne("sr_" + item.Short, item.Key + " [value]"); } foreach (string key in Rates.Groups.Keys) { RegisterOne("sr_" + key, "all " + key + " rates [value]"); } _registered = true; ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)"ServerRates: registered F5/console commands (rates, sr_*)."); } } catch (Exception ex) { _registered = false; ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("ServerRates: failed to register console commands: " + ex)); } } } private static void RegisterOne(string cmd, string help) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) new ConsoleCommand(cmd, "ServerRates " + help, new ConsoleEvent(OnConsole), false, true, true, false, true, false, (ConsoleOptionsFetcher)null, false, true, false); } private static List<string> FetchOptions() { List<string> list = new List<string> { "status", "list", "cmds", "preset", "reset", "help" }; foreach (Rate item in Rates.All) { list.Add(item.Short); } return list; } private static void OnConsole(ConsoleEventArgs args) { if (args != null && !((Object)(object)args.Context == (Object)null) && args.Args != null && args.Args.Length != 0) { Process(string.Join(" ", args.Args), new CommandContext { Who = "admin", CanWrite = true, Reply = RatesFeedback.Say }); } } } [HarmonyPatch(typeof(Terminal), "InitTerminal")] internal static class RatesTerminalPatch { private static void Postfix() { RatesCommands.Register(); } } internal static class RatesFeedback { [HarmonyPatch(typeof(ZNet), "RPC_RemoteCommand")] private static class RemoteCommandPatch { private static void Prefix(ZRpc rpc) { CurrentRemoteRpc = rpc; } } [HarmonyPatch(typeof(ZNet), "InternalCommand")] private static class InternalCommandPatch { private static void Prefix(ZRpc rpc) { CurrentRemoteRpc = rpc; } private static void Postfix() { CurrentRemoteRpc = null; } private static Exception Finalizer(Exception __exception) { CurrentRemoteRpc = null; return __exception; } } internal static ZRpc CurrentRemoteRpc { get; private set; } private static string[] SplitLines(string msg) { return msg.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }, StringSplitOptions.RemoveEmptyEntries); } public static void Say(string msg) { if (string.IsNullOrEmpty(msg)) { return; } string[] array = SplitLines(msg); foreach (string text in array) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)text); } ZRpc currentRemoteRpc = CurrentRemoteRpc; if (currentRemoteRpc != null && currentRemoteRpc.IsConnected() && (Object)(object)ZNet.instance != (Object)null) { try { ZNet.instance.RemotePrint(currentRemoteRpc, text); } catch (Exception ex) { ManualLogSource log2 = Plugin.Log; if (log2 != null) { log2.LogWarning((object)("ServerRates RemotePrint failed: " + ex.Message)); } } } else if ((Object)(object)Console.instance != (Object)null) { Console.instance.Print(text); } } } public static void ReplyChat(long peer, string text, Vector3 near = default(Vector3)) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) string[] array = SplitLines(text ?? ""); foreach (string text2 in array) { SendChat(peer, text2, near); } } public static void Announce(string hud, string chatLine) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) ManualLogSource log = Plugin.Log; if (log != null) { log.LogInfo((object)chatLine); } SendHud(hud); SendChat(0L, chatLine); } public static void AnnounceChanges(List<Rate> changed, string who, string label = null) { if (changed == null || changed.Count == 0) { return; } string text; if (changed.Count > 3) { text = (string.IsNullOrEmpty(label) ? (changed.Count + " rates changed") : (label + " " + changed[0].Display())); } else { List<string> list = new List<string>(); foreach (Rate item in changed) { list.Add(item.Label + " " + item.Display()); } text = string.Join(" | ", list.ToArray()); } string text2 = (string.IsNullOrEmpty(who) ? "" : (" (" + who + ")")); Announce(text, "[ServerRates] " + text + text2); } private static void SendHud(string text) { if (string.IsNullOrEmpty(text) || ZRoutedRpc.instance == null) { return; } try { ZRoutedRpc.instance.InvokeRoutedRPC(0L, "ShowMessage", new object[2] { 2, text }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates HUD failed: " + ex.Message)); } } } private static void SendChat(long target, string text, Vector3 at = default(Vector3)) { //IL_0011: 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_0022: Expected O, but got Unknown //IL_0051: 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) if (string.IsNullOrEmpty(text) || ZRoutedRpc.instance == null) { return; } try { UserInfo val = new UserInfo { Name = "ServerRates" }; PlatformUserID? val2 = ChatBridge.ServerUser(); if (val2.HasValue) { val.UserId = val2.Value; } ZRoutedRpc.instance.InvokeRoutedRPC(target, "ChatMessage", new object[4] { at, 1, val, text }); } catch (Exception ex) { ManualLogSource log = Plugin.Log; if (log != null) { log.LogWarning((object)("ServerRates chat failed: " + ex.Message)); } } } } internal static class StationBoost { private enum Kind { Plant, Fermenter, Smelter, Cooking } private sealed class StationInfo { public Kind Kind; public string Name; public float Min; public float Max; public float Duration; public int MaxFuel; public int MaxOre; public float SecPerProduct; public int Slots; public Dictionary<string, float> CookTime; } private sealed class Track { public float NextCheck; public float LastTime; public float LastBake; public float LastFuel; public float[] LastCooked; } private const double FreshSeconds = 120.0; private const uint RevisionJump = 64u; private const float SmelterPeriod = 5f; private const float CookingPeriod = 2f; private const float MaxElapsed = 15f; private static readonly Dictionary<int, StationInfo> Known = new Dictionary<int, StationInfo>(); private static readonly HashSet<int> NotAStation = new HashSet<int>(); private static readonly HashSet<ZDOID> DonePlants = new HashSet<ZDOID>(); private static readonly Dictionary<ZDOID, long> LastStart = new Dictionary<ZDOID, long>(); private static readonly Dictionary<ZDOID, Track> Tracks = new Dictionary<ZDOID, Track>(); private static readonly List<ZDOID> Scratch = new List<ZDOID>(); private static int _logged; private const float InfoRange = 3f; private const float InfoCooldown = 30f; private static readonly Dictionary<string, float> InfoShown = new Dictionary<string, float>(); private static StationInfo GetInfo(int hash) { if (Known.TryGetValue(hash, out var value)) { return value; } if (NotAStation.Contains(hash)) { return null; } GameObject val = (((Object)(object)ZNetScene.instance != (Object)null) ? ZNetScene.instance.GetPrefab(hash) : null); if ((Object)(object)val != (Object)null) { Plant component = val.GetComponent<Plant>(); Fermenter val2 = (((Object)(object)component == (Object)null) ? val.GetComponent<Fermenter>() : null); Smelter component2 = val.GetComponent<Smelter>(); CookingStation component3 = val.GetComponent<CookingStation>(); if ((Object)(object)component != (Object)null) { value = new StationInfo { Kind = Kind.Plant, Min = component.m_growTime, Max = component.m_growTimeMax }; } else if ((Object)(object)val2 != (Object)null) { value = new StationInfo { Kind = Kind.Fermenter, Duration = val2.m_fermentationDuration }; } else if ((Object)(object)component2 != (Object)null) { value = new StationInfo { Kind = Kind.Smelter, MaxFuel = component2.m_maxFuel, MaxOre = component2.m_maxOre, SecPerProduct = component2.m_secPerProduct }; } else if ((Object)(object)component3 != (Object)null && component3.m_slots != null) { value = new StationInfo { Kind = Kind.Cooking, Slots = component3.m_slots.Length, CookTime = new Dictionary<string, float>() }; foreach (ItemConversion item in component3.m_conversion) { if (item != null && (Object)(object)item.m_from != (Object)null) { value.CookTime[((Object)item.m_from).name] = item.m_cookTime; } } } if (value != null) { value.Name = ((Object)val).name; } } if (value == null) { NotAStation.Add(hash); return null; } Known[hash] = value; return value; } public static void Observe(ZDO zdo, float now) { int prefab = zdo.GetPrefab(); if (NotAStation.Contains(prefab) || (Mathf.Approximately(Rates.Plants.Value, 1f) && Mathf.Approximately(Rates.Fermenter.Value, 1f) && Mathf.Approximately(Rates.Smelter.Value, 1f) && Mathf.Approximately(Rates.Cooking.Value, 1f))) { return; } StationInfo info = GetInfo(prefab); if (info != null) { switch (info.Kind) { case Kind.Plant: BoostPlant(zdo, info, Rates.Plants.Value); break; case Kind.Fermenter: BoostFermenter(zdo, info, Rates.Fermenter.Value); break; case Kind.Smelter: BoostSmelter(zdo, info, Rates.Smelter.Value, now); break; case Kind.Cooking: BoostCooking(zdo, info, Rates.Cooking.Value, now); break; } } } private static void BoostPlant(ZDO zdo, StationInfo info, float mult) { //IL_0020: 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_00a9: 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) mult = Mathf.Max(0.01f, mult); if (Mathf.Approximately(mult, 1f) || DonePlants.Contains(zdo.m_uid)) { return; } long num = zdo.GetLong(ZDOVars.s_plantTime, 0L); if (num != 0L) { if (!IsFresh(num)) { DonePlants.Add(zdo.m_uid); return; } State state = Random.state; Random.InitState(zdo.GetInt(ZDOVars.s_seed, 0)); float value = Random.value; Random.state = state; double duration = Mathf.Lerp(info.Min, info.Max, value); ShiftStart(zdo, ZDOVars.s_plantTime, num, duration, mult, info.Name); DonePlants.Add(zdo.m_uid); } } private static void BoostFermenter(ZDO zdo, StationInfo info, float mult) { //IL_0033: 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_0054: Unknown result type (might be due to invalid IL or missing references) mult = Mathf.Max(0.01f, mult); if (Mathf.Approximately(mult, 1f)) { return; } long num = zdo.GetLong(ZDOVars.s_startTime, 0L); if (num != 0L && (!LastStart.TryGetValue(zdo.m_uid, out var value) || value != num)) { if (!IsFresh(num)) { LastStart[zdo.m_uid] = num; } else { LastStart[zdo.m_uid] = ShiftStart(zdo, ZDOVars.s_startTime, num, info.Duration, mult, info.Name); } } } private static bool IsFresh(long ticks) { if ((Object)(object)ZNet.instance == (Object)null) { return false; } double totalSeconds = new TimeSpan(ZNet.instance.GetTime().Ticks - ticks).TotalSeconds; if (totalSeconds >= 0.0) { return totalSeconds < 120.0; } return false; } private static long ShiftStart(ZDO zdo, int key, long startTicks, double duration, float mult, string name) { double num = duration * (1.0 - 1.0 / (double)mult); long num2 = startTicks - (long)(num * 10000000.0); zdo.Set(key, num2); Publish(zdo); Log(name + " x" + Rate.Fmt(mult) + ": needs " + Rate.Fmt((float)(duration / (double)mult)) + "s instead of " + Rate.Fmt((float)duration) + "s"); return num2; } private static void BoostSmelter(ZDO zdo, StationInfo info, float mult, float now) { //IL_000f: 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 (mult <= 1.001f) { return; } if (!Tracks.TryGetValue(zdo.m_uid, out var value)) { Dictionary<ZDOID, Track> tracks = Tracks; ZDOID uid = zdo.m_uid; Track obj = new Track { NextCheck = now + 5f, LastTime = now, LastBake = zdo.GetFloat(ZDOVars.s_bakeTimer, 0f), LastFuel = zdo.GetFloat(ZDOVars.s_fuel, 0f) }; value = obj; tracks[uid] = obj; } else if (!(now < value.NextCheck)) { float num = Mathf.Min(now - value.LastTime, 15f); value.NextCheck = now + 5f; value.LastTime = now; float num2 = zdo.GetFloat(ZDOVars.s_bakeTimer, 0f); float num3 = zdo.GetFloat(ZDOVars.s_fuel, 0f); bool num4 = zdo.GetInt(ZDOVars.s_queued, 0) > 0 && (info.MaxFuel == 0 || num3 > 0f) && (Mathf.Abs(num2 - value.LastBake) > 0.001f || num3 < value.LastFuel - 0.0001f); value.LastBake = num2; value.LastFuel = num3; if (num4) { float num5 = zdo.GetFloat(ZDOVars.s_accTime, 0f) + (mult - 1f) * num; zdo.Set(ZDOVars.s_accTime, Mathf.Min(num5, 3000f)); Publish(zdo); Log(info.Name + " x" + Rate.Fmt(mult) + ": +" + Rate.Fmt((mult - 1f) * num) + "s of work"); } } } private static void BoostCooking(ZDO zdo, StationInfo info, float mult, float now) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) if (mult <= 1.001f) { return; } if (!Tracks.TryGetValue(zdo.m_uid, out var value)) { value = new Track { NextCheck = now + 2f, LastTime = now, LastCooked = new float[info.Slots] }; for (int i = 0; i < info.Slots; i++) { value.LastCooked[i] = zdo.GetFloat("slot" + i, 0f); } Tracks[zdo.m_uid] = value; } else { if (now < value.NextCheck || value.LastCooked == null) { return; } float num = Mathf.Min(now - value.LastTime, 15f); value.NextCheck = now + 2f; value.LastTime = now; bool flag = false; for (int j = 0; j < info.Slots; j++) { string text = zdo.GetString("slot" + j, ""); int num2 = zdo.GetInt("slotstatus" + j, 0); float num3 = zdo.GetFloat("slot" + j, 0f); float num4 = value.LastCooked[j]; value.LastCooked[j] = num3; if (!string.IsNullOrEmpty(text) && num2 == 0 && !(num3 <= num4 + 0.001f) && info.CookTime.TryGetValue(text, out var value2) && !(value2 <= 0f) && !(num3 >= value2)) { float num5 = Mathf.Min(num3 + (mult - 1f) * num, value2); zdo.Set("slot" + j, num5); value.LastCooked[j] = num5; flag = true; } } if (flag) { Publish(zdo); Log(info.Name + " x" + Rate.Fmt(mult) + ": cooking advanced"); } } } public unsafe static void Info(ZDO zdo, float now) { //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_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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_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) if (!Rates.StationInfo.Flag) { return; } int prefab = zdo.GetPrefab(); if (NotAStation.Contains(prefab)) { return; } StationInfo info = GetInfo(prefab); if (info == null || info.Kind == Kind.Plant) { return; } List<ZNetPeer> peers = ZNet.instance.GetPeers(); Vector3 position = zdo.GetPosition(); int num = 0; while (peers != null && num < peers.Count) { ZNetPeer val = peers[num]; if (val != null && val.IsReady()) { Vector3 val2 = val.GetRefPos() - position; if (!(((Vector3)(ref val2)).sqrMagnitude > 9f)) { string text = val.m_uid.ToString(); ZDOID uid = zdo.m_uid; string key = text + ":" + ((object)(*(ZDOID*)(&uid))/*cast due to .constrained prefix*/).ToString(); if (!InfoShown.TryGetValue(key, out var value) || !(now < value)) { InfoShown[key] = now + 30f; string text2 = InfoText(info); if (text2 != null && ZRoutedRpc.instance != null) { ZRoutedRpc.instance.InvokeRoutedRPC(val.m_uid, "ShowMessage", new object[2] { 1, text2 }); } } } } num++; } } private static string InfoText(StationInfo info) { string text = Pretty(info.Name); switch (info.Kind) { case Kind.Smelter: return text + ": " + PerItem(info.SecPerProduct, Rates.Smelter.Value) + " per item"; case Kind.Fermenter: return text + ": " + PerItem(info.Duration, Rates.Fermenter.Value, canSlowDown: true) + " per batch"; case Kind.Cooking: { float num = float.MaxValue; float num2 = 0f; foreach (float value in info.CookTime.Values) { num = Mathf.Min(num, value); num2 = Mathf.Max(num2, value); } if (num2 <= 0f) { return null; } float num3 = Mathf.Max(1f, Rates.Cooking.Value); string text2 = (Mathf.Approximately(num, num2) ? (Rate.Fmt(num / num3) + "s") : (Rate.Fmt(num / num3) + "s - " + Rate.Fmt(num2 / num3) + "s")); return text + ": " + text2 + " per item" + ((num3 > 1.001f) ? (" (x" + Rate.Fmt(num3) + ")") : ""); } default: return null; } } private static string Pretty(string prefabName) { string[] array = (prefabName ?? "").Replace("piece_", "").Replace('_', ' ').Trim() .Replace("blastfurnace", "blast furnace") .Replace("cookingstation", "cooking station") .Replace("eitrrefinery", "eitr refinery") .Replace("spinningwheel", "spinning wheel") .Replace("charcoalkiln", "charcoal kiln") .Split(new char[1] { ' ' }); for (int i = 0; i < array.Length; i++) { if (array[i].Length > 0) { array[i] = char.ToUpperInvariant(array[i][0]) + array[i].Substring(1); } } return string.Join(" ", array); } public static void Inspect(CommandContext ctx, bool repair) { //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) //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) //IL_005a: 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_0086: Unknown result type (might be due to invalid IL or missing references) if (!ctx.Position.HasValue || ZDOMan.instance == null || (Object)(object)ZNet.instance == (Object)null) { ctx.Reply("Stand next to the station and use this in the game chat."); return; } Vector3 value = ctx.Position.Value; List<ZDO> list = new List<ZDO>(); List<ZDO> list2 = new List<ZDO>(); ZDOMan.instance.FindSectorObjects(ZoneSystem.GetZone(value), ZNet.instance.GetSyncedSimulationDistance(), list, list2); List<KeyValuePair<float, ZDO>> list3 = new List<KeyValuePair<float, ZDO>>(); foreach (ZDO item in list) { float num = Vector3.Distance(item.GetPosition(), value); if (num <= 8f && GetInfo(item.GetPrefab()) != null) { list3.Add(new KeyValuePair<float, ZDO>(num, item)); } } list3.Sort((KeyValuePair<float, ZDO> a, KeyValuePair<float, ZDO> b) => a.Key.CompareTo(b.Key)); if (list3.Count == 0) { ctx.Reply("No station within 8 m of you."); return; } long sessionID = ZDOMan.GetSessionID(); for (int num2 = 0; num2 < list3.Count && num2 < 3; num2++) { ZDO value2 = list3[num2].Value; StationInfo info = GetInfo(value2.GetPrefab()); long owner = value2.GetOwner(); string text = info.Name + " " + Rate.Fmt(list3[num2].Key) + "m owner=" + ((own