Decompiled source of EverythingCanDieAlternative v1.1.1

nwnt.EverythingCanDieAlternative.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using GameNetcodeStuff;
using HarmonyLib;
using LethalNetworkAPI;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("EverythingCanDieAlternative")]
[assembly: AssemblyFileVersion("1.1.0")]
[assembly: AssemblyDescription("A mod that makes everything in Lethal Company damageable")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.1.0.0")]
namespace EverythingCanDieAlternative;

public static class NetworkedHealthManager
{
	[Serializable]
	public struct HitData
	{
		public int EnemyInstanceId;

		public ulong EnemyNetworkId;

		public int EnemyIndex;

		public string EnemyName;

		public int Damage;

		public override string ToString()
		{
			return $"HitData(EnemyId={EnemyInstanceId}, NetworkId={EnemyNetworkId}, Index={EnemyIndex}, Name={EnemyName}, Damage={Damage})";
		}
	}

	private static int networkVarCounter = 0;

	private static readonly Dictionary<int, LNetworkVariable<int>> enemyHealthVars = new Dictionary<int, LNetworkVariable<int>>();

	private static readonly Dictionary<int, int> enemyMaxHealth = new Dictionary<int, int>();

	private static readonly Dictionary<int, bool> processedEnemies = new Dictionary<int, bool>();

	private static readonly Dictionary<int, ulong> enemyNetworkIds = new Dictionary<int, ulong>();

	private static readonly Dictionary<int, string> enemyNetworkVarNames = new Dictionary<int, string>();

	private static readonly int DamageAnimTrigger = Animator.StringToHash("damage");

	private static LNetworkMessage<HitData> hitMessage;

	public static void Initialize()
	{
		enemyHealthVars.Clear();
		enemyMaxHealth.Clear();
		processedEnemies.Clear();
		enemyNetworkIds.Clear();
		enemyNetworkVarNames.Clear();
		networkVarCounter = 0;
		CreateNetworkMessage();
		Plugin.Log.LogInfo((object)"Networked Health Manager initialized");
	}

	private static void CreateNetworkMessage()
	{
		try
		{
			hitMessage = LNetworkMessage<HitData>.Create("ECD_HitMessage", (Action<HitData, ulong>)delegate(HitData hitData, ulong clientId)
			{
				Plugin.Log.LogInfo((object)$"[HOST] Received hit message from client {clientId}: {hitData}");
				if (((NetworkBehaviour)StartOfRound.Instance).IsHost)
				{
					EnemyAI val = FindEnemyMultiMethod(hitData);
					if ((Object)(object)val != (Object)null && !val.isEnemyDead)
					{
						ProcessDamageDirectly(val, hitData.Damage);
					}
					else
					{
						Plugin.Log.LogWarning((object)$"Could not find enemy: {hitData.EnemyName} (NetworkID: {hitData.EnemyNetworkId}, Index: {hitData.EnemyIndex})");
					}
				}
			}, (Action<HitData>)null, (Action<HitData, ulong>)null);
			Plugin.Log.LogInfo((object)"Network message created successfully");
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error creating network message: {arg}");
		}
	}

	private static EnemyAI FindEnemyMultiMethod(HitData hitData)
	{
		if (hitData.EnemyIndex >= 0)
		{
			foreach (EnemyAI spawnedEnemy in RoundManager.Instance.SpawnedEnemies)
			{
				if (spawnedEnemy.thisEnemyIndex == hitData.EnemyIndex)
				{
					Plugin.Log.LogInfo((object)$"Found enemy by index: {hitData.EnemyIndex}");
					return spawnedEnemy;
				}
			}
		}
		if (hitData.EnemyNetworkId != 0)
		{
			EnemyAI[] array = Object.FindObjectsOfType<EnemyAI>();
			foreach (EnemyAI val in array)
			{
				if (((NetworkBehaviour)val).NetworkObjectId == hitData.EnemyNetworkId)
				{
					Plugin.Log.LogInfo((object)$"Found enemy by NetworkObjectId: {hitData.EnemyNetworkId}");
					return val;
				}
			}
		}
		if (!string.IsNullOrEmpty(hitData.EnemyName))
		{
			EnemyAI[] array2 = Object.FindObjectsOfType<EnemyAI>();
			foreach (EnemyAI val2 in array2)
			{
				if (val2.enemyType.enemyName == hitData.EnemyName)
				{
					Plugin.Log.LogInfo((object)("Found enemy by name: " + hitData.EnemyName));
					return val2;
				}
			}
		}
		Plugin.Log.LogWarning((object)"Could not find enemy. All enemies in scene:");
		EnemyAI[] array3 = Object.FindObjectsOfType<EnemyAI>();
		foreach (EnemyAI val3 in array3)
		{
			Plugin.Log.LogWarning((object)$"  - {val3.enemyType.enemyName}, Index: {val3.thisEnemyIndex}, NetworkId: {((NetworkBehaviour)val3).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();
			if ((Object)(object)((NetworkBehaviour)enemy).NetworkObject != (Object)null)
			{
				enemyNetworkIds[instanceId] = ((NetworkBehaviour)enemy).NetworkObjectId;
			}
			if (processedEnemies.ContainsKey(instanceId) && processedEnemies[instanceId])
			{
				Plugin.Log.LogInfo((object)$"Enemy {enemy.enemyType.enemyName} (ID: {instanceId}) already processed, skipping setup");
				return;
			}
			string enemyName = enemy.enemyType.enemyName;
			string mobName = Plugin.RemoveInvalidCharacters(enemyName).ToUpper();
			if (Plugin.CanMob(".Unimmortal", mobName))
			{
				int mobHealth = Plugin.GetMobHealth(mobName, enemy.enemyHP);
				string text = $"ECD_Health_{enemy.thisEnemyIndex}_{networkVarCounter++}";
				enemyNetworkVarNames[instanceId] = text;
				Plugin.Log.LogInfo((object)$"Creating network variable {text} for enemy {enemyName} (ID: {instanceId})");
				if (!enemyHealthVars.ContainsKey(instanceId))
				{
					try
					{
						LNetworkVariable<int> val = LNetworkVariable<int>.Create(text, mobHealth, (LNetworkVariableWritePerms)0, (Action<int, int>)null);
						val.OnValueChanged += delegate(int oldHealth, int newHealth)
						{
							HandleHealthChange(instanceId, newHealth);
						};
						enemyHealthVars[instanceId] = val;
					}
					catch (Exception ex)
					{
						Plugin.Log.LogError((object)("Failed to create network variable " + text + ": " + ex.Message));
						text = $"ECD_Health_{enemy.thisEnemyIndex}_{networkVarCounter++}_Retry";
						Plugin.Log.LogInfo((object)("Retrying with new variable name: " + text));
						enemyNetworkVarNames[instanceId] = text;
						LNetworkVariable<int> val = LNetworkVariable<int>.Create(text, mobHealth, (LNetworkVariableWritePerms)0, (Action<int, int>)null);
						val.OnValueChanged += delegate(int oldHealth, int newHealth)
						{
							HandleHealthChange(instanceId, newHealth);
						};
						enemyHealthVars[instanceId] = val;
					}
				}
				else
				{
					LNetworkVariable<int> val = enemyHealthVars[instanceId];
					Plugin.Log.LogInfo((object)$"Using existing health variable for enemy {enemyName} (ID: {instanceId})");
				}
				enemyMaxHealth[instanceId] = mobHealth;
				enemy.enemyType.canDie = true;
				enemy.enemyType.canBeDestroyed = true;
				enemy.enemyHP = 999;
				processedEnemies[instanceId] = true;
				Plugin.Log.LogInfo((object)$"Setup enemy {enemyName} (ID: {instanceId}, NetID: {((NetworkBehaviour)enemy).NetworkObjectId}, Index: {enemy.thisEnemyIndex}) with {mobHealth} networked health");
			}
			else
			{
				Plugin.Log.LogInfo((object)("Enemy " + enemyName + " is not configured to be damageable"));
			}
		}
		catch (Exception ex2)
		{
			Plugin.Log.LogError((object)("Error setting up enemy: " + ex2.Message));
			Plugin.Log.LogError((object)("Stack trace: " + ex2.StackTrace));
		}
	}

	private static void HandleHealthChange(int instanceId, int newHealth)
	{
		EnemyAI val = FindEnemyById(instanceId);
		if (!((Object)(object)val == (Object)null))
		{
			Plugin.Log.LogInfo((object)$"Health changed for enemy {val.enemyType.enemyName} (ID: {instanceId}): new health = {newHealth}");
			if (newHealth <= 0 && !val.isEnemyDead && ((NetworkBehaviour)StartOfRound.Instance).IsHost)
			{
				KillEnemy(val);
			}
		}
	}

	private static EnemyAI FindEnemyById(int instanceId)
	{
		EnemyAI[] array = Object.FindObjectsOfType<EnemyAI>();
		EnemyAI[] array2 = array;
		foreach (EnemyAI val in array2)
		{
			if (((Object)val).GetInstanceID() == instanceId)
			{
				return val;
			}
		}
		return null;
	}

	public static void ProcessHit(EnemyAI enemy, int damage, PlayerControllerB playerWhoHit)
	{
		if ((Object)(object)enemy == (Object)null || enemy.isEnemyDead)
		{
			return;
		}
		int instanceID = ((Object)enemy).GetInstanceID();
		if (((NetworkBehaviour)StartOfRound.Instance).IsHost)
		{
			Plugin.Log.LogInfo((object)$"Processing hit locally as host: Enemy {enemy.enemyType.enemyName}, Damage {damage}");
			ProcessDamageDirectly(enemy, damage);
			return;
		}
		HitData hitData = default(HitData);
		hitData.EnemyInstanceId = instanceID;
		hitData.EnemyNetworkId = ((NetworkBehaviour)enemy).NetworkObjectId;
		hitData.EnemyIndex = enemy.thisEnemyIndex;
		hitData.EnemyName = enemy.enemyType.enemyName;
		hitData.Damage = damage;
		HitData hitData2 = hitData;
		try
		{
			if (hitMessage == null)
			{
				Plugin.Log.LogWarning((object)"Hit message is null, recreating it");
				CreateNetworkMessage();
			}
			hitMessage.SendServer(hitData2);
			Plugin.Log.LogInfo((object)$"Sent hit message to server: Enemy {enemy.enemyType.enemyName}, Damage {damage}, Index {enemy.thisEnemyIndex}");
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error sending hit message: {arg}");
		}
	}

	private static void ProcessDamageDirectly(EnemyAI enemy, int damage)
	{
		if (!((Object)(object)enemy == (Object)null) && !enemy.isEnemyDead)
		{
			int instanceID = ((Object)enemy).GetInstanceID();
			if (!processedEnemies.ContainsKey(instanceID) || !processedEnemies[instanceID])
			{
				SetupEnemy(enemy);
			}
			if (enemyHealthVars.TryGetValue(instanceID, out var value))
			{
				int value2 = value.Value;
				int num = Mathf.Max(0, value2 - damage);
				Plugin.Log.LogInfo((object)$"Enemy {enemy.enemyType.enemyName} damaged for {damage}: {value2} -> {num}");
				value.Value = num;
			}
			else
			{
				Plugin.Log.LogWarning((object)$"No health variable found for enemy {enemy.enemyType.enemyName} (ID: {instanceID})");
			}
		}
	}

	private static void KillEnemy(EnemyAI enemy)
	{
		if (!((Object)(object)enemy == (Object)null) && !enemy.isEnemyDead)
		{
			Plugin.Log.LogInfo((object)("Killing enemy " + enemy.enemyType.enemyName));
			if (!((NetworkBehaviour)enemy).IsOwner)
			{
				Plugin.Log.LogInfo((object)("Attempting to take ownership of " + enemy.enemyType.enemyName + " to kill it"));
				ulong actualClientId = StartOfRound.Instance.allPlayerScripts[0].actualClientId;
				enemy.ChangeOwnershipOfEnemy(actualClientId);
			}
			enemy.KillEnemyOnOwnerClient(false);
			if (enemy.enemyType.enemyName.Contains("Spring"))
			{
				Plugin.Log.LogInfo((object)("Using fallback kill method for " + enemy.enemyType.enemyName));
				enemy.KillEnemyOnOwnerClient(true);
			}
		}
	}

	public static int GetEnemyHealth(EnemyAI enemy)
	{
		if ((Object)(object)enemy == (Object)null)
		{
			return 0;
		}
		int instanceID = ((Object)enemy).GetInstanceID();
		if (enemyHealthVars.TryGetValue(instanceID, out var value))
		{
			return value.Value;
		}
		return 0;
	}

	public static int GetEnemyMaxHealth(EnemyAI enemy)
	{
		if ((Object)(object)enemy == (Object)null)
		{
			return 0;
		}
		int instanceID = ((Object)enemy).GetInstanceID();
		if (enemyMaxHealth.TryGetValue(instanceID, out var value))
		{
			return value;
		}
		return 0;
	}
}
public static class Patches
{
	public static void Initialize(Harmony harmony)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Expected O, but got Unknown
		//IL_0074: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Expected O, but got Unknown
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: Expected O, but got Unknown
		try
		{
			MethodInfo methodInfo = AccessTools.Method(typeof(StartOfRound), "Start", (Type[])null, (Type[])null);
			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);
			Plugin.Log.LogInfo((object)"Harmony patches applied successfully");
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error applying Harmony patches: {arg}");
		}
	}

	public static void StartOfRoundPostfix(StartOfRound __instance)
	{
		try
		{
			Plugin.Log.LogInfo((object)"Game starting, initializing networked enemy health system...");
			NetworkedHealthManager.Initialize();
			Plugin.enemies = new List<EnemyType>(Resources.FindObjectsOfTypeAll<EnemyType>());
			Plugin.Log.LogInfo((object)$"Found {Plugin.enemies.Count} enemy types");
			foreach (EnemyType enemy in Plugin.enemies)
			{
				string mobName = Plugin.RemoveInvalidCharacters(enemy.enemyName).ToUpper();
				Plugin.CanMob(".Unimmortal", mobName);
				Plugin.GetMobHealth(mobName, 3);
			}
			ProcessExistingEnemies();
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error in StartOfRoundPostfix: {arg}");
		}
	}

	private static void ProcessExistingEnemies()
	{
		try
		{
			EnemyAI[] array = Object.FindObjectsOfType<EnemyAI>();
			Plugin.Log.LogInfo((object)$"Found {array.Length} active enemies");
			EnemyAI[] array2 = array;
			foreach (EnemyAI val in array2)
			{
				if (!((Object)(object)val?.enemyType == (Object)null))
				{
					NetworkedHealthManager.SetupEnemy(val);
				}
			}
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error processing existing enemies: {arg}");
		}
	}

	public static void EnemyAIStartPostfix(EnemyAI __instance)
	{
		try
		{
			if (!((Object)(object)__instance?.enemyType == (Object)null))
			{
				NetworkedHealthManager.SetupEnemy(__instance);
			}
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error in EnemyAIStartPostfix: {arg}");
		}
	}

	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;
			}
			Plugin.Log.LogInfo((object)string.Format("Local hit detected on {0} from {1} with force {2}", __instance.enemyType.enemyName, playerWhoHit?.playerUsername ?? "unknown", force));
			NetworkedHealthManager.ProcessHit(__instance, force, playerWhoHit);
			return true;
		}
		catch (Exception arg)
		{
			Plugin.Log.LogError((object)$"Error in HitEnemyOnLocalClientPrefix: {arg}");
			return true;
		}
	}
}
[BepInPlugin("nwnt.EverythingCanDieAlternative", "EverythingCanDieAlternative", "1.1.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class Plugin : BaseUnityPlugin
{
	public static List<EnemyType> enemies = new List<EnemyType>();

	public static Plugin Instance { get; private set; }

	public static ManualLogSource Log { get; private set; }

	public static Harmony Harmony { get; private set; }

	private void Awake()
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0023: Expected O, but got Unknown
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		Harmony = new Harmony("nwnt.EverythingCanDieAlternative");
		try
		{
			Patches.Initialize(Harmony);
			Log.LogInfo((object)"EverythingCanDieAlternative v1.1.0 is loaded with network support!");
		}
		catch (Exception arg)
		{
			Log.LogError((object)string.Format("Error initializing {0}: {1}", "EverythingCanDieAlternative", arg));
		}
	}

	public static string RemoveInvalidCharacters(string source)
	{
		if (string.IsNullOrEmpty(source))
		{
			return string.Empty;
		}
		StringBuilder stringBuilder = new StringBuilder();
		foreach (char c in source)
		{
			if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
			{
				stringBuilder.Append(c);
			}
		}
		return string.Join("", stringBuilder.ToString().Split((string[]?)null, StringSplitOptions.RemoveEmptyEntries));
	}

	public static bool CanMob(string identifier, string mobName)
	{
		try
		{
			string text = RemoveInvalidCharacters(mobName).ToUpper();
			string text2 = text + identifier.ToUpper();
			foreach (ConfigDefinition key in ((BaseUnityPlugin)Instance).Config.Keys)
			{
				if (RemoveInvalidCharacters(key.Key.ToUpper()).Equals(RemoveInvalidCharacters(text2)))
				{
					bool flag = ((BaseUnityPlugin)Instance).Config[key].BoxedValue.ToString().ToUpper().Equals("TRUE");
					Log.LogDebug((object)$"Mob config: [Mobs] {text2} = {flag}");
					return flag;
				}
			}
			((BaseUnityPlugin)Instance).Config.Bind<bool>("Mobs", text + identifier, true, "If true, " + mobName + " will be damageable");
			Log.LogDebug((object)("No config found for [Mobs] " + text2 + ", defaulting to true"));
			return true;
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Error in config check for mob " + mobName + ": " + ex.Message));
			return false;
		}
	}

	public static int GetMobHealth(string mobName, int defaultHealth)
	{
		try
		{
			string text = RemoveInvalidCharacters(mobName).ToUpper();
			string value = text + ".HEALTH";
			foreach (ConfigDefinition key in ((BaseUnityPlugin)Instance).Config.Keys)
			{
				if (RemoveInvalidCharacters(key.Key.ToUpper()).Equals(value))
				{
					int num = Convert.ToInt32(((BaseUnityPlugin)Instance).Config[key].BoxedValue);
					Log.LogInfo((object)$"Enemy {mobName} health from config: {num}");
					return num;
				}
			}
			ConfigEntry<int> val = ((BaseUnityPlugin)Instance).Config.Bind<int>("Mobs", text + ".Health", defaultHealth, "Health for " + mobName);
			Log.LogInfo((object)$"Created config for {mobName} health: {val.Value}");
			return val.Value;
		}
		catch (Exception ex)
		{
			Log.LogError((object)("Error getting health for " + mobName + ": " + ex.Message));
			return defaultHealth;
		}
	}
}
public static class PluginInfo
{
	public const string PLUGIN_GUID = "nwnt.EverythingCanDieAlternative";

	public const string PLUGIN_NAME = "EverythingCanDieAlternative";

	public const string PLUGIN_VERSION = "1.1.0";
}