using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using EverythingCanDieAlternative.ModCompatibility; using EverythingCanDieAlternative.ModCompatibility.Handlers; using EverythingCanDieAlternative.UI; using GameNetcodeStuff; using HarmonyLib; using LethalNetworkAPI; using TMPro; using Unity.Netcode; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("EverythingCanDieAlternative")] [assembly: AssemblyFileVersion("1.1.81")] [assembly: AssemblyDescription("A mod that makes everything in Lethal Company damageable")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("1.1.81.0")] namespace EverythingCanDieAlternative { public class DespawnConfiguration { private static DespawnConfiguration _instance; private ConfigFile _configFile; private readonly Dictionary> _enemyDespawnEnabled = new Dictionary>(); private static readonly HashSet _enemiesWithProperDeathAnimations = new HashSet(StringComparer.Ordinal) { "DOGDAY", "DOGDAYCRITTER", "PICKYCRITTER", "CATNAPCRITTER", "BOBBYCRITTER", "CRAFTYCRITTER", "HOPPYCRITTER", "BUBBACRITTER", "KICKINCRITTER", "BABOONHAWK", "MANEATER", "CENTIPEDE", "CRAWLER", "TULIPSNAKE", "FLOWERMAN", "FORESTGIANT", "HOARDINGBUG", "MASKED", "MOUTHDOG", "NUTCRACKER", "BUNKERSPIDER", "BUSHWOLF" }; public static DespawnConfiguration Instance => _instance ?? (_instance = new DespawnConfiguration()); public ConfigEntry EnableDespawnFeature { get; private set; } private DespawnConfiguration() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "nwnt.EverythingCanDieAlternative_Despawn_Rules.cfg"); _configFile = new ConfigFile(text, true); EnableDespawnFeature = _configFile.Bind("General", "EnableDespawnFeature", true, new ConfigDescription("If true, dead enemies can despawn based on other settings", (AcceptableValueBase)null, Array.Empty())); PreCreateEnemyConfigEntries(); } public void ReloadConfig() { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown try { _configFile.Reload(); _enemyDespawnEnabled.Clear(); EnableDespawnFeature = _configFile.Bind("General", "EnableDespawnFeature", true, new ConfigDescription("If true, dead enemies can despawn based on other settings", (AcceptableValueBase)null, Array.Empty())); Plugin.LogInfo("Despawn configuration reloaded from disk"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error reloading despawn config: " + ex.Message)); } } private void PreCreateEnemyConfigEntries() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown if (Plugin.enemies == null || Plugin.enemies.Count <= 0) { return; } foreach (EnemyType enemy in Plugin.enemies) { string text = Plugin.RemoveInvalidCharacters(enemy.enemyName).ToUpper(); bool flag = !_enemiesWithProperDeathAnimations.Contains(text); _configFile.Bind("Enemies", text + ".Despawn", flag, new ConfigDescription("If true, " + enemy.enemyName + " will despawn after death", (AcceptableValueBase)null, Array.Empty())); } } public bool ShouldDespawnEnemy(string enemyName) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected O, but got Unknown if (!EnableDespawnFeature.Value) { return false; } string text = Plugin.RemoveInvalidCharacters(enemyName).ToUpper(); if (_enemyDespawnEnabled.TryGetValue(text, out var value)) { return value.Value; } bool flag = !_enemiesWithProperDeathAnimations.Contains(text); ConfigEntry val = _configFile.Bind("Enemies", text + ".Despawn", flag, new ConfigDescription("If true, " + enemyName + " will despawn after death", (AcceptableValueBase)null, Array.Empty())); _enemyDespawnEnabled[text] = val; return val.Value; } public void ClearCache() { _enemyDespawnEnabled.Clear(); Plugin.Log.LogInfo((object)"Despawn cache cleared"); } } public class EnemyControlConfiguration { private static EnemyControlConfiguration _instance; private ConfigFile _configFile; private readonly Dictionary> _enemyModEnabled = new Dictionary>(); public static EnemyControlConfiguration Instance => _instance ?? (_instance = new EnemyControlConfiguration()); private EnemyControlConfiguration() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown string text = Path.Combine(Paths.ConfigPath, "nwnt.EverythingCanDieAlternative_Enemy_Control.cfg"); _configFile = new ConfigFile(text, true); _configFile.Bind("Enemies", "_INFO", "Control which enemies are affected by this mod", new ConfigDescription("This is an Experimental Feature: If you set an enemy's value to 'false', it will not be affected by the EverythingCanDieAlternative mod. The health and hit synconization, and the despawn feature will not take effect for the configured enemy. This can be useful if specific enemies have built-in hit/health/death mechanisms that you want to preserve, but that ECDA overwrites. Now you can preserve them by setting the enemy to false.", (AcceptableValueBase)null, Array.Empty())); } public void ReloadConfig() { try { _configFile.Reload(); _enemyModEnabled.Clear(); Plugin.LogInfo("Enemy control configuration reloaded from disk"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error reloading enemy control config: " + ex.Message)); } } public void PreCreateEnemyConfigEntries() { //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown if (Plugin.enemies == null || Plugin.enemies.Count <= 0) { return; } Plugin.LogInfo($"Creating control entries for {Plugin.enemies.Count} enemy types"); foreach (EnemyType enemy in Plugin.enemies) { if (!((Object)(object)enemy == (Object)null) && !string.IsNullOrEmpty(enemy.enemyName)) { string text = Plugin.RemoveInvalidCharacters(enemy.enemyName).ToUpper(); _configFile.Bind("Enemies", text + ".Enabled", true, new ConfigDescription("If set to false, " + enemy.enemyName + " will not be affected by this mod", (AcceptableValueBase)null, Array.Empty())); } } } public bool IsModEnabledForEnemy(string enemyName) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown string text = Plugin.RemoveInvalidCharacters(enemyName).ToUpper(); if (_enemyModEnabled.TryGetValue(text, out var value)) { return value.Value; } try { ConfigEntry val = _configFile.Bind("Enemies", text + ".Enabled", true, new ConfigDescription("If set to false, " + enemyName + " will not be affected by this mod", (AcceptableValueBase)null, Array.Empty())); _enemyModEnabled[text] = val; return val.Value; } catch (Exception ex) { Plugin.Log.LogError((object)("Error checking if mod is enabled for " + enemyName + ": " + ex.Message + " - defaulting to true")); return true; } } public void ClearCache() { _enemyModEnabled.Clear(); Plugin.LogInfo("Enemy control cache cleared"); } } public static class HealthManager { [Serializable] public struct HitData { public int EnemyInstanceId; public ulong EnemyNetworkId; public int EnemyIndex; public string EnemyName; public float Damage; public ulong PlayerClientId; public override string ToString() { return $"HitData(EnemyId={EnemyInstanceId}, NetworkId={EnemyNetworkId}, Index={EnemyIndex}, Name={EnemyName}, Damage={Damage}, PlayerClientId={PlayerClientId})"; } } [CompilerGenerated] private sealed class d__32 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public EnemyAI enemy; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__32(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Plugin.LogInfo("DelayedSilence started for " + enemy?.enemyType?.enemyName); <>2__current = (object)new WaitForSeconds(0.5f); <>1__state = 1; return true; case 1: <>1__state = -1; if ((Object)(object)enemy == (Object)null || (Object)(object)((Component)enemy).gameObject == (Object)null) { return false; } try { AudioSource[] componentsInChildren = ((Component)enemy).GetComponentsInChildren(true); if (componentsInChildren.Length == 0) { return false; } ((MonoBehaviour)StartOfRound.Instance).StartCoroutine(FadeOutEnemyAudio(componentsInChildren, 1f)); } catch (Exception ex) { Plugin.Log.LogError((object)("Error silencing dead enemy " + enemy?.enemyType?.enemyName + ": " + ex.Message)); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__33 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public AudioSource[] sources; public float duration; private float[] 5__2; private float 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: { <>1__state = -1; 5__2 = new float[sources.Length]; for (int i = 0; i < sources.Length; i++) { 5__2[i] = (((Object)(object)sources[i] != (Object)null) ? sources[i].volume : 0f); } 5__3 = 0f; break; } case 1: <>1__state = -1; break; } if (5__3 < duration) { 5__3 += Time.deltaTime; float num = Mathf.Clamp01(5__3 / duration); for (int j = 0; j < sources.Length; j++) { if ((Object)(object)sources[j] != (Object)null) { sources[j].volume = Mathf.Lerp(5__2[j], 0f, num); } } <>2__current = null; <>1__state = 1; return true; } for (int k = 0; k < sources.Length; k++) { if ((Object)(object)sources[k] != (Object)null) { sources[k].volume = 0f; sources[k].Stop(); } } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__35 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public EnemyAI enemy; private int 5__2; private int 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__35(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: { <>1__state = -1; if ((Object)(object)enemy == (Object)null) { return false; } 5__2 = ((Object)enemy).GetInstanceID(); 5__3 = enemy.thisEnemyIndex; float num = 0.5f; SellBodiesCompatibility sellBodies = ModCompatibilityManager.Instance.SellBodies; if (sellBodies != null && sellBodies.IsInstalled) { num = sellBodies.GetDespawnDelay(); Plugin.LogInfo($"Using SellBodies compatibility despawn delay: {num}s for {enemy.enemyType.enemyName}"); } <>2__current = (object)new WaitForSeconds(num); <>1__state = 1; return true; } case 1: <>1__state = -1; if ((Object)(object)enemy != (Object)null && enemy.isEnemyDead) { if (despawnMessage != null) { despawnMessage.SendClients(5__3); } Object.Destroy((Object)(object)((Component)enemy).gameObject); } enemiesInDespawnProcess.Remove(5__2); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static int networkVarCounter = 0; private static readonly Dictionary> enemyHealthVars = new Dictionary>(); private static readonly Dictionary enemyMaxHealth = new Dictionary(); private static readonly Dictionary processedEnemies = new Dictionary(); private static readonly Dictionary enemyNetworkIds = new Dictionary(); private static readonly Dictionary enemyNetworkVarNames = new Dictionary(); private static readonly Dictionary enemiesInDespawnProcess = new Dictionary(); private static readonly Dictionary immortalEnemies = new Dictionary(); private static readonly Dictionary enemyInstanceCache = new Dictionary(); private static readonly Dictionary enemyByNetworkId = new Dictionary(); private static readonly Dictionary sanitizedEnemyNames = new Dictionary(); private static readonly Dictionary pendingDamage = new Dictionary(); private static float lastBatchTime = 0f; private const float BATCH_INTERVAL = 0.1f; private static LNetworkMessage despawnMessage; private static LNetworkMessage hitMessage; private static bool networkMessagesCreated = false; public static void Initialize() { enemyHealthVars.Clear(); enemyMaxHealth.Clear(); processedEnemies.Clear(); enemyNetworkIds.Clear(); enemyNetworkVarNames.Clear(); enemiesInDespawnProcess.Clear(); immortalEnemies.Clear(); enemyInstanceCache.Clear(); enemyByNetworkId.Clear(); sanitizedEnemyNames.Clear(); pendingDamage.Clear(); lastBatchTime = 0f; CreateNetworkMessages(); Plugin.LogInfo("Networked Health Manager initialized"); } private static void CreateNetworkMessages() { if (networkMessagesCreated) { Plugin.LogInfo("Network messages already created, skipping recreation"); return; } try { hitMessage = LNetworkMessage.Create("ECDA_HitMessage", (Action)delegate(HitData hitData, ulong clientId) { Plugin.LogInfo($"[HOST] Received hit message from client {clientId}: {hitData}"); if (((NetworkBehaviour)StartOfRound.Instance).IsHost) { EnemyAI val2 = FindEnemyMultiMethod(hitData); PlayerControllerB playerWhoHit = null; if (hitData.PlayerClientId != 0L) { PlayerControllerB[] allPlayerScripts = StartOfRound.Instance.allPlayerScripts; foreach (PlayerControllerB val3 in allPlayerScripts) { if (val3.actualClientId == hitData.PlayerClientId) { playerWhoHit = val3; break; } } } if ((Object)(object)val2 != (Object)null && !val2.isEnemyDead) { ProcessDamageDirectly(val2, hitData.Damage, playerWhoHit); } else { Plugin.Log.LogWarning((object)$"Could not find enemy: {hitData.EnemyName} (NetworkID: {hitData.EnemyNetworkId}, Index: {hitData.EnemyIndex})"); } } }, (Action)null, (Action)null); despawnMessage = LNetworkMessage.Create("ECDA_DespawnMessage", (Action)delegate(int enemyIndex, ulong clientId) { if (!((NetworkBehaviour)StartOfRound.Instance).IsHost) { EnemyAI val = FindEnemyByIndex(enemyIndex); if ((Object)(object)val != (Object)null) { Plugin.LogInfo($"[CLIENT] Received despawn message for enemy index {enemyIndex}"); Object.Destroy((Object)(object)((Component)val).gameObject); } } }, (Action)null, (Action)null); networkMessagesCreated = true; Plugin.LogInfo("Network messages created successfully"); } catch (Exception arg) { Plugin.Log.LogError((object)$"Error creating network messages: {arg}"); } } public static string GetSanitizedName(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null || (Object)(object)enemy.enemyType == (Object)null) { return string.Empty; } int instanceID = ((Object)enemy).GetInstanceID(); if (sanitizedEnemyNames.TryGetValue(instanceID, out var value)) { return value; } string text = Plugin.RemoveInvalidCharacters(enemy.enemyType.enemyName).ToUpper(); sanitizedEnemyNames[instanceID] = text; return text; } private static EnemyAI FindEnemyByIndex(int index) { RoundManager instance = RoundManager.Instance; if (instance?.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if ((Object)(object)spawnedEnemy != (Object)null && spawnedEnemy.thisEnemyIndex == index) { return spawnedEnemy; } } } return null; } private static EnemyAI FindEnemyMultiMethod(HitData hitData) { if (hitData.EnemyNetworkId != 0L && enemyByNetworkId.TryGetValue(hitData.EnemyNetworkId, out var value) && (Object)(object)value != (Object)null) { Plugin.LogInfo($"Found enemy by NetworkObjectId (cache): {hitData.EnemyNetworkId}"); return value; } if (hitData.EnemyIndex >= 0) { EnemyAI val = FindEnemyByIndex(hitData.EnemyIndex); if ((Object)(object)val != (Object)null) { Plugin.LogInfo($"Found enemy by index: {hitData.EnemyIndex}"); return val; } } if (!string.IsNullOrEmpty(hitData.EnemyName)) { RoundManager instance = RoundManager.Instance; if (instance?.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy in instance.SpawnedEnemies) { if ((Object)(object)spawnedEnemy != (Object)null && (Object)(object)spawnedEnemy.enemyType != (Object)null && spawnedEnemy.enemyType.enemyName == hitData.EnemyName) { Plugin.LogInfo("Found enemy by name: " + hitData.EnemyName); return spawnedEnemy; } } } } Plugin.Log.LogWarning((object)"Could not find enemy. SpawnedEnemies in round:"); RoundManager instance2 = RoundManager.Instance; if (instance2?.SpawnedEnemies != null) { foreach (EnemyAI spawnedEnemy2 in instance2.SpawnedEnemies) { if ((Object)(object)spawnedEnemy2 != (Object)null) { Plugin.Log.LogWarning((object)$" - {spawnedEnemy2.enemyType?.enemyName}, Index: {spawnedEnemy2.thisEnemyIndex}, NetworkId: {((NetworkBehaviour)spawnedEnemy2).NetworkObjectId}"); } } } return null; } public static void SetupEnemy(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null || (Object)(object)enemy.enemyType == (Object)null) { return; } try { int instanceId = ((Object)enemy).GetInstanceID(); enemyInstanceCache[instanceId] = enemy; if ((Object)(object)((NetworkBehaviour)enemy).NetworkObject != (Object)null) { enemyNetworkIds[instanceId] = ((NetworkBehaviour)enemy).NetworkObjectId; enemyByNetworkId[((NetworkBehaviour)enemy).NetworkObjectId] = enemy; } if (processedEnemies.TryGetValue(instanceId, out var value) && value) { Plugin.LogInfo($"Enemy {enemy.enemyType.enemyName} (ID: {instanceId}) already processed, skipping setup"); return; } string enemyName = enemy.enemyType.enemyName; string sanitizedName = GetSanitizedName(enemy); if (!Plugin.IsModEnabledForEnemy(sanitizedName)) { Plugin.LogInfo("Mod disabled for enemy " + enemyName + " via config, using vanilla behavior"); processedEnemies[instanceId] = true; } else if (Plugin.CanMob(".Unimmortal", sanitizedName)) { float num = Plugin.GetMobHealth(sanitizedName, enemy.enemyHP); BrutalCompanyMinusCompatibility brutalCompanyMinus = ModCompatibilityManager.Instance.BrutalCompanyMinus; if (brutalCompanyMinus != null && brutalCompanyMinus.IsInstalled) { num = brutalCompanyMinus.ApplyBonusHp(num); } string text = $"ECDA_Health_{enemy.thisEnemyIndex}_{networkVarCounter++}"; enemyNetworkVarNames[instanceId] = text; Plugin.LogInfo($"Creating network variable {text} for enemy {enemyName} (ID: {instanceId})"); if (!enemyHealthVars.TryGetValue(instanceId, out var value2)) { try { value2 = LNetworkVariable.Create(text, num, (LNetworkVariableWritePerms)0, (Action)null); value2.OnValueChanged += delegate(float oldHealth, float newHealth) { HandleHealthChange(instanceId, newHealth); }; enemyHealthVars[instanceId] = value2; } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to create network variable " + text + ": " + ex.Message)); text = $"ECDA_Health_{enemy.thisEnemyIndex}_{networkVarCounter++}_Retry"; Plugin.LogInfo("Retrying with new variable name: " + text); enemyNetworkVarNames[instanceId] = text; value2 = LNetworkVariable.Create(text, num, (LNetworkVariableWritePerms)0, (Action)null); value2.OnValueChanged += delegate(float oldHealth, float newHealth) { HandleHealthChange(instanceId, newHealth); }; enemyHealthVars[instanceId] = value2; } } else { Plugin.LogInfo($"Using existing health variable for enemy {enemyName} (ID: {instanceId})"); } enemyMaxHealth[instanceId] = num; enemy.enemyType.canDie = true; enemy.enemyType.canBeDestroyed = true; enemy.enemyHP = 999; processedEnemies[instanceId] = true; immortalEnemies[instanceId] = false; try { EnemyHealthBarUI.Attach(enemy); } catch (Exception ex2) { Plugin.Log.LogWarning((object)("Failed to attach health bar UI to " + enemyName + ": " + ex2.Message)); } Plugin.LogInfo($"Setup enemy {enemyName} (ID: {instanceId}, NetID: {((NetworkBehaviour)enemy).NetworkObjectId}, Index: {enemy.thisEnemyIndex}) with {num} networked health"); } else { Plugin.LogInfo("Enemy " + enemyName + " is configured as immortal (Unimmortal=false, Enabled=true)"); enemy.enemyHP = 999; if (Plugin.ProtectImmortalEnemiesFromInstaKill.Value) { enemy.enemyType.canDie = false; Plugin.LogInfo("Protected immortal enemy " + enemyName + " from insta-kill effects (canDie = false)"); } else { enemy.enemyType.canDie = true; Plugin.LogInfo("Immortal enemy " + enemyName + " can still be killed by insta-kill effects (canDie = true)"); } immortalEnemies[instanceId] = true; processedEnemies[instanceId] = true; Plugin.LogInfo($"Set enemy {enemyName} (ID: {instanceId}) to be immortal with 999 HP"); } } catch (Exception ex3) { Plugin.Log.LogError((object)("Error setting up enemy: " + ex3.Message)); Plugin.Log.LogError((object)("Stack trace: " + ex3.StackTrace)); } } private static void HandleHealthChange(int instanceId, float newHealth) { EnemyAI val = FindEnemyById(instanceId); if ((Object)(object)val == (Object)null) { return; } Plugin.LogInfo($"Health changed for enemy {val.enemyType.enemyName} (ID: {instanceId}): new health = {newHealth}"); if (newHealth <= 0f && !val.isEnemyDead && ((NetworkBehaviour)StartOfRound.Instance).IsHost) { HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled) { PlayerControllerB lastDamageSource = hitmarker.GetLastDamageSource(instanceId); hitmarker.NotifyEnemyKilled(val, lastDamageSource); } KillEnemy(val); } } private static EnemyAI FindEnemyById(int instanceId) { if (enemyInstanceCache.TryGetValue(instanceId, out var value)) { if ((Object)(object)value != (Object)null) { return value; } enemyInstanceCache.Remove(instanceId); } EnemyAI[] array = Object.FindObjectsOfType(); foreach (EnemyAI val in array) { if (((Object)val).GetInstanceID() == instanceId) { enemyInstanceCache[instanceId] = val; return val; } } return null; } public static void ProcessHit(EnemyAI enemy, float damage, PlayerControllerB playerWhoHit) { if ((Object)(object)enemy == (Object)null || enemy.isEnemyDead) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!Plugin.IsModEnabledForEnemy(GetSanitizedName(enemy))) { Plugin.LogInfo("Mod disabled for enemy " + enemy.enemyType.enemyName + ", not processing hit"); return; } if (immortalEnemies.TryGetValue(instanceID, out var value) && value) { enemy.enemyHP = 999; Plugin.LogInfo("Refreshed immortal enemy " + enemy.enemyType.enemyName + " HP to 999"); return; } LethalHandsCompatibility lethalHands = ModCompatibilityManager.Instance.LethalHands; if (lethalHands != null && lethalHands.IsInstalled && damage == -22f) { damage = lethalHands.ConvertPunchForceToDamage(damage); Plugin.LogInfo($"Converted LethalHands punch to damage: {damage}"); } else if (damage < 0f) { Plugin.Log.LogWarning((object)$"Received negative damage value: {damage}, setting to 0"); damage = 0f; } if (damage <= 0f) { return; } if (((NetworkBehaviour)StartOfRound.Instance).IsHost) { Plugin.LogInfo($"Processing hit locally as host: Enemy {enemy.enemyType.enemyName}, Damage {damage}"); ProcessDamageDirectly(enemy, damage, playerWhoHit); return; } pendingDamage.TryGetValue(instanceID, out var value2); pendingDamage[instanceID] = value2 + damage; HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled && (Object)(object)playerWhoHit != (Object)null) { hitmarker.TrackDamageSource(instanceID, playerWhoHit); } if (Time.time - lastBatchTime >= 0.1f) { SendDamageBatch(); } } private static void SendDamageBatch() { if (pendingDamage.Count == 0) { return; } foreach (KeyValuePair item in pendingDamage) { EnemyAI val = FindEnemyById(item.Key); if (!((Object)(object)val != (Object)null) || val.isEnemyDead) { continue; } HitData hitData = default(HitData); hitData.EnemyInstanceId = item.Key; hitData.EnemyNetworkId = ((NetworkBehaviour)val).NetworkObjectId; hitData.EnemyIndex = val.thisEnemyIndex; hitData.EnemyName = val.enemyType.enemyName; hitData.Damage = item.Value; hitData.PlayerClientId = StartOfRound.Instance.localPlayerController?.actualClientId ?? 0; HitData hitData2 = hitData; try { if (hitMessage == null) { Plugin.LogWarning("Hit message is null, recreating it"); CreateNetworkMessages(); } hitMessage.SendServer(hitData2); Plugin.LogInfo($"Sent batched damage: {item.Value} to {val.enemyType.enemyName}"); } catch (Exception arg) { Plugin.LogError($"Error sending batched hit message: {arg}"); } } pendingDamage.Clear(); lastBatchTime = Time.time; } private static void ProcessDamageDirectly(EnemyAI enemy, float damage, PlayerControllerB playerWhoHit = null) { if ((Object)(object)enemy == (Object)null || enemy.isEnemyDead) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!Plugin.IsModEnabledForEnemy(GetSanitizedName(enemy))) { Plugin.LogInfo("Mod disabled for enemy " + enemy.enemyType.enemyName + ", not processing damage"); return; } if ((Object)(object)playerWhoHit != (Object)null) { HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled) { hitmarker.TrackDamageSource(instanceID, playerWhoHit); } } if (immortalEnemies.TryGetValue(instanceID, out var value) && value) { enemy.enemyHP = 999; Plugin.LogInfo("Refreshed immortal enemy " + enemy.enemyType.enemyName + " HP to 999"); return; } if (!processedEnemies.TryGetValue(instanceID, out var value2) || !value2) { SetupEnemy(enemy); } if (enemyHealthVars.TryGetValue(instanceID, out var value3)) { float value4 = value3.Value; float num = Mathf.Max(0f, value4 - damage); Plugin.LogInfo($"Enemy {enemy.enemyType.enemyName} damaged for {damage}: {value4} -> {num}"); value3.Value = num; } else { Plugin.Log.LogWarning((object)$"No health variable found for enemy {enemy.enemyType.enemyName} (ID: {instanceID})"); } } public static void NotifyClientsOfDestroy(int enemyIndex) { if (despawnMessage != null) { despawnMessage.SendClients(enemyIndex); } } private static void KillEnemy(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null || enemy.isEnemyDead) { return; } Plugin.LogInfo("Killing enemy " + enemy.enemyType.enemyName); SellBodiesCompatibility sellBodies = ModCompatibilityManager.Instance.SellBodies; if (sellBodies != null && sellBodies.IsInstalled && sellBodies.IsProblemEnemy(enemy.enemyType.enemyName)) { sellBodies.HandleProblemEnemyDeath(enemy); } if (!((NetworkBehaviour)enemy).IsOwner) { Plugin.LogInfo("Attempting to take ownership of " + enemy.enemyType.enemyName + " to kill it"); ulong actualClientId = StartOfRound.Instance.allPlayerScripts[0].actualClientId; enemy.ChangeOwnershipOfEnemy(actualClientId); } LastResortKillerCompatibility lastResortKiller = ModCompatibilityManager.Instance.LastResortKiller; if (lastResortKiller != null) { bool allowDespawn = DespawnConfiguration.Instance.ShouldDespawnEnemy(enemy.enemyType.enemyName); lastResortKiller.AttemptToKillEnemy(enemy, allowDespawn); } else { enemy.KillEnemyOnOwnerClient(false); if (enemy.enemyType.enemyName.Contains("Spring")) { Plugin.LogInfo("Using fallback kill method for " + enemy.enemyType.enemyName); enemy.KillEnemyOnOwnerClient(true); } } if (DespawnConfiguration.Instance.ShouldDespawnEnemy(enemy.enemyType.enemyName)) { StartDespawnProcess(enemy); } else if (Plugin.MuteDeadEnemies.Value) { Plugin.LogInfo("Starting audio fade for " + enemy.enemyType.enemyName); SilenceDeadEnemy(enemy); } else { Plugin.LogInfo($"Silence skipped: MuteDeadEnemies={Plugin.MuteDeadEnemies.Value}, Despawn={DespawnConfiguration.Instance.ShouldDespawnEnemy(enemy.enemyType.enemyName)}"); } } public static void SilenceDeadEnemy(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return; } try { if ((Object)(object)StartOfRound.Instance == (Object)null) { Plugin.Log.LogError((object)"SilenceDeadEnemy: StartOfRound.Instance is null"); } else { ((MonoBehaviour)StartOfRound.Instance).StartCoroutine(DelayedSilence(enemy)); } } catch (Exception ex) { Plugin.Log.LogError((object)("Failed to start DelayedSilence coroutine: " + ex.Message)); } } [IteratorStateMachine(typeof(d__32))] private static IEnumerator DelayedSilence(EnemyAI enemy) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__32(0) { enemy = enemy }; } [IteratorStateMachine(typeof(d__33))] private static IEnumerator FadeOutEnemyAudio(AudioSource[] sources, float duration) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__33(0) { sources = sources, duration = duration }; } private static void StartDespawnProcess(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!(enemiesInDespawnProcess.TryGetValue(instanceID, out var value) && value)) { enemiesInDespawnProcess[instanceID] = true; Plugin.LogInfo($"Starting despawn process for {enemy.enemyType.enemyName} (Index: {enemy.thisEnemyIndex})"); if ((Object)(object)StartOfRound.Instance != (Object)null) { ((MonoBehaviour)StartOfRound.Instance).StartCoroutine(WaitForDeathAnimationAndDespawn(enemy)); } } } [IteratorStateMachine(typeof(d__35))] private static IEnumerator WaitForDeathAnimationAndDespawn(EnemyAI enemy) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__35(0) { enemy = enemy }; } public static float GetEnemyHealth(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return 0f; } int instanceID = ((Object)enemy).GetInstanceID(); if (enemyHealthVars.TryGetValue(instanceID, out var value)) { return value.Value; } return 0f; } public static float GetEnemyMaxHealth(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return 0f; } int instanceID = ((Object)enemy).GetInstanceID(); if (enemyMaxHealth.TryGetValue(instanceID, out var value)) { return value; } return 0f; } public static bool IsEnemyTracked(EnemyAI enemy) { if ((Object)(object)enemy == (Object)null) { return false; } int instanceID = ((Object)enemy).GetInstanceID(); if (!enemyHealthVars.ContainsKey(instanceID)) { return immortalEnemies.ContainsKey(instanceID); } return true; } public static void DirectHealthChange(EnemyAI enemy, int damage, PlayerControllerB playerWhoHit = null) { if ((Object)(object)enemy == (Object)null || enemy.isEnemyDead || damage <= 0) { return; } int instanceID = ((Object)enemy).GetInstanceID(); if (!enemyHealthVars.ContainsKey(instanceID)) { Plugin.LogInfo("No network health variable found for " + enemy.enemyType.enemyName + ", probably set to enabled = false"); return; } HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled && (Object)(object)playerWhoHit != (Object)null) { hitmarker.TrackDamageSource(instanceID, playerWhoHit); } LNetworkVariable val = enemyHealthVars[instanceID]; float value = val.Value; float num = Mathf.Max(0f, value - (float)damage); Plugin.LogInfo($"DirectHealthChange: {enemy.enemyType.enemyName} damaged for {damage}: {value} -> {num}"); val.Value = num; } public static void CleanupExternallyKilledEnemy(EnemyAI enemy) { if (!((Object)(object)enemy == (Object)null)) { int instanceID = ((Object)enemy).GetInstanceID(); enemyHealthVars.Remove(instanceID); enemyMaxHealth.Remove(instanceID); processedEnemies.Remove(instanceID); if (enemyNetworkIds.TryGetValue(instanceID, out var value)) { enemyNetworkIds.Remove(instanceID); enemyByNetworkId.Remove(value); } enemyNetworkVarNames.Remove(instanceID); immortalEnemies.Remove(instanceID); enemyInstanceCache.Remove(instanceID); sanitizedEnemyNames.Remove(instanceID); Plugin.LogInfo("Cleaned up tracking data for externally killed enemy " + enemy.enemyType.enemyName); } } } public static class Patches { [CompilerGenerated] private sealed class d__10 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public EnemyAI enemy; private string 5__2; private float 5__3; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__10(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { 5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; if ((Object)(object)enemy == (Object)null) { return false; } 5__2 = enemy.enemyType.enemyName; 5__3 = 2f; goto IL_007e; case 1: <>1__state = -1; goto IL_007e; case 2: { <>1__state = -1; if ((Object)(object)enemy != (Object)null && enemy.isEnemyDead) { Plugin.LogInfo("Despawning spike-killed enemy: " + 5__2); if (enemy.thisEnemyIndex >= 0) { HealthManager.NotifyClientsOfDestroy(enemy.thisEnemyIndex); } Object.Destroy((Object)(object)((Component)enemy).gameObject); } break; } IL_007e: if (!enemy.isEnemyDead && 5__3 > 0f) { 5__3 -= Time.deltaTime; <>2__current = null; <>1__state = 1; return true; } if (enemy.isEnemyDead) { Plugin.LogInfo("Cleaning up spike-killed enemy: " + 5__2); HealthManager.CleanupExternallyKilledEnemy(enemy); int instanceID = ((Object)enemy).GetInstanceID(); HandleKillCompatibility(enemy, instanceID, 5__2); if (DespawnConfiguration.Instance.ShouldDespawnEnemy(5__2)) { <>2__current = (object)new WaitForSeconds(GetDespawnDelay()); <>1__state = 2; return true; } } break; } return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool isProcessingLandmineExplosion; public static void Initialize(Harmony harmony) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Expected O, but got Unknown //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Expected O, but got Unknown //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_01e2: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Expected O, but got Unknown //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0261: Expected O, but got Unknown //IL_0261: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(StartOfRound), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogError((object)"Could not find StartOfRound.Start method - patches failed!"); return; } MethodInfo methodInfo2 = AccessTools.Method(typeof(Patches), "StartOfRoundPostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo3 = AccessTools.Method(typeof(EnemyAI), "Start", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(typeof(Patches), "EnemyAIStartPostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo5 = AccessTools.Method(typeof(EnemyAI), "HitEnemyOnLocalClient", (Type[])null, (Type[])null); MethodInfo methodInfo6 = AccessTools.Method(typeof(Patches), "HitEnemyOnLocalClientPrefix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo5, new HarmonyMethod(methodInfo6), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo7 = AccessTools.Method(typeof(EnemyAI), "HitEnemy", (Type[])null, (Type[])null); MethodInfo methodInfo8 = AccessTools.Method(typeof(Patches), "HitEnemyPrefix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo7, new HarmonyMethod(methodInfo8), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo9 = AccessTools.Method(typeof(RoundManager), "FinishGeneratingLevel", (Type[])null, (Type[])null); MethodInfo methodInfo10 = AccessTools.Method(typeof(Patches), "FinishGeneratingLevelPostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo9, (HarmonyMethod)null, new HarmonyMethod(methodInfo10), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo11 = AccessTools.Method(typeof(StartOfRound), "ShipLeave", (Type[])null, (Type[])null); MethodInfo methodInfo12 = AccessTools.Method(typeof(Patches), "ShipLeavePostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo11, (HarmonyMethod)null, new HarmonyMethod(methodInfo12), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo13 = AccessTools.Method(typeof(SpikeRoofTrap), "OnTriggerStay", (Type[])null, (Type[])null); MethodInfo methodInfo14 = AccessTools.Method(typeof(Patches), "SpikeRoofTrapPrefix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo13, new HarmonyMethod(methodInfo14), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"SpikeRoofTrap.OnTriggerStay patched successfully"); MethodInfo methodInfo15 = AccessTools.Method(typeof(Landmine), "SpawnExplosion", (Type[])null, (Type[])null); MethodInfo methodInfo16 = AccessTools.Method(typeof(Patches), "SpawnExplosionPrefix", (Type[])null, (Type[])null); MethodInfo methodInfo17 = AccessTools.Method(typeof(Patches), "SpawnExplosionPostfix", (Type[])null, (Type[])null); harmony.Patch((MethodBase)methodInfo15, new HarmonyMethod(methodInfo16), new HarmonyMethod(methodInfo17), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Plugin.Log.LogInfo((object)"Landmine.SpawnExplosion patched successfully"); Plugin.Log.LogInfo((object)"All Harmony patches applied successfully"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error applying Harmony patches: " + ex.Message)); Plugin.Log.LogError((object)("Stack trace: " + ex.StackTrace)); } } public static void StartOfRoundPostfix(StartOfRound __instance) { try { Plugin.Log.LogInfo((object)"Game starting, initializing networked enemy health system..."); ((BaseUnityPlugin)Plugin.Instance).Config.Reload(); Plugin.InvalidateConfigKeyIndex(); EnemyControlConfiguration.Instance.ReloadConfig(); DespawnConfiguration.Instance.ReloadConfig(); Plugin.LogInfo("All configurations reloaded from files"); HealthManager.Initialize(); Plugin.enemies = new List(Resources.FindObjectsOfTypeAll()); Plugin.LogInfo($"Found {Plugin.enemies.Count} enemy types"); foreach (EnemyType enemy in Plugin.enemies) { if ((Object)(object)enemy == (Object)null || string.IsNullOrEmpty(enemy.enemyName)) { Plugin.Log.LogWarning((object)"Found null or invalid enemy type, skipping"); continue; } string mobName = Plugin.RemoveInvalidCharacters(enemy.enemyName).ToUpper(); Plugin.LogInfo("Processing enemy type: " + enemy.enemyName); Plugin.CanMob(".Unimmortal", mobName); float num = 3f; if ((Object)(object)enemy.enemyPrefab != (Object)null) { EnemyAI componentInChildren = enemy.enemyPrefab.GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { if (componentInChildren.enemyHP <= 0) { num = 1f; Plugin.LogInfo("Detected 0 or negative HP for " + enemy.enemyName + ", setting default to 1"); } else { num = Math.Min(componentInChildren.enemyHP, 30f); if (componentInChildren.enemyHP > 30) { Plugin.LogInfo($"Capped HP for {enemy.enemyName} from {componentInChildren.enemyHP} to {num}"); } } } } Plugin.GetMobHealth(mobName, num); Plugin.ShouldDespawn(mobName); } EnemyControlConfiguration.Instance.PreCreateEnemyConfigEntries(); ProcessExistingEnemies(); EnemyAliases.ScanAndPersistDisplayNames(); Plugin.Log.LogInfo((object)"StartOfRoundPostfix completed successfully"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error in StartOfRoundPostfix: " + ex.Message)); Plugin.Log.LogError((object)("Stack trace: " + ex.StackTrace)); } } private static void ProcessExistingEnemies() { try { EnemyAI[] array = Object.FindObjectsOfType(); Plugin.LogInfo($"Found {array.Length} active enemies"); EnemyAI[] array2 = array; foreach (EnemyAI val in array2) { if (!((Object)(object)val?.enemyType == (Object)null)) { Plugin.LogInfo("Setting up enemy: " + val.enemyType.enemyName); HealthManager.SetupEnemy(val); } } } catch (Exception ex) { Plugin.Log.LogError((object)("Error processing existing enemies: " + ex.Message)); Plugin.Log.LogError((object)("Stack trace: " + ex.StackTrace)); } } public static void EnemyAIStartPostfix(EnemyAI __instance) { try { if (!((Object)(object)__instance?.enemyType == (Object)null)) { HealthManager.SetupEnemy(__instance); } } catch (Exception ex) { Plugin.Log.LogError((object)("Error in EnemyAIStartPostfix: " + ex.Message)); } } [HarmonyPatch(typeof(Landmine), "SpawnExplosion")] [HarmonyPrefix] public static void SpawnExplosionPrefix() { isProcessingLandmineExplosion = true; } [HarmonyPatch(typeof(Landmine), "SpawnExplosion")] [HarmonyPostfix] public static void SpawnExplosionPostfix() { isProcessingLandmineExplosion = false; } public static bool HitEnemyOnLocalClientPrefix(EnemyAI __instance, int force, Vector3 hitDirection, PlayerControllerB playerWhoHit, bool playHitSFX, int hitID) { try { if ((Object)(object)__instance == (Object)null || __instance.isEnemyDead) { return true; } if ((Object)(object)__instance == (Object)null || __instance.isEnemyDead) { return true; } if (Plugin.ProtectOldBirdsFromOwnRockets.Value && __instance.enemyType.enemyName.Contains("RadMech") && (Object)(object)playerWhoHit == (Object)null && isProcessingLandmineExplosion) { Plugin.LogInfo($"Blocking Old Bird damage during landmine explosion processing (force: {force})"); return false; } Plugin.LogInfo(string.Format("Local hit detected on {0} from {1} with force {2}", __instance.enemyType.enemyName, playerWhoHit?.playerUsername ?? "unknown", force)); LethalHandsCompatibility lethalHands = ModCompatibilityManager.Instance.LethalHands; if (lethalHands != null && lethalHands.IsInstalled && force == -22) { Plugin.LogInfo($"Detected LethalHands punch with force {force}"); } HealthManager.ProcessHit(__instance, force, playerWhoHit); return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Error in HitEnemyOnLocalClientPrefix: " + ex.Message)); return true; } } public static bool HitEnemyPrefix(EnemyAI __instance, int force, PlayerControllerB playerWhoHit) { try { string enemyName = __instance.enemyType.enemyName; string sanitizedName = HealthManager.GetSanitizedName(__instance); if (Plugin.IsModEnabledForEnemy(sanitizedName) && !Plugin.CanMob(".Unimmortal", sanitizedName)) { __instance.enemyHP = 999; Plugin.LogInfo("HitEnemyPrefix: Refreshed immortal enemy " + enemyName + " HP to 999"); return true; } return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Error in HitEnemyPrefix: " + ex.Message)); return true; } } public static bool SpikeRoofTrapPrefix(SpikeRoofTrap __instance, Collider other) { try { if (!((Component)other).CompareTag("Enemy")) { return true; } EnemyAICollisionDetect component = ((Component)other).gameObject.GetComponent(); if ((Object)(object)component?.mainScript == (Object)null) { return true; } EnemyAI mainScript = component.mainScript; if ((Object)(object)mainScript == (Object)null || mainScript.isEnemyDead) { return true; } string enemyName = mainScript.enemyType.enemyName; if (!Plugin.IsModEnabledForEnemy(HealthManager.GetSanitizedName(mainScript))) { return true; } if (!Plugin.AllowSpikeTrapsToKillEnemies.Value) { Plugin.LogInfo("Spike trap blocked from killing " + enemyName + " due to global setting"); return false; } Plugin.LogInfo("Spike trap allowed to kill " + enemyName); if (DespawnConfiguration.Instance.ShouldDespawnEnemy(enemyName) && ((NetworkBehaviour)StartOfRound.Instance).IsHost) { ((MonoBehaviour)__instance).StartCoroutine(HandleSpikeKillCleanup(mainScript)); } return true; } catch (Exception ex) { Plugin.Log.LogError((object)("Error in SpikeRoofTrapPrefix: " + ex.Message)); return true; } } [IteratorStateMachine(typeof(d__10))] private static IEnumerator HandleSpikeKillCleanup(EnemyAI enemy) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__10(0) { enemy = enemy }; } private static float GetDespawnDelay() { SellBodiesCompatibility sellBodies = ModCompatibilityManager.Instance.SellBodies; if (sellBodies != null && sellBodies.IsInstalled) { return sellBodies.GetDespawnDelay(); } return 0.5f; } private static void HandleKillCompatibility(EnemyAI enemy, int instanceId, string enemyName) { SellBodiesCompatibility sellBodies = ModCompatibilityManager.Instance.SellBodies; if (sellBodies != null && sellBodies.IsInstalled && sellBodies.IsProblemEnemy(enemyName)) { sellBodies.HandleProblemEnemyDeath(enemy); } HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled) { PlayerControllerB lastDamageSource = hitmarker.GetLastDamageSource(instanceId); if ((Object)(object)lastDamageSource != (Object)null) { hitmarker.NotifyEnemyKilled(enemy, lastDamageSource); } } } [HarmonyPostfix] public static void ShipLeavePostfix() { try { HitmarkerCompatibility hitmarker = ModCompatibilityManager.Instance.Hitmarker; if (hitmarker != null && hitmarker.IsInstalled) { Plugin.LogInfo("Clearing Hitmarker compatibility data on level unload"); hitmarker.ClearTracking(); } } catch (Exception ex) { Plugin.Log.LogError((object)("Error in ShipLeavePostfix: " + ex.Message)); } } public static void FinishGeneratingLevelPostfix() { try { BrutalCompanyMinusCompatibility brutalCompanyMinus = ModCompatibilityManager.Instance.BrutalCompanyMinus; if (brutalCompanyMinus != null && brutalCompanyMinus.IsInstalled) { Plugin.LogInfo("Refreshing BrutalCompanyMinus compatibility data after level generation"); brutalCompanyMinus.RefreshBonusHp(); } } catch (Exception ex) { Plugin.Log.LogError((object)("Error in FinishGeneratingLevelPostfix: " + ex.Message)); } } } [BepInPlugin("nwnt.EverythingCanDieAlternative", "EverythingCanDieAlternative", "1.1.81")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BaseUnityPlugin { public static List enemies = new List(); private static bool _infoLogsEnabled = true; private static readonly Dictionary> boolConfigCache = new Dictionary>(); private static readonly Dictionary> floatConfigCache = new Dictionary>(); private static readonly Dictionary sanitizedNameCache = new Dictionary(); private static Dictionary _configKeyIndex = null; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } public static Harmony Harmony { get; private set; } public static ConfigEntry PatchCruiserDamage { get; private set; } public static ConfigEntry CruiserDamageAtHighSpeeds { get; private set; } public static ConfigEntry AllowSpikeTrapsToKillEnemies { get; private set; } public static ConfigEntry ProtectImmortalEnemiesFromInstaKill { get; private set; } public static ConfigEntry ProtectOldBirdsFromOwnRockets { get; private set; } public static ConfigEntry MuteDeadEnemies { get; private set; } public bool IsSellBodiesModDetected => IsModInstalled("Entity378.sellbodies"); public static void InvalidateConfigKeyIndex() { _configKeyIndex = null; } private static Dictionary GetConfigKeyIndex() { if (_configKeyIndex != null) { return _configKeyIndex; } Dictionary dictionary = new Dictionary(); foreach (ConfigDefinition key2 in ((BaseUnityPlugin)Instance).Config.Keys) { string key = RemoveInvalidCharacters(key2.Key.ToUpper()); dictionary[key] = key2; } _configKeyIndex = dictionary; return _configKeyIndex; } private void Awake() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown Instance = this; Log = ((BaseUnityPlugin)this).Logger; Harmony = new Harmony("nwnt.EverythingCanDieAlternative"); try { _ = UIConfiguration.Instance; if (UIConfiguration.Instance != null && UIConfiguration.Instance.IsInitialized) { _infoLogsEnabled = UIConfiguration.Instance.ShouldLogInfo(); } AllowSpikeTrapsToKillEnemies = ((BaseUnityPlugin)this).Config.Bind("Traps", "AllowSpikeTrapsToKillEnemies", true, "If true, spike roof traps can kill enemies. If false, spike traps will not affect enemies managed by this mod."); ProtectImmortalEnemiesFromInstaKill = ((BaseUnityPlugin)this).Config.Bind("General", "ProtectImmortalEnemiesFromInstaKill", true, "If true, enemies set as immortal will be protected from insta-kill effects by setting their canDie property to false."); ProtectOldBirdsFromOwnRockets = ((BaseUnityPlugin)this).Config.Bind("General", "ProtectOldBirdsFromOwnRockets", true, "If true, Old Birds will be protected from damage caused by their own rocket explosions."); MuteDeadEnemies = ((BaseUnityPlugin)this).Config.Bind("General", "MuteDeadEnemies", true, "If true, all audio sources on dead enemies are faded out and silenced when despawn is disabled."); _ = DespawnConfiguration.Instance; _ = EnemyControlConfiguration.Instance; ModCompatibilityManager.Instance.Initialize(); Patches.Initialize(Harmony); if (UIConfiguration.Instance.IsConfigMenuEnabled()) { ConfigMenuPatch.Initialize(Harmony); Log.LogInfo((object)"EverythingCanDieAlternative v1.1.81 is loaded with network support and configuration menu!"); } else { Log.LogInfo((object)"EverythingCanDieAlternative v1.1.81 is loaded with network support (configuration menu disabled)"); } } catch (Exception ex) { Log.LogError((object)("Error initializing EverythingCanDieAlternative: " + ex.Message)); Log.LogError((object)("Stack trace: " + ex.StackTrace)); } } public bool IsModInstalled(string modId) { return ModCompatibilityManager.Instance.IsModInstalled(modId); } public static bool IsModEnabledForEnemy(string mobName) { return EnemyControlConfiguration.Instance.IsModEnabledForEnemy(mobName); } public static string RemoveInvalidCharacters(string source) { if (string.IsNullOrEmpty(source)) { return string.Empty; } if (sanitizedNameCache.TryGetValue(source, out var value)) { return value; } StringBuilder stringBuilder = new StringBuilder(source.Length); foreach (char c in source) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) { stringBuilder.Append(c); } } string text = stringBuilder.ToString(); sanitizedNameCache[source] = text; return text; } public static void LogInfo(string message) { if (_infoLogsEnabled) { Log.LogInfo((object)message); } } public static void LogError(string message) { Log.LogError((object)message); } public static void LogWarning(string message) { Log.LogWarning((object)message); } public static void RefreshLoggingState() { if (UIConfiguration.Instance != null && UIConfiguration.Instance.IsInitialized) { _infoLogsEnabled = UIConfiguration.Instance.ShouldLogInfo(); LogInfo("Logging state refreshed: info logs " + (_infoLogsEnabled ? "enabled" : "disabled")); } } public static bool CanMob(string identifier, string mobName) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown try { string text = RemoveInvalidCharacters(mobName).ToUpper(); string text2 = text + identifier.ToUpper(); if (boolConfigCache.TryGetValue(text2, out var value)) { return value.Value; } ConfigEntry val = null; string key = RemoveInvalidCharacters(text2); if (GetConfigKeyIndex().TryGetValue(key, out var value2)) { val = (ConfigEntry)(object)((BaseUnityPlugin)Instance).Config[value2]; boolConfigCache[text2] = val; return val.Value; } val = ((BaseUnityPlugin)Instance).Config.Bind("Mobs", text + identifier, true, "If true, " + mobName + " will be damageable"); boolConfigCache[text2] = val; if (_configKeyIndex != null) { _configKeyIndex[key] = new ConfigDefinition("Mobs", text + identifier); } return val.Value; } catch (Exception ex) { Log.LogError((object)("Error in config check for mob " + mobName + ": " + ex.Message)); return false; } } public static float GetMobHealth(string mobName, float defaultHealth) { //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Expected O, but got Unknown try { if (defaultHealth <= 0f) { defaultHealth = 1f; LogInfo("Enforcing minimum health of 1 for " + mobName); } string text = RemoveInvalidCharacters(mobName).ToUpper(); string key = text + ".HEALTH"; if (floatConfigCache.TryGetValue(key, out var value)) { float num = value.Value; if (num <= 0f) { num = 1f; value.Value = 1f; } return num; } ConfigEntry val = null; string key2 = text + "HEALTH"; if (GetConfigKeyIndex().TryGetValue(key2, out var value2)) { val = (ConfigEntry)(object)((BaseUnityPlugin)Instance).Config[value2]; floatConfigCache[key] = val; float num2 = val.Value; if (num2 <= 0f) { num2 = 1f; val.Value = 1f; } return num2; } val = ((BaseUnityPlugin)Instance).Config.Bind("Mobs", text + ".Health", defaultHealth, "Health for " + mobName); floatConfigCache[key] = val; if (_configKeyIndex != null) { _configKeyIndex[key2] = new ConfigDefinition("Mobs", text + ".Health"); } return val.Value; } catch (Exception ex) { Log.LogError((object)("Error getting health for " + mobName + ": " + ex.Message)); return Math.Max(1f, defaultHealth); } } public static bool ShouldDespawn(string mobName) { return DespawnConfiguration.Instance.ShouldDespawnEnemy(mobName); } } public static class PluginInfo { public const string PLUGIN_GUID = "nwnt.EverythingCanDieAlternative"; public const string PLUGIN_NAME = "EverythingCanDieAlternative"; public const string PLUGIN_VERSION = "1.1.81"; } } namespace EverythingCanDieAlternative.UI { public class UIConfiguration { public enum HealthBarDisplayMode { Off, NumberOnly, BarOnly, Both } public enum HealthBarSize { Small, Medium, Large } public enum HealthBarVisibilityDistance { Close, Medium, Far } public static ConfigEntry EnableConfigMenu; public static ConfigEntry EnableInfoLogs; public static ConfigEntry ShowEnemyImages; public static ConfigEntry HealthBarMode; public static ConfigEntry HealthBarSizeOption; public static ConfigEntry HideHealthBarForFullHpEnemies; public static ConfigEntry HealthBarRange; private static UIConfiguration _instance; public static UIConfiguration Instance => _instance ?? (_instance = new UIConfiguration()); public bool IsInitialized { get; private set; } private UIConfiguration() { try { EnableConfigMenu = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "EnableConfigMenu", true, "If set to true, the config menu button will be shown in the main menu"); EnableInfoLogs = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "EnableInfoLogs", true, "If set to false, info logs will be suppressed to reduce console spam"); ShowEnemyImages = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "ShowEnemyImages", false, "If set to true, preview images for enemies will be shown in the config menu if available"); HealthBarMode = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "EnemyHealthBar", HealthBarDisplayMode.Off, "Show enemy health above damageable enemies. Off / NumberOnly / BarOnly / Both"); HealthBarSizeOption = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "EnemyHealthBarSize", HealthBarSize.Medium, "Size of the floating enemy health bar and number. Small / Medium / Large"); HideHealthBarForFullHpEnemies = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "HideHealthBarForFullHpEnemies", true, "If true, the floating health bar is only shown after the enemy has taken damage. Prevents giving away hiding enemies."); HealthBarRange = ((BaseUnityPlugin)Plugin.Instance).Config.Bind("General", "EnemyHealthBarRange", HealthBarVisibilityDistance.Close, "Maximum distance at which the floating health bar is visible. Close / Medium / Far"); IsInitialized = true; } catch (Exception ex) { Plugin.Log.LogError((object)("Error initializing UI configuration: " + ex.Message)); IsInitialized = false; } } public bool IsConfigMenuEnabled() { if (!IsInitialized || EnableConfigMenu == null) { return true; } return EnableConfigMenu.Value; } public bool ShouldLogInfo() { if (!IsInitialized || EnableInfoLogs == null) { return true; } return EnableInfoLogs.Value; } public bool ShouldShowEnemyImages() { if (!IsInitialized || ShowEnemyImages == null) { return false; } return ShowEnemyImages.Value; } public void SetInfoLogsEnabled(bool enabled) { if (IsInitialized && EnableInfoLogs != null) { EnableInfoLogs.Value = enabled; Plugin.Log.LogInfo((object)("Info logs " + (enabled ? "enabled" : "disabled"))); Plugin.RefreshLoggingState(); } } public void SetConfigMenuEnabled(bool enabled) { if (IsInitialized && EnableConfigMenu != null) { EnableConfigMenu.Value = enabled; Plugin.Log.LogInfo((object)("Config menu " + (enabled ? "enabled" : "disabled"))); } } public void SetShowEnemyImages(bool enabled) { if (IsInitialized && ShowEnemyImages != null) { ShowEnemyImages.Value = enabled; Plugin.Log.LogInfo((object)("Enemy images " + (enabled ? "enabled" : "disabled"))); } } public HealthBarDisplayMode GetHealthBarMode() { if (!IsInitialized || HealthBarMode == null) { return HealthBarDisplayMode.Off; } return HealthBarMode.Value; } public void SetHealthBarMode(HealthBarDisplayMode mode) { if (IsInitialized && HealthBarMode != null) { HealthBarMode.Value = mode; Plugin.Log.LogInfo((object)$"Enemy health bar mode set to {mode}"); } } public HealthBarSize GetHealthBarSize() { if (!IsInitialized || HealthBarSizeOption == null) { return HealthBarSize.Medium; } return HealthBarSizeOption.Value; } public void SetHealthBarSize(HealthBarSize size) { if (IsInitialized && HealthBarSizeOption != null) { HealthBarSizeOption.Value = size; Plugin.Log.LogInfo((object)$"Enemy health bar size set to {size}"); } } public bool ShouldHideHealthBarForFullHp() { if (!IsInitialized || HideHealthBarForFullHpEnemies == null) { return true; } return HideHealthBarForFullHpEnemies.Value; } public void SetHideHealthBarForFullHp(bool hide) { if (IsInitialized && HideHealthBarForFullHpEnemies != null) { HideHealthBarForFullHpEnemies.Value = hide; Plugin.Log.LogInfo((object)("Hide health bar for full HP enemies " + (hide ? "enabled" : "disabled"))); } } public HealthBarVisibilityDistance GetHealthBarRange() { if (!IsInitialized || HealthBarRange == null) { return HealthBarVisibilityDistance.Close; } return HealthBarRange.Value; } public void SetHealthBarRange(HealthBarVisibilityDistance range) { if (IsInitialized && HealthBarRange != null) { HealthBarRange.Value = range; Plugin.Log.LogInfo((object)$"Enemy health bar range set to {range}"); } } public void Save() { try { ((BaseUnityPlugin)Plugin.Instance).Config.Save(); ((BaseUnityPlugin)Plugin.Instance).Config.Reload(); EnemyControlConfiguration.Instance.ReloadConfig(); DespawnConfiguration.Instance.ReloadConfig(); Plugin.LogInfo("All UI settings saved and reloaded"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error saving UI settings: " + ex.Message)); } } public void ReloadAllConfigs() { try { ((BaseUnityPlugin)Plugin.Instance).Config.Reload(); EnemyControlConfiguration.Instance.ReloadConfig(); DespawnConfiguration.Instance.ReloadConfig(); Plugin.LogInfo("All configs reloaded from files"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error reloading configs: " + ex.Message)); } } } public static class ConfigBridge { private static readonly Regex sectionRegex = new Regex("^\\[(.*)\\]$", RegexOptions.Compiled); private static string pluginConfigPath = Path.Combine(Paths.ConfigPath, "nwnt.EverythingCanDieAlternative.cfg"); private static string enemyControlConfigPath = Path.Combine(Paths.ConfigPath, "nwnt.EverythingCanDieAlternative_Enemy_Control.cfg"); private static string despawnConfigPath = Path.Combine(Paths.ConfigPath, "nwnt.EverythingCanDieAlternative_Despawn_Rules.cfg"); private static Dictionary> cachedConfigEntries = new Dictionary>(); private static bool isLoading = false; public static List LoadAllEnemyConfigs() { if (isLoading) { return new List(); } isLoading = true; List list = new List(); try { cachedConfigEntries.Clear(); LogFileDetails(pluginConfigPath, "Main plugin config"); LogFileDetails(enemyControlConfigPath, "Enemy control config"); LogFileDetails(despawnConfigPath, "Despawn config"); ReadConfigFileDirectly(pluginConfigPath, "Mobs"); ReadConfigFileDirectly(enemyControlConfigPath, "Enemies"); ReadConfigFileDirectly(despawnConfigPath, "Enemies"); List allConfiguredEnemyNames = GetAllConfiguredEnemyNames(); if (allConfiguredEnemyNames.Count == 0) { Plugin.Log.LogWarning((object)"No configured enemies found in any config files. Start a round to generate configurations."); isLoading = false; return list; } Plugin.LogInfo($"Found {allConfiguredEnemyNames.Count} configured enemies in config files"); foreach (string item in allConfiguredEnemyNames) { bool isEnabled = false; bool canDie = true; bool shouldDespawn = true; float health = 3f; if (cachedConfigEntries.TryGetValue(item, out var value)) { if (value.TryGetValue(".ENABLED", out var value2) && value2 is bool flag) { isEnabled = flag; } if (value.TryGetValue(".UNIMMORTAL", out var value3) && value3 is bool flag2) { canDie = flag2; } if (value.TryGetValue(".DESPAWN", out var value4) && value4 is bool flag3) { shouldDespawn = flag3; } if (value.TryGetValue(".HEALTH", out var value5)) { float result; if (value5 is float num) { health = num; } else if (value5 is int num2) { health = num2; } else if (value5 is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) { health = result; } } } list.Add(new EnemyConfigData(item, isEnabled, canDie, shouldDespawn, health)); } Plugin.LogInfo($"Loaded {list.Count} enemy configurations successfully"); } catch (Exception arg) { Plugin.Log.LogError((object)$"Error loading enemy configurations: {arg}"); } finally { isLoading = false; } return list; } private static void LogFileDetails(string path, string description) { if (File.Exists(path)) { new FileInfo(path); } } private static void ReadConfigFileDirectly(string path, string targetSection) { if (!File.Exists(path)) { return; } try { string[] array = File.ReadAllLines(path); Plugin.LogInfo($"Reading config file {Path.GetFileName(path)} ({array.Length} lines)"); string text = ""; int num = 0; string[] array2 = array; for (int i = 0; i < array2.Length; i++) { string text2 = array2[i].Trim(); if (string.IsNullOrWhiteSpace(text2) || text2.StartsWith("#") || text2.StartsWith("//")) { continue; } Match match = sectionRegex.Match(text2); if (match.Success) { text = match.Groups[1].Value; } else if (!(text != targetSection)) { int num2 = text2.IndexOf('='); if (num2 > 0) { string key = text2.Substring(0, num2).Trim(); string value = text2.Substring(num2 + 1).Trim(); ProcessConfigEntry(text, key, value); num++; } } } Plugin.LogInfo($"Found {num} entries in section [{targetSection}]"); } catch (Exception ex) { Plugin.Log.LogError((object)("Error reading config file " + path + ": " + ex.Message)); } } private static void ProcessConfigEntry(string section, string key, string value) { string text = null; string text2 = null; if (section == "Mobs") { int num = key.LastIndexOf('.'); if (num > 0) { text = key.Substring(0, num).ToUpper(); text2 = key.Substring(num).ToUpper(); } } else if (section == "Enemies") { int num2 = key.LastIndexOf('.'); if (num2 > 0) { text = key.Substring(0, num2).ToUpper(); text2 = key.Substring(num2).ToUpper(); } } if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(text2)) { return; } if (!cachedConfigEntries.ContainsKey(text)) { cachedConfigEntries[text] = new Dictionary(); } switch (text2) { case ".ENABLED": case ".UNIMMORTAL": case ".DESPAWN": { if (bool.TryParse(value, out var result2)) { cachedConfigEntries[text][text2] = result2; } break; } case ".HEALTH": { if (float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { cachedConfigEntries[text][text2] = result; break; } cachedConfigEntries[text][text2] = value; Plugin.Log.LogWarning((object)("Could not parse health value " + value + " for " + text)); break; } } } private static List GetAllConfiguredEnemyNames() { return cachedConfigEntries.Keys.ToList(); } public static void SaveEnemyConfig(EnemyConfigData config) { if (config == null) { return; } string sanitizedName = config.SanitizedName; string name = config.Name; if (!cachedConfigEntries.ContainsKey(sanitizedName)) { Plugin.Log.LogWarning((object)("Cannot save config for " + name + " as no existing entries were found")); return; } try { if (cachedConfigEntries[sanitizedName].ContainsKey(".UNIMMORTAL") || cachedConfigEntries[sanitizedName].ContainsKey(".HEALTH")) { Dictionary dictionary = new Dictionary(); if (cachedConfigEntries[sanitizedName].ContainsKey(".UNIMMORTAL")) { dictionary[sanitizedName + ".Unimmortal"] = config.CanDie.ToString().ToLower(); } if (cachedConfigEntries[sanitizedName].ContainsKey(".HEALTH")) { dictionary[sanitizedName + ".Health"] = config.Health.ToString(CultureInfo.InvariantCulture); } UpdateConfigFileDirectly(pluginConfigPath, "Mobs", dictionary); } if (cachedConfigEntries[sanitizedName].ContainsKey(".ENABLED")) { UpdateConfigFileDirectly(enemyControlConfigPath, "Enemies", new Dictionary { { sanitizedName + ".Enabled", config.IsEnabled.ToString().ToLower() } }); } if (cachedConfigEntries[sanitizedName].ContainsKey(".DESPAWN")) { UpdateConfigFileDirectly(despawnConfigPath, "Enemies", new Dictionary { { sanitizedName + ".Despawn", config.ShouldDespawn.ToString().ToLower() } }); } cachedConfigEntries[sanitizedName][".ENABLED"] = config.IsEnabled; cachedConfigEntries[sanitizedName][".UNIMMORTAL"] = config.CanDie; cachedConfigEntries[sanitizedName][".HEALTH"] = config.Health; cachedConfigEntries[sanitizedName][".DESPAWN"] = config.ShouldDespawn; } catch (Exception ex) { Plugin.Log.LogError((object)("Error saving configuration: " + ex.Message)); } } private static bool UpdateConfigFileDirectly(string path, string section, Dictionary updates) { if (!File.Exists(path)) { return false; } try { string[] array = File.ReadAllLines(path); List list = new List(); string text = ""; bool flag = false; Dictionary dictionary = new Dictionary(); foreach (string key in updates.Keys) { dictionary[key.ToUpper()] = false; } string[] array2 = array; foreach (string text2 in array2) { string text3 = text2.Trim(); Match match = sectionRegex.Match(text3); if (match.Success) { text = match.Groups[1].Value; list.Add(text2); } else if (text == section) { bool flag2 = false; int num = text3.IndexOf('='); if (num > 0) { string text4 = text3.Substring(0, num).Trim(); foreach (string key2 in updates.Keys) { if (text4.Equals(key2, StringComparison.OrdinalIgnoreCase) || Plugin.RemoveInvalidCharacters(text4.ToUpper()) == Plugin.RemoveInvalidCharacters(key2.ToUpper())) { list.Add(text4 + " = " + updates[key2]); dictionary[key2.ToUpper()] = true; flag = true; flag2 = true; break; } } } if (!flag2) { list.Add(text2); } } else { list.Add(text2); } } bool flag3 = false; foreach (KeyValuePair item in dictionary) { if (!item.Value) { flag3 = true; break; } } if (flag3) { bool flag4 = false; for (int j = 0; j < list.Count; j++) { if (list[j].Trim() == "[" + section + "]") { flag4 = true; break; } } if (!flag4) { if (list.Count > 0 && !string.IsNullOrWhiteSpace(list[list.Count - 1])) { list.Add(""); } list.Add("[" + section + "]"); } foreach (KeyValuePair entry in dictionary) { if (!entry.Value) { string text5 = updates.Keys.First((string k) => k.ToUpper() == entry.Key); string text6 = updates[text5]; list.Add(text5 + " = " + text6); flag = true; } } } if (flag) { File.WriteAllLines(path, list); } return flag; } catch (Exception ex) { Plugin.Log.LogError((object)("Error updating config file " + path + ": " + ex.Message)); return false; } } } public class ConfigMenuManager : MonoBehaviour { private enum GlobalSettingsTab { Rules, HealthBar, BulkConfig } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static Comparison <>9__24_0; public static UnityAction <>9__34_0; public static UnityAction <>9__34_1; public static UnityAction <>9__34_2; public static UnityAction <>9__34_3; public static UnityAction <>9__42_0; public static UnityAction <>9__42_1; public static UnityAction <>9__42_2; public static UnityAction <>9__42_3; public static UnityAction <>9__43_0; public static UnityAction <>9__43_1; public static UnityAction <>9__43_2; public static UnityAction <>9__43_3; internal int b__24_0(EnemyConfigData a, EnemyConfigData b) { return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); } internal void b__34_0() { CloseConfigMenu(); } internal void b__34_1(bool isHideMenu) { PlayConfirmSFX(); UIConfiguration.Instance.SetConfigMenuEnabled(!isHideMenu); UIConfiguration.Instance.Save(); } internal void b__34_2(bool isLessLogs) { PlayConfirmSFX(); UIConfiguration.Instance.SetInfoLogsEnabled(!isLessLogs); UIConfiguration.Instance.Save(); } internal void b__34_3(bool showImages) { PlayConfirmSFX(); UIConfiguration.Instance.SetShowEnemyImages(showImages); UIConfiguration.Instance.Save(); ConfigMenuManager component = menuPrefab.GetComponent(); if ((Object)(object)component != (Object)null && !string.IsNullOrEmpty(component.selectedEnemyName)) { component.UpdateConfigPanel(); } } internal void b__42_0(bool allowSpikeTraps) { Plugin.AllowSpikeTrapsToKillEnemies.Value = allowSpikeTraps; ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); Plugin.LogInfo("Spike trap kills " + (allowSpikeTraps ? "enabled" : "disabled")); } internal void b__42_1(bool protectImmortals) { Plugin.ProtectImmortalEnemiesFromInstaKill.Value = protectImmortals; ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); Plugin.LogInfo("Immortal enemy insta-kill protection " + (protectImmortals ? "enabled" : "disabled")); } internal void b__42_2(bool protectOldBirds) { Plugin.ProtectOldBirdsFromOwnRockets.Value = protectOldBirds; ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); Plugin.LogInfo("Old Birds rocket self-damage protection " + (protectOldBirds ? "enabled" : "disabled")); } internal void b__42_3(bool mute) { Plugin.MuteDeadEnemies.Value = mute; ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); Plugin.LogInfo("Mute dead enemies " + (mute ? "enabled" : "disabled")); } internal void b__43_0(int newIndex) { UIConfiguration.Instance.SetHealthBarMode((UIConfiguration.HealthBarDisplayMode)newIndex); ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); } internal void b__43_1(int newIndex) { UIConfiguration.Instance.SetHealthBarSize((UIConfiguration.HealthBarSize)newIndex); ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); } internal void b__43_2(bool hide) { UIConfiguration.Instance.SetHideHealthBarForFullHp(hide); ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); } internal void b__43_3(int newIndex) { UIConfiguration.Instance.SetHealthBarRange((UIConfiguration.HealthBarVisibilityDistance)newIndex); ((BaseUnityPlugin)Plugin.Instance).Config.Save(); PlayConfirmSFX(); } } [CompilerGenerated] private sealed class d__60 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ConfigMenuManager <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__60(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown int num = <>1__state; ConfigMenuManager configMenuManager = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(0.2f); <>1__state = 1; return true; case 1: <>1__state = -1; if (configMenuManager.isShowingGlobalSettings) { configMenuManager.ShowGlobalSettings(); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class d__33 : IEnumerator, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ConfigMenuManager <>4__this; object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public d__33(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown int num = <>1__state; ConfigMenuManager configMenuManager = <>4__this; switch (num) { default: return false; case 0: <>1__state = -1; Plugin.LogInfo("Starting delayed refresh"); <>2__current = (object)new WaitForSeconds(0.1f); <>1__state = 1; return true; case 1: <>1__state = -1; configMenuManager.RefreshEnemyData(); refreshScheduled = false; Plugin.LogInfo("Delayed refresh completed"); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static GameObject menuPrefab; private GameObject menuPanel; private RectTransform enemyListContent; private GameObject enemyConfigPanel; private GameObject globalSettingsButton; private bool isShowingGlobalSettings; private GlobalSettingsTab selectedGlobalTab; private GameObject globalRulesTabButton; private GameObject globalHealthBarTabButton; private GameObject globalBulkTabButton; private GameObject globalSettingsContentRoot; private List enemyConfigs = new List(); private Dictionary enemyEntries = new Dictionary(); private Dictionary enemyConfigsByName = new Dictionary(); private readonly Stack enemyEntryPool = new Stack(); private const int INITIAL_POOL_SIZE = 20; private string selectedEnemyName; private const float ENTRY_HEIGHT = 35f; private static bool refreshScheduled; private TMP_InputField searchInputField; private string lastSearchText = ""; private static MenuManager _cachedMenuManager; private void ClearSearch() { if ((Object)(object)searchInputField != (Object)null) { searchInputField.text = ""; lastSearchText = ""; FilterEnemyList(lastSearchText); } } private void ScheduleRefresh() { if (!refreshScheduled) { refreshScheduled = true; ClearSearch(); ((MonoBehaviour)this).StartCoroutine(DelayedRefresh()); } } public void RefreshEnemyData() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown Plugin.LogInfo("RefreshEnemyData called"); foreach (Transform item in (Transform)enemyListContent) { Transform val = item; ((Component)val).gameObject.SetActive(false); enemyEntryPool.Push(((Component)val).gameObject); } enemyEntries.Clear(); enemyConfigs = ConfigBridge.LoadAllEnemyConfigs(); enemyConfigs.Sort((EnemyConfigData a, EnemyConfigData b) => string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase)); Plugin.LogInfo($"Sorted {enemyConfigs.Count} enemies alphabetically"); enemyConfigsByName.Clear(); foreach (EnemyConfigData enemyConfig in enemyConfigs) { enemyConfigsByName[enemyConfig.Name] = enemyConfig; } foreach (EnemyConfigData enemyConfig2 in enemyConfigs) { CreateEnemyListEntry(enemyConfig2); } FilterEnemyList(lastSearchText); selectedEnemyName = null; UpdateConfigPanel(); Plugin.LogInfo($"Loaded {enemyConfigs.Count} enemy configurations"); } private static MenuManager GetMenuManager() { if ((Object)(object)_cachedMenuManager == (Object)null) { _cachedMenuManager = Object.FindObjectOfType(); } return _cachedMenuManager; } private static void PlayConfirmSFX() { MenuManager menuManager = GetMenuManager(); if ((Object)(object)menuManager != (Object)null && (Object)(object)menuManager.MenuAudio != (Object)null) { menuManager.PlayConfirmSFX(); } } private static void PlayCancelSFX() { MenuManager menuManager = GetMenuManager(); if ((Object)(object)menuManager != (Object)null && (Object)(object)menuManager.MenuAudio != (Object)null) { menuManager.PlayCancelSFX(); } } public static void CloseConfigMenu(bool playSfx = true) { if (!((Object)(object)menuPrefab == (Object)null) && menuPrefab.activeSelf) { if (playSfx) { PlayCancelSFX(); } ConfigMenuManager component = menuPrefab.GetComponent(); if ((Object)(object)component != (Object)null) { component.ResetViewState(); } menuPrefab.SetActive(false); } } private void ResetViewState() { try { if (isShowingGlobalSettings) { isShowingGlobalSettings = false; UpdateGlobalSettingsButtonStyle(isActive: false); } selectedGlobalTab = GlobalSettingsTab.Rules; if (selectedEnemyName != null) { selectedEnemyName = null; } if ((Object)(object)enemyConfigPanel != (Object)null) { UpdateConfigPanel(); } } catch (Exception ex) { Plugin.LogError("Error resetting menu view state: " + ex.Message); } } private void Update() { Keyboard current = Keyboard.current; if (current != null && ((ButtonControl)current.escapeKey).wasPressedThisFrame) { CloseConfigMenu(); } } public static void ToggleConfigMenu() { if (!UIConfiguration.Instance.IsConfigMenuEnabled()) { Plugin.LogWarning("Attempted to open config menu, but it's disabled in settings"); return; } Plugin.LogInfo("ToggleConfigMenu called"); if ((Object)(object)menuPrefab == (Object)null) { CreateMenuPrefab(); } if (menuPrefab.activeSelf) { CloseConfigMenu(playSfx: false); return; } menuPrefab.SetActive(true); Plugin.LogInfo("Menu opened, scheduling refresh"); menuPrefab.GetComponent().ScheduleRefresh(); } [IteratorStateMachine(typeof(d__33))] private IEnumerator DelayedRefresh() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new d__33(0) { <>4__this = this }; } private static void CreateMenuPrefab() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01f0: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02c4: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03ad: Unknown result type (might be due to invalid IL or missing references) //IL_03c2: Unknown result type (might be due to invalid IL or missing references) //IL_03d6: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_052f: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Unknown result type (might be due to invalid IL or missing references) //IL_04bd: Unknown result type (might be due to invalid IL or missing references) //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_057b: Unknown result type (might be due to invalid IL or missing references) //IL_0590: Unknown result type (might be due to invalid IL or missing references) //IL_05a5: Unknown result type (might be due to invalid IL or missing references) //IL_05b9: Unknown result type (might be due to invalid IL or missing references) //IL_05d9: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_0610: Unknown result type (might be due to invalid IL or missing references) //IL_0625: Unknown result type (might be due to invalid IL or missing references) //IL_063a: Unknown result type (might be due to invalid IL or missing references) //IL_064e: Unknown result type (might be due to invalid IL or missing references) //IL_0750: Unknown result type (might be due to invalid IL or missing references) //IL_075a: Expected O, but got Unknown //IL_0694: Unknown result type (might be due to invalid IL or missing references) //IL_06a9: Unknown result type (might be due to invalid IL or missing references) //IL_06be: Unknown result type (might be due to invalid IL or missing references) //IL_06d3: Unknown result type (might be due to invalid IL or missing references) //IL_06e7: Unknown result type (might be due to invalid IL or missing references) //IL_0510: Unknown result type (might be due to invalid IL or missing references) //IL_08c7: Unknown result type (might be due to invalid IL or missing references) //IL_0788: Unknown result type (might be due to invalid IL or missing references) //IL_079d: Unknown result type (might be due to invalid IL or missing references) //IL_07b2: Unknown result type (might be due to invalid IL or missing references) //IL_07c7: Unknown result type (might be due to invalid IL or missing references) //IL_07db: Unknown result type (might be due to invalid IL or missing references) //IL_072e: Unknown result type (might be due to invalid IL or missing references) //IL_0901: Unknown result type (might be due to invalid IL or missing references) //IL_0917: Unknown result type (might be due to invalid IL or missing references) //IL_092d: Unknown result type (might be due to invalid IL or missing references) //IL_0943: Unknown result type (might be due to invalid IL or missing references) //IL_0959: Unknown result type (might be due to invalid IL or missing references) //IL_07fa: Unknown result type (might be due to invalid IL or missing references) //IL_07ff: Unknown result type (might be due to invalid IL or missing references) //IL_0817: Unknown result type (might be due to invalid IL or missing references) //IL_0837: Unknown result type (might be due to invalid IL or missing references) //IL_0857: Unknown result type (might be due to invalid IL or missing references) //IL_0863: Unknown result type (might be due to invalid IL or missing references) //IL_089f: Unknown result type (might be due to invalid IL or missing references) //IL_0a0f: Unknown result type (might be due to invalid IL or missing references) //IL_0a16: Expected O, but got Unknown //IL_0a3c: Unknown result type (might be due to invalid IL or missing references) //IL_0a51: Unknown result type (might be due to invalid IL or missing references) //IL_0a66: Unknown result type (might be due to invalid IL or missing references) //IL_0a7b: Unknown result type (might be due to invalid IL or missing references) //IL_0a8f: Unknown result type (might be due to invalid IL or missing references) //IL_0ab4: Unknown result type (might be due to invalid IL or missing references) //IL_0ac3: Unknown result type (might be due to invalid IL or missing references) //IL_0ac8: Unknown result type (might be due to invalid IL or missing references) //IL_0adb: Unknown result type (might be due to invalid IL or missing references) //IL_0aec: Unknown result type (might be due to invalid IL or missing references) //IL_0b01: Unknown result type (might be due to invalid IL or missing references) //IL_0b16: Unknown result type (might be due to invalid IL or missing references) //IL_0b2b: Unknown result type (might be due to invalid IL or missing references) //IL_0b3f: Unknown result type (might be due to invalid IL or missing references) //IL_0b79: Unknown result type (might be due to invalid IL or missing references) //IL_0b9b: Unknown result type (might be due to invalid IL or missing references) //IL_0ba0: Unknown result type (might be due to invalid IL or missing references) //IL_0bb3: Unknown result type (might be due to invalid IL or missing references) //IL_0bc4: Unknown result type (might be due to invalid IL or missing references) //IL_0bd9: Unknown result type (might be due to invalid IL or missing references) //IL_0bee: Unknown result type (might be due to invalid IL or missing references) //IL_0c02: Unknown result type (might be due to invalid IL or missing references) //IL_0c35: Unknown result type (might be due to invalid IL or missing references) //IL_0c50: Unknown result type (might be due to invalid IL or missing references) //IL_0c55: Unknown result type (might be due to invalid IL or missing references) //IL_0c68: Unknown result type (might be due to invalid IL or missing references) //IL_0c79: Unknown result type (might be due to invalid IL or missing references) //IL_0c8e: Unknown result type (might be due to invalid IL or missing references) //IL_0ca3: Unknown result type (might be due to invalid IL or missing references) //IL_0cb7: Unknown result type (might be due to invalid IL or missing references) //IL_0cf6: Unknown result type (might be due to invalid IL or missing references) //IL_0d5b: Unknown result type (might be due to invalid IL or missing references) //IL_0d71: Unknown result type (might be due to invalid IL or missing references) //IL_0d91: Unknown result type (might be due to invalid IL or missing references) //IL_0dc8: Unknown result type (might be due to invalid IL or missing references) //IL_0ddd: Unknown result type (might be due to invalid IL or missing references) //IL_0df2: Unknown result type (might be due to invalid IL or missing references) //IL_0e06: Unknown result type (might be due to invalid IL or missing references) //IL_0e9f: Unknown result type (might be due to invalid IL or missing references) //IL_0e56: Unknown result type (might be due to invalid IL or missing references) //IL_0e6b: Unknown result type (might be due to invalid IL or missing references) //IL_0e76: Unknown result type (might be due to invalid IL or missing references) //IL_0e80: Unknown result type (might be due to invalid IL or missing references) //IL_0eca: Unknown result type (might be due to invalid IL or missing references) //IL_0edf: Unknown result type (might be due to invalid IL or missing references) //IL_0ef4: Unknown result type (might be due to invalid IL or missing references) //IL_0f08: Unknown result type (might be due to invalid IL or missing references) //IL_0f3b: Unknown result type (might be due to invalid IL or missing references) //IL_0fa1: Unknown result type (might be due to invalid IL or missing references) try { Plugin.LogInfo("Creating menu prefab"); menuPrefab = new GameObject("ECDAConfigMenu"); ConfigMenuManager menuManager = menuPrefab.AddComponent(); menuPrefab.SetActive(false); Object.DontDestroyOnLoad((Object)(object)menuPrefab); Canvas obj = menuPrefab.AddComponent(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 1000; CanvasScaler obj2 = menuPrefab.AddComponent(); obj2.uiScaleMode = (ScaleMode)1; obj2.referenceResolution = new Vector2(1920f, 1080f); menuPrefab.AddComponent(); GameObject val = UIHelper.CreatePanel(menuPrefab.transform, "Overlay", new Vector2(2000f, 2000f)); if ((Object)(object)val == (Object)null) { Plugin.LogError("Failed to create overlay panel"); return; } Image component = val.GetComponent(); if ((Object)(object)component != (Object)null) { ((Graphic)component).color = new Color(0f, 0f, 0f, 0.7f); } GameObject val2 = UIHelper.CreatePanel(menuPrefab.transform, "MainPanel", new Vector2(800f, 600f)); if ((Object)(object)val2 == (Object)null) { Plugin.LogError("Failed to create main panel"); return; } menuManager.menuPanel = val2; Image component2 = val2.GetComponent(); if ((Object)(object)component2 != (Object)null) { ((Graphic)component2).color = new Color(0.1f, 0.1f, 0.1f, 0.95f); } Transform transform = val2.transform; object obj3 = <>c.<>9__34_0; if (obj3 == null) { UnityAction val3 = delegate { CloseConfigMenu(); }; <>c.<>9__34_0 = val3; obj3 = (object)val3; } GameObject val4 = UIHelper.CreateButton(transform, "CloseButton", "X", (UnityAction)obj3); if ((Object)(object)val4 != (Object)null) { RectTransform component3 = val4.GetComponent(); component3.anchorMin = new Vector2(1f, 1f); component3.anchorMax = new Vector2(1f, 1f); component3.pivot = new Vector2(1f, 1f); component3.sizeDelta = new Vector2(80f, 40f); component3.anchoredPosition = new Vector2(-10f, -10f); } GameObject val5 = UIHelper.CreateYesNoSelector(val2.transform, "HideMenuSelector", "Hide Menu:", !UIConfiguration.Instance.IsConfigMenuEnabled(), delegate(bool isHideMenu) { PlayConfirmSFX(); UIConfiguration.Instance.SetConfigMenuEnabled(!isHideMenu); UIConfiguration.Instance.Save(); }); if ((Object)(object)val5 != (Object)null) { RectTransform component4 = val5.GetComponent(); component4.anchorMin = new Vector2(0f, 1f); component4.anchorMax = new Vector2(0f, 1f); component4.pivot = new Vector2(0f, 1f); component4.sizeDelta = new Vector2(160f, 30f); component4.anchoredPosition = new Vector2(40f, -15f); Transform obj4 = val5.transform.Find("Label"); RectTransform val6 = ((obj4 != null) ? ((Component)obj4).GetComponent() : null); if ((Object)(object)val6 != (Object)null) { val6.sizeDelta = new Vector2(80f, 30f); } } GameObject val7 = UIHelper.CreateYesNoSelector(val2.transform, "LessLogsSelector", "Less Logs:", !UIConfiguration.Instance.ShouldLogInfo(), delegate(bool isLessLogs) { PlayConfirmSFX(); UIConfiguration.Instance.SetInfoLogsEnabled(!isLessLogs); UIConfiguration.Instance.Save(); }); if ((Object)(object)val7 != (Object)null) { RectTransform component5 = val7.GetComponent(); component5.anchorMin = new Vector2(0f, 1f); component5.anchorMax = new Vector2(0f, 1f); component5.pivot = new Vector2(0f, 1f); component5.sizeDelta = new Vector2(150f, 30f); component5.anchoredPosition = new Vector2(260f, -15f); Transform obj5 = val7.transform.Find("Label"); RectTransform val8 = ((obj5 != null) ? ((Component)obj5).GetComponent() : null); if ((Object)(object)val8 != (Object)null) { val8.sizeDelta = new Vector2(70f, 30f); } } GameObject val9 = UIHelper.CreateYesNoSelector(val2.transform, "ShowImagesSelector", "Show Images:", UIConfiguration.Instance.ShouldShowEnemyImages(), delegate(bool showImages) { PlayConfirmSFX(); UIConfiguration.Instance.SetShowEnemyImages(showImages); UIConfiguration.Instance.Save(); ConfigMenuManager component19 = menuPrefab.GetComponent(); if ((Object)(object)component19 != (Object)null && !string.IsNullOrEmpty(component19.selectedEnemyName)) { component19.UpdateConfigPanel(); } }); if ((Object)(object)val9 != (Object)null) { RectTransform component6 = val9.GetComponent(); component6.anchorMin = new Vector2(0f, 1f); component6.anchorMax = new Vector2(0f, 1f); component6.pivot = new Vector2(0f, 1f); component6.sizeDelta = new Vector2(170f, 30f); component6.anchoredPosition = new Vector2(470f, -15f); Transform obj6 = val9.transform.Find("Label"); RectTransform val10 = ((obj6 != null) ? ((Component)obj6).GetComponent() : null); if ((Object)(object)val10 != (Object)null) { val10.sizeDelta = new Vector2(100f, 30f); } } GameObject val11 = UIHelper.CreatePanel(val2.transform, "ContentPanel", new Vector2(780f, 520f)); if ((Object)(object)val11 == (Object)null) { Plugin.LogError("Failed to create content panel"); return; } RectTransform component7 = val11.GetComponent(); component7.anchorMin = new Vector2(0f, 0f); component7.anchorMax = new Vector2(1f, 1f); component7.pivot = new Vector2(0.5f, 0.5f); component7.offsetMin = new Vector2(10f, 60f); component7.offsetMax = new Vector2(-10f, -50f); GameObject val12 = UIHelper.CreatePanel(val11.transform, "EnemyListPanel", new Vector2(250f, 520f)); if ((Object)(object)val12 == (Object)null) { Plugin.LogError("Failed to create enemy list panel"); return; } RectTransform component8 = val12.GetComponent(); component8.anchorMin = new Vector2(0f, 0f); component8.anchorMax = new Vector2(0f, 1f); component8.pivot = new Vector2(0f, 0.5f); component8.sizeDelta = new Vector2(250f, 0f); GameObject val13 = UIHelper.CreateText(val12.transform, "ListLabel", "ENEMIES", (TextAlignmentOptions)514); if ((Object)(object)val13 != (Object)null) { RectTransform component9 = val13.GetComponent(); component9.anchorMin = new Vector2(0f, 1f); component9.anchorMax = new Vector2(1f, 1f); component9.pivot = new Vector2(0.5f, 1f); component9.sizeDelta = new Vector2(0f, 30f); component9.anchoredPosition = new Vector2(0f, -5f); TextMeshProUGUI component10 = val13.GetComponent(); if ((Object)(object)component10 != (Object)null) { ((TMP_Text)component10).fontSize = 16f; ((TMP_Text)component10).fontStyle = (FontStyles)1; ((Graphic)component10).color = new Color(1f, 0.9f, 0.5f, 1f); } } GameObject val14 = UIHelper.CreateButton(val12.transform, "GlobalSettingsButton", "Global Settings", (UnityAction)delegate { PlayConfirmSFX(); menuManager.ToggleGlobalSettings(); }); if ((Object)(object)val14 != (Object)null) { menuManager.globalSettingsButton = val14; RectTransform component11 = val14.GetComponent(); component11.anchorMin = new Vector2(0f, 1f); component11.anchorMax = new Vector2(1f, 1f); component11.pivot = new Vector2(0.5f, 1f); component11.sizeDelta = new Vector2(-20f, 35f); component11.anchoredPosition = new Vector2(0f, -40f); Button component12 = val14.GetComponent