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 ServersideResourceMultiplier v1.0.0
ServersideResourceMultiplier.dll
Decompiled 2 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("ServersideResourceMultiplier")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("ServersideResourceMultiplier")] [assembly: AssemblyTitle("ServersideResourceMultiplier")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ServersideResourceMultiplier { internal static class DropTracker { private struct PendingDrop { public ZDOID Id; public float Time; } private struct BreakEvent { public Vector3 Position; public float Time; public SourceIndex.SourceInfo Source; } private const uint RevisionMargin = 10u; private const int MaxExtraPiles = 50; private static readonly int s_setAreaHealthHash = StringExtensionMethods.GetStableHashCode("RPC_SetAreaHealth"); private static readonly int s_hideHash = StringExtensionMethods.GetStableHashCode("Hide"); private static readonly List<PendingDrop> s_pending = new List<PendingDrop>(); private static readonly List<BreakEvent> s_events = new List<BreakEvent>(); private static readonly HashSet<ZDOID> s_ownSpawns = new HashSet<ZDOID>(); private static bool Active { get { if (Plugin.Enabled.Value && SourceIndex.Ready && (Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } } internal static void Reset() { s_pending.Clear(); s_events.Clear(); s_ownSpawns.Clear(); } internal static void OnZdoCreated(ZDO zdo) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (zdo != null && Active) { s_pending.Add(new PendingDrop { Id = zdo.m_uid, Time = Time.time }); } } internal static void OnZdoDestroyed(ZDO zdo) { if (Active) { AddEvent(zdo); } } internal static void OnRoutedRpc(RoutedRPCData data) { //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref data.m_targetZDO)).IsNone() || !Active) { return; } if (data.m_methodHash == s_setAreaHealthHash) { ZPackage parameters = data.m_parameters; int pos = parameters.GetPos(); parameters.ReadInt(); float num = parameters.ReadSingle(); parameters.SetPos(pos); if (num > 0f) { return; } } else if (data.m_methodHash != s_hideHash) { return; } AddEvent(ZDOMan.instance.GetZDO(data.m_targetZDO)); } private static void AddEvent(ZDO zdo) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (zdo != null && SourceIndex.TryGetSource(zdo.GetPrefab(), out var source)) { s_events.Add(new BreakEvent { Position = zdo.GetPosition(), Time = Time.time, Source = source }); } } internal static void Tick() { if (!Active) { if (s_pending.Count > 0 || s_events.Count > 0) { Reset(); } return; } float time = Time.time; float value = Plugin.MatchWindowSeconds.Value; for (int num = s_events.Count - 1; num >= 0; num--) { if (time - s_events[num].Time > value * 2f) { RemoveSwapBack(s_events, num); } } for (int num2 = s_pending.Count - 1; num2 >= 0; num2--) { if (TryProcess(s_pending[num2], time, value)) { RemoveSwapBack(s_pending, num2); } } } private static bool TryProcess(PendingDrop pending, float now, float window) { //IL_0006: 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_0061: Unknown result type (might be due to invalid IL or missing references) ZDO zDO = ZDOMan.instance.GetZDO(pending.Id); if (zDO == null) { return true; } if (s_ownSpawns.Remove(pending.Id)) { return true; } int prefab = zDO.GetPrefab(); if (!SourceIndex.IsResourceItem(prefab)) { return true; } bool result = now - pending.Time > window; byte[] byteArray = zDO.GetByteArray(ZDOVars.s_itemData, (byte[])null); if (byteArray == null || byteArray.Length <= 2) { return result; } if (!TryFindEvent(prefab, zDO.GetPosition(), pending.Time, window, out var match)) { return result; } Apply(zDO, prefab, match); return true; } private static bool TryFindEvent(int itemHash, Vector3 position, float dropTime, float window, out BreakEvent match) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_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) for (int num = s_events.Count - 1; num >= 0; num--) { BreakEvent breakEvent = s_events[num]; if (!(Mathf.Abs(breakEvent.Time - dropTime) > window) && breakEvent.Source.Items.Contains(itemHash)) { Vector3 val = breakEvent.Position - position; if (!(((Vector3)(ref val)).sqrMagnitude > breakEvent.Source.Radius * breakEvent.Source.Radius)) { match = breakEvent; return true; } } } match = default(BreakEvent); return false; } private static void Apply(ZDO zdo, int prefabHash, BreakEvent ev) { GameObject prefab = ZNetScene.instance.GetPrefab(prefabHash); ItemDrop val = (((Object)(object)prefab != (Object)null) ? prefab.GetComponent<ItemDrop>() : null); if ((Object)(object)val == (Object)null) { return; } ItemData val2 = val.m_itemData.Clone(); val2.m_dropPrefab = prefab; ItemDrop.LoadFromZDO(val2, zdo, -1); if (val2.m_pickedUp) { return; } float multiplier = Plugin.GetMultiplier(ev.Source.Category, prefabHash); int stack = val2.m_stack; int num = Scale(stack, multiplier); if (num == stack) { return; } if (Plugin.LogMatches.Value) { Plugin.Log.LogInfo((object)$"{ev.Source.Name} -> {((Object)prefab).name} x{stack} => x{num} (multiplier {multiplier:0.##})"); } if (num <= 0) { zdo.SetOwner(ZDOMan.GetSessionID()); ZDOMan.instance.DestroyZDO(zdo); return; } int num2 = Mathf.Max(1, val2.m_shared.m_maxStackSize); val2.m_stack = Mathf.Min(num, num2); ItemDrop.SaveToZDO(val2, zdo, -1); zdo.DataRevision += 10; int num3 = num - val2.m_stack; int num4 = 0; while (num3 > 0 && num4 < 50) { int num5 = Mathf.Min(num3, num2); SpawnPile(zdo, prefab, prefabHash, val2, num5); num3 -= num5; num4++; } } private static void SpawnPile(ZDO original, GameObject prefab, int prefabHash, ItemData data, int stack) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0067: 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) Vector3 val = original.GetPosition() + new Vector3(Random.Range(-0.5f, 0.5f), 0.25f, Random.Range(-0.5f, 0.5f)); ZDO val2 = ZDOMan.instance.CreateNewZDO(val, prefabHash); s_ownSpawns.Add(val2.m_uid); ZNetView component = prefab.GetComponent<ZNetView>(); val2.Persistent = component.m_persistent; val2.Type = component.m_type; val2.Distant = component.m_distant; val2.SetPrefab(prefabHash); val2.SetRotation(original.GetRotation()); val2.Set(ZDOVars.s_spawnTime, original.GetLong(ZDOVars.s_spawnTime, ZNet.instance.GetTime().Ticks)); ItemData obj = data.Clone(); obj.m_stack = stack; ItemDrop.SaveToZDO(obj, val2, -1); val2.SetOwner(original.GetOwner()); } private static int Scale(int stack, float multiplier) { float num = (float)stack * multiplier; int num2 = Mathf.FloorToInt(num); if (Random.value < num - (float)num2) { num2++; } return num2; } private static void RemoveSwapBack<T>(List<T> list, int index) { int index2 = list.Count - 1; list[index] = list[index2]; list.RemoveAt(index2); } } [HarmonyPatch] internal static class Patches { [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] private static void ZNetScene_Awake(ZNetScene __instance) { DropTracker.Reset(); SourceIndex.Build(__instance); ZDOMan instance = ZDOMan.instance; instance.m_onZDODestroyed = (Action<ZDO>)Delegate.Combine(instance.m_onZDODestroyed, new Action<ZDO>(DropTracker.OnZdoDestroyed)); } [HarmonyPatch(typeof(ZNet), "Update")] [HarmonyPostfix] private static void ZNet_Update() { Plugin.Instance.OnNetUpdate(); } [HarmonyPatch(typeof(ZDOMan), "CreateNewZDO", new Type[] { typeof(ZDOID), typeof(Vector3), typeof(int) })] [HarmonyPostfix] private static void ZDOMan_CreateNewZDO(ZDO __result) { DropTracker.OnZdoCreated(__result); } [HarmonyPatch(typeof(ZRoutedRpc), "HandleRoutedRPC")] [HarmonyPrefix] private static void ZRoutedRpc_HandleRoutedRPC(RoutedRPCData data) { DropTracker.OnRoutedRpc(data); } } [BepInPlugin("thelm.ServersideResourceMultiplier", "ServersideResourceMultiplier", "1.0.0")] public class Plugin : BaseUnityPlugin { public const string ModGuid = "thelm.ServersideResourceMultiplier"; public const string ModName = "ServersideResourceMultiplier"; public const string ModVersion = "1.0.0"; private const string GeneralSection = "1 - General"; private const string AdvancedSection = "2 - Advanced"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> GlobalMultiplier; internal static ConfigEntry<float> MatchWindowSeconds; internal static ConfigEntry<bool> LogMatches; internal static ConfigEntry<bool> LogSources; private static readonly Dictionary<Category, ConfigEntry<float>> s_categoryMultipliers = new Dictionary<Category, ConfigEntry<float>>(); private static readonly Dictionary<(Category, int), ConfigEntry<float>> s_itemMultipliers = new Dictionary<(Category, int), ConfigEntry<float>>(); private FileSystemWatcher _watcher; private volatile bool _reloadRequested; private DateTime _ignoreWatcherUntil; private void Awake() { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_013a: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("1 - General", "Enabled", true, "Turns the mod on or off. Only does anything on the machine that hosts the world (dedicated server, player-hosted server or single player)."); GlobalMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "GlobalMultiplier", 1f, Range("Applied to every resource drop, on top of the category and per-item multipliers. Final amount = GlobalMultiplier x MiningMultiplier/WoodcuttingMultiplier x item multiplier.")); s_categoryMultipliers[Category.Mining] = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "MiningMultiplier", 1f, Range("Applied to everything broken with a pickaxe: rocks, ore veins, Leviathans, ruins...")); s_categoryMultipliers[Category.Woodcutting] = ((BaseUnityPlugin)this).Config.Bind<float>("1 - General", "WoodcuttingMultiplier", 1f, Range("Applied to everything broken with an axe: trees, logs, stumps...")); MatchWindowSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("2 - Advanced", "MatchWindowSeconds", 2f, new ConfigDescription("How far apart in time (seconds) a resource breaking and its drops appearing may be and still count as the same event. Raise it if players with high ping miss the bonus.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 10f), Array.Empty<object>())); LogMatches = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Advanced", "LogMatches", false, "Writes a log line for every drop the mod changes. Useful to check it is working."); LogSources = ((BaseUnityPlugin)this).Config.Bind<bool>("2 - Advanced", "LogSources", false, "Lists every resource node the mod recognised, by category, when a world loads."); new Harmony("thelm.ServersideResourceMultiplier").PatchAll(typeof(Plugin).Assembly); WatchConfigFile(); } private void OnDestroy() { _watcher?.Dispose(); } internal void OnNetUpdate() { if (_reloadRequested) { _reloadRequested = false; ReloadConfig(); } DropTracker.Tick(); } internal static float GetMultiplier(Category category, int itemPrefabHash) { ConfigEntry<float> value; float num = (s_itemMultipliers.TryGetValue((category, itemPrefabHash), out value) ? value.Value : 1f); return GlobalMultiplier.Value * s_categoryMultipliers[category].Value * num; } internal static void BindItems(IEnumerable<SourceIndex.ItemInfo> items) { ConfigFile config = ((BaseUnityPlugin)Instance).Config; bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { foreach (SourceIndex.ItemInfo item in items.OrderBy<SourceIndex.ItemInfo, string>((SourceIndex.ItemInfo i) => i.Name, StringComparer.OrdinalIgnoreCase)) { if (!s_itemMultipliers.ContainsKey((item.Category, item.Hash))) { string text = string.Join(", ", item.Sources.Take(6)) + ((item.Sources.Count > 6) ? ", ..." : ""); s_itemMultipliers[(item.Category, item.Hash)] = config.Bind<float>(SectionFor(item.Category), item.Name, 1f, Range("Multiplier for " + item.Name + " from " + text + ".")); } } Instance._ignoreWatcherUntil = DateTime.UtcNow.AddSeconds(2.0); config.Save(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } } private static string SectionFor(Category category) { if (category != Category.Mining) { return "4 - Woodcutting items"; } return "3 - Mining items"; } private static ConfigDescription Range(string description) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown return new ConfigDescription(description, (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()); } private void WatchConfigFile() { _watcher = new FileSystemWatcher(Path.GetDirectoryName(((BaseUnityPlugin)this).Config.ConfigFilePath), Path.GetFileName(((BaseUnityPlugin)this).Config.ConfigFilePath)) { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite), IncludeSubdirectories = false, EnableRaisingEvents = true }; FileSystemEventHandler onChange = delegate { if (DateTime.UtcNow > _ignoreWatcherUntil) { _reloadRequested = true; } }; _watcher.Changed += onChange; _watcher.Created += onChange; _watcher.Renamed += delegate { onChange(null, null); }; } private void ReloadConfig() { if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath)) { return; } try { ((BaseUnityPlugin)this).Config.Reload(); Log.LogInfo((object)"Config reloaded."); } catch (Exception ex) { Log.LogWarning((object)("Could not reload config: " + ex.Message)); } } } internal enum Category { Mining, Woodcutting } internal static class SourceIndex { internal sealed class SourceInfo { public string Name; public Category Category; public float Radius; public readonly HashSet<int> Items = new HashSet<int>(); } internal sealed class ItemInfo { public int Hash; public string Name; public Category Category; public readonly List<string> Sources = new List<string>(); } private const float DestructibleRadius = 8f; private const float MineRockRadius = 30f; private const float TreeRadius = 12f; private const float LogMargin = 6f; private static readonly Dictionary<int, SourceInfo> s_sources = new Dictionary<int, SourceInfo>(); private static readonly Dictionary<(Category, int), ItemInfo> s_items = new Dictionary<(Category, int), ItemInfo>(); private static readonly HashSet<int> s_itemHashes = new HashSet<int>(); internal static bool Ready { get; private set; } internal static bool TryGetSource(int prefabHash, out SourceInfo source) { return s_sources.TryGetValue(prefabHash, out source); } internal static bool IsResourceItem(int prefabHash) { return s_itemHashes.Contains(prefabHash); } internal static void Build(ZNetScene scene) { s_sources.Clear(); s_items.Clear(); s_itemHashes.Clear(); foreach (GameObject prefab in scene.m_prefabs) { if (!((Object)(object)prefab == (Object)null) && !((Object)(object)prefab.GetComponent<Piece>() != (Object)null) && !((Object)(object)prefab.GetComponent<Character>() != (Object)null)) { MineRock component = prefab.GetComponent<MineRock>(); if ((Object)(object)component != (Object)null) { Add(prefab, component.m_dropItems, Category.Mining, 30f); } MineRock5 component2 = prefab.GetComponent<MineRock5>(); if ((Object)(object)component2 != (Object)null) { Add(prefab, component2.m_dropItems, Category.Mining, 30f); } TreeLog component3 = prefab.GetComponent<TreeLog>(); if ((Object)(object)component3 != (Object)null) { Add(prefab, component3.m_dropWhenDestroyed, Category.Woodcutting, component3.m_spawnDistance + 6f); } TreeBase component4 = prefab.GetComponent<TreeBase>(); if ((Object)(object)component4 != (Object)null) { Add(prefab, component4.m_dropWhenDestroyed, Category.Woodcutting, 12f); } Destructible component5 = prefab.GetComponent<Destructible>(); DropOnDestroyed component6 = prefab.GetComponent<DropOnDestroyed>(); if ((Object)(object)component5 != (Object)null && (Object)(object)component6 != (Object)null && TryClassify(component5, out var category)) { Add(prefab, component6.m_dropWhenDestroyed, category, 8f); } } } Ready = s_sources.Count > 0; Plugin.Log.LogInfo((object)$"Found {s_sources.Count} resource nodes dropping {s_itemHashes.Count} different items."); if (Plugin.LogSources.Value) { foreach (IGrouping<Category, SourceInfo> item in from s in s_sources.Values group s by s.Category) { Plugin.Log.LogInfo((object)string.Format("{0}: {1}", item.Key, string.Join(", ", from s in item select s.Name into n orderby n select n))); } } Plugin.BindItems(s_items.Values); } private static bool TryClassify(Destructible destructible, out Category category) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Invalid comparison between Unknown and I4 //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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) category = Category.Woodcutting; if ((int)destructible.m_destructibleType == 2) { return true; } DamageModifiers damages = destructible.m_damages; if (Hurts(damages.m_blunt) || Hurts(damages.m_slash) || Hurts(damages.m_pierce)) { return false; } bool flag = Hurts(damages.m_pickaxe); bool flag2 = Hurts(damages.m_chop); if (flag == flag2) { return false; } category = ((!flag) ? Category.Woodcutting : Category.Mining); return true; } private static bool Hurts(DamageModifier modifier) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)modifier != 3) { return (int)modifier != 4; } return false; } private static void Add(GameObject prefab, DropTable table, Category category, float radius) { //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_0031: 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_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) if (table == null || table.m_drops == null) { return; } int stableHashCode = StringExtensionMethods.GetStableHashCode(((Object)prefab).name); foreach (DropData drop in table.m_drops) { if (!((Object)(object)drop.m_item == (Object)null) && !((Object)(object)drop.m_item.GetComponent<ItemDrop>() == (Object)null)) { if (!s_sources.TryGetValue(stableHashCode, out var value)) { value = new SourceInfo { Name = ((Object)prefab).name, Category = category, Radius = radius }; s_sources.Add(stableHashCode, value); } value.Radius = Mathf.Max(value.Radius, radius); int stableHashCode2 = StringExtensionMethods.GetStableHashCode(((Object)drop.m_item).name); value.Items.Add(stableHashCode2); s_itemHashes.Add(stableHashCode2); if (!s_items.TryGetValue((value.Category, stableHashCode2), out var value2)) { value2 = new ItemInfo { Hash = stableHashCode2, Name = ((Object)drop.m_item).name, Category = value.Category }; s_items.Add((value.Category, stableHashCode2), value2); } if (!value2.Sources.Contains(((Object)prefab).name)) { value2.Sources.Add(((Object)prefab).name); } } } } } }