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";
}