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 BM CorpseCleaner v0.1.0
BM_CorpseCleaner.dll
Decompiled 7 hours agousing System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using LethalConfig; using LethalConfig.ConfigItems; using LethalConfig.ConfigItems.Options; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: AssemblyTitle("BM_CorpseCleaner")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("BM_CorpseCleaner")] [assembly: AssemblyCopyright("Copyright © 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("cbac02e1-4c13-47a9-a908-a7d3d8b6229c")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("1.0.0.0")] namespace BM_CorpseCleaner; internal static class Configuration { internal static ConfigEntry<int> MaximumCorpses; internal static ConfigEntry<float> MinimumCorpseLifetime; internal static void Initialize(ConfigFile config) { MaximumCorpses = config.Bind<int>("Corpse Cleanup", "Maximum Enemy Corpses", 20, "Maximum number of enemy corpses allowed to remain. When the limit is exceeded, the oldest eligible corpse is removed first."); MinimumCorpseLifetime = config.Bind<float>("Corpse Cleanup", "Minimum Corpse Lifetime", 30f, "Minimum number of seconds an enemy corpse must remain before it may be removed."); RegisterLethalConfig(); } private static void RegisterLethalConfig() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown //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) //IL_0055: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown LethalConfigManager.SetModDescription("Limits the number of enemy corpses kept in the level. Oldest eligible enemy corpses are removed first. Player corpses are never affected."); LethalConfigManager.SkipAutoGen(); ConfigEntry<int> maximumCorpses = MaximumCorpses; IntSliderOptions val = new IntSliderOptions(); ((BaseRangeOptions<int>)val).Min = 1; ((BaseRangeOptions<int>)val).Max = 100; ((BaseOptions)val).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new IntSliderConfigItem(maximumCorpses, val)); ConfigEntry<float> minimumCorpseLifetime = MinimumCorpseLifetime; FloatSliderOptions val2 = new FloatSliderOptions(); ((BaseRangeOptions<float>)val2).Min = 0f; ((BaseRangeOptions<float>)val2).Max = 600f; ((BaseOptions)val2).RequiresRestart = false; LethalConfigManager.AddConfigItem((BaseConfigItem)new FloatSliderConfigItem(minimumCorpseLifetime, val2)); } } internal static class CorpseManager { private sealed class CorpseEntry { public EnemyAI Enemy; public float DeathTime; public long Sequence; } [HarmonyPatch(typeof(StartOfRound), "Start")] private static class StartOfRoundStartPatch { [HarmonyPostfix] private static void Postfix(StartOfRound __instance) { if (!((Object)(object)__instance == (Object)null)) { BeginSession(__instance); } } } [HarmonyPatch(typeof(EnemyAI), "KillEnemy")] private static class EnemyAIKillEnemyPatch { [HarmonyPostfix] private static void Postfix(EnemyAI __instance) { if (!((Object)(object)__instance == (Object)null)) { RegisterDeath(__instance); } } } private static readonly List<CorpseEntry> corpses = new List<CorpseEntry>(); private static long nextSequence; private static Coroutine cleanupCoroutine; private static StartOfRound activeRound; private static readonly WaitForSecondsRealtime CleanupDelay = new WaitForSecondsRealtime(1f); internal static void Initialize() { corpses.Clear(); nextSequence = 0L; cleanupCoroutine = null; activeRound = null; Plugin.Log.LogInfo((object)"Corpse manager initialized."); } internal static void BeginSession(StartOfRound round) { if ((Object)(object)round == (Object)null) { return; } if (!IsServer()) { Plugin.Log.LogInfo((object)"BM_CorpseCleaner running as client. Corpse cleanup will be handled by server."); return; } corpses.Clear(); nextSequence = 0L; activeRound = round; if (cleanupCoroutine != null) { try { ((MonoBehaviour)round).StopCoroutine(cleanupCoroutine); } catch { } cleanupCoroutine = null; } ScanExistingDeadEnemies(); cleanupCoroutine = ((MonoBehaviour)round).StartCoroutine(CleanupLoop(round)); Plugin.Log.LogWarning((object)"### BM_CORPSECLEANER SERVER ACTIVE ###"); } internal static void RegisterDeath(EnemyAI enemy) { if (IsServer() && !((Object)(object)enemy == (Object)null) && enemy.isEnemyDead && !ContainsEnemy(enemy)) { CorpseEntry corpseEntry = new CorpseEntry(); corpseEntry.Enemy = enemy; corpseEntry.DeathTime = Time.realtimeSinceStartup; corpseEntry.Sequence = nextSequence++; corpses.Add(corpseEntry); Plugin.Log.LogInfo((object)("Registered enemy corpse #" + corpseEntry.Sequence + ": " + GetEnemyName(enemy) + " | tracked=" + corpses.Count)); TryCleanup(); } } private static IEnumerator CleanupLoop(StartOfRound round) { Plugin.Log.LogInfo((object)"BM corpse cleanup loop started."); while ((Object)(object)round != (Object)null && (Object)(object)round == (Object)(object)activeRound) { if (IsServer()) { ScanExistingDeadEnemies(); RemoveInvalidEntries(); TryCleanup(); } yield return CleanupDelay; } cleanupCoroutine = null; Plugin.Log.LogInfo((object)"BM corpse cleanup loop ended."); } private static void TryCleanup() { if (!IsServer()) { return; } RemoveInvalidEntries(); int num = Configuration.MaximumCorpses.Value; if (num < 1) { num = 1; } while (CountTrackedCorpses() > num) { CorpseEntry corpseEntry = FindOldestEligibleCorpse(); if (corpseEntry == null) { break; } if (!RemoveCorpse(corpseEntry)) { corpses.Remove(corpseEntry); } } } private static CorpseEntry FindOldestEligibleCorpse() { float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Mathf.Max(0f, Configuration.MinimumCorpseLifetime.Value); CorpseEntry corpseEntry = null; foreach (CorpseEntry corpse in corpses) { if (corpse == null) { continue; } EnemyAI enemy = corpse.Enemy; if ((Object)(object)enemy == (Object)null || !enemy.isEnemyDead) { continue; } float num2 = realtimeSinceStartup - corpse.DeathTime; if (!(num2 < num) && !((Object)(object)enemy.enemyType == (Object)null) && enemy.enemyType.canBeDestroyed) { NetworkObject networkObject = ((NetworkBehaviour)enemy).NetworkObject; if (!((Object)(object)networkObject == (Object)null) && networkObject.IsSpawned && (corpseEntry == null || corpse.Sequence < corpseEntry.Sequence)) { corpseEntry = corpse; } } } return corpseEntry; } private static bool RemoveCorpse(CorpseEntry entry) { if (entry == null || (Object)(object)entry.Enemy == (Object)null) { return false; } EnemyAI enemy = entry.Enemy; if (!enemy.isEnemyDead) { return false; } if ((Object)(object)enemy.enemyType == (Object)null || !enemy.enemyType.canBeDestroyed) { return false; } NetworkObject networkObject = ((NetworkBehaviour)enemy).NetworkObject; if ((Object)(object)networkObject == (Object)null || !networkObject.IsSpawned) { return false; } float num = Time.realtimeSinceStartup - entry.DeathTime; Plugin.Log.LogInfo((object)("Removing oldest enemy corpse #" + entry.Sequence + ": " + GetEnemyName(enemy) + " | age=" + num.ToString("0.0") + "s")); networkObject.Despawn(true); corpses.Remove(entry); Plugin.Log.LogInfo((object)("Enemy corpse removed. Remaining tracked corpses=" + CountTrackedCorpses())); return true; } private static void ScanExistingDeadEnemies() { if ((Object)(object)RoundManager.Instance == (Object)null || RoundManager.Instance.SpawnedEnemies == null) { return; } List<EnemyAI> spawnedEnemies = RoundManager.Instance.SpawnedEnemies; foreach (EnemyAI item in spawnedEnemies) { if (!((Object)(object)item == (Object)null) && item.isEnemyDead && !ContainsEnemy(item)) { CorpseEntry corpseEntry = new CorpseEntry(); corpseEntry.Enemy = item; corpseEntry.DeathTime = Time.realtimeSinceStartup; corpseEntry.Sequence = nextSequence++; corpses.Add(corpseEntry); Plugin.Log.LogDebug((object)("Discovered existing dead enemy #" + corpseEntry.Sequence + ": " + GetEnemyName(item))); } } } private static void RemoveInvalidEntries() { for (int num = corpses.Count - 1; num >= 0; num--) { CorpseEntry corpseEntry = corpses[num]; if (corpseEntry == null || (Object)(object)corpseEntry.Enemy == (Object)null) { corpses.RemoveAt(num); } else { NetworkObject networkObject = ((NetworkBehaviour)corpseEntry.Enemy).NetworkObject; if ((Object)(object)networkObject == (Object)null || !networkObject.IsSpawned) { corpses.RemoveAt(num); } } } } private static bool ContainsEnemy(EnemyAI enemy) { foreach (CorpseEntry corpse in corpses) { if (corpse != null && (Object)(object)corpse.Enemy == (Object)(object)enemy) { return true; } } return false; } private static int CountTrackedCorpses() { int num = 0; foreach (CorpseEntry corpse in corpses) { if (corpse != null && (Object)(object)corpse.Enemy != (Object)null && corpse.Enemy.isEnemyDead) { num++; } } return num; } private static bool IsServer() { NetworkManager singleton = NetworkManager.Singleton; return (Object)(object)singleton != (Object)null && singleton.IsServer; } private static string GetEnemyName(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return "<null>"; } if ((Object)(object)enemy.enemyType != (Object)null && !string.IsNullOrEmpty(enemy.enemyType.enemyName)) { return enemy.enemyType.enemyName; } return ((Object)enemy).name; } } [BepInPlugin("com.brox.BM_CorpseCleaner", "BM_CorpseCleaner", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public const string ModGuid = "com.brox.BM_CorpseCleaner"; public const string ModName = "BM_CorpseCleaner"; public const string ModVersion = "0.1.0"; internal static ManualLogSource Log; internal static ConfigFile PluginConfig; private static bool initialized; private void Awake() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown if (!initialized) { initialized = true; Log = ((BaseUnityPlugin)this).Logger; PluginConfig = ((BaseUnityPlugin)this).Config; Log.LogInfo((object)"BM_CorpseCleaner v0.1.0 initializing..."); Configuration.Initialize(PluginConfig); CorpseManager.Initialize(); Harmony val = new Harmony("com.brox.BM_CorpseCleaner"); val.PatchAll(typeof(Plugin).Assembly); Log.LogWarning((object)"### BM_CORPSECLEANER PATCHES INSTALLED ###"); Log.LogInfo((object)("Maximum enemy corpses: " + Configuration.MaximumCorpses.Value)); Log.LogInfo((object)("Minimum corpse lifetime: " + Configuration.MinimumCorpseLifetime.Value + " seconds")); Log.LogInfo((object)"BM_CorpseCleaner loaded."); } } }