Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SSMPEnemyHealthSync v1.0.2
SSMPEnemyHealthSync.dll
Decompiled 5 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using SSMP.Api.Client; using SSMP.Api.Client.Networking; using SSMP.Api.Server; using SSMP.Api.Server.Networking; using SSMP.Networking.Packet; using SSMPEnemySync.Client; using SSMPEnemySync.Networking; using SSMPEnemySync.Server; using SSMPEnemySync.Utils; using UnityEngine; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("SSMPEnemySync")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("SSMPEnemySync")] [assembly: AssemblyTitle("SSMPEnemySync")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace SSMPEnemySync { [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("io.github.jarbe.ssmp.enemysync", "SSMPEnemySync", "1.0.0")] public class SSMPEnemySyncPlugin : BaseUnityPlugin { internal static SSMPEnemySyncPlugin instance; public const string Id = "io.github.jarbe.ssmp.enemysync"; public static string Name => "SSMPEnemySync"; public static string Version => "1.0.0"; private void Awake() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown instance = this; ClientAddon.RegisterAddon((ClientAddon)(object)new SSMPEnemySync.Client.Client()); ServerAddon.RegisterAddon((ServerAddon)(object)new SSMPEnemySync.Server.Server()); Config.Init(((BaseUnityPlugin)this).Config); Logger.SetLogger(((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + Name + " (io.github.jarbe.ssmp.enemysync) has loaded!")); Harmony val = new Harmony("ssmp.enemysync"); val.PatchAll(); } private void Update() { EnemySyncManager.Update(); } } } namespace SSMPEnemySync.Utils { internal static class Config { public const string ModName = "SSMP.EnemySync"; public const string Version = "1.0.0"; public const uint SSMPApiVersion = 1u; public static bool EnableEnemySync { get; private set; } = true; public static bool EnableHealthSync { get; private set; } = true; public static bool EnableDeathSync { get; private set; } = true; public static void Init(ConfigFile config) { EnableEnemySync = config.Bind<bool>("General", "EnableEnemySync", true, "Enable enemy synchronization").Value; EnableHealthSync = config.Bind<bool>("General", "EnableHealthSync", true, "Enable health/damage synchronization").Value; EnableDeathSync = config.Bind<bool>("General", "EnableDeathSync", true, "Enable death synchronization").Value; } } internal static class Logger { private static ManualLogSource? _logger; internal static void SetLogger(ManualLogSource logger) { _logger = logger; } internal static void LogInfo(string message) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogInfo((object)message); } } internal static void LogDebug(string message) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogDebug((object)message); } } internal static void LogWarning(string message) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogWarning((object)message); } } internal static void LogError(string message) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogError((object)message); } } } } namespace SSMPEnemySync.Server { internal static class PacketHandler { private sealed class EntityState { internal int Health; internal int MaxHealth; internal bool IsDead; } private static readonly Dictionary<ushort, ushort> _entityHostMap = new Dictionary<ushort, ushort>(); private static readonly Dictionary<ushort, string> _playerLastScene = new Dictionary<ushort, string>(); private static readonly Dictionary<string, HashSet<ushort>> _deadEntitiesByScene = new Dictionary<string, HashSet<ushort>>(); private static readonly Dictionary<ushort, EntityState> _entityStateMap = new Dictionary<ushort, EntityState>(); internal static void Init() { if (Server.networkReceiver != null) { Server.networkReceiver.RegisterPacketHandler<EnemyHealthPacket>(PacketId.EnemyHealthUpdate, (GenericServerPacketHandler<EnemyHealthPacket>)OnHealthPacketReceived); Server.networkReceiver.RegisterPacketHandler<SceneEnteredPacket>(PacketId.SceneEntered, (GenericServerPacketHandler<SceneEnteredPacket>)OnSceneEnteredReceived); Debug.Log((object)"[SSMP.EnemySync] Server packet handler registered successfully"); } else { Debug.LogWarning((object)"[SSMP.EnemySync] Server networkReceiver is null, cannot register handler!"); } Debug.Log((object)"[SSMP.EnemySync] Server PacketHandler initialized"); } private static void OnHealthPacketReceived(ushort fromPlayerId, EnemyHealthPacket packet) { Debug.Log((object)$"[SSMP.EnemySync] Server received health update from player {fromPlayerId} for entity {packet.EntityId}: {packet.Health}/{packet.MaxHealth}, isDead={packet.IsDead}"); IServerPlayer val = ((IEnumerable<IServerPlayer>)Server.api.ServerManager.Players).FirstOrDefault((Func<IServerPlayer, bool>)((IServerPlayer p) => p.Id == fromPlayerId)); if (val == null) { Debug.LogWarning((object)$"[SSMP.EnemySync] Cannot find sender player {fromPlayerId}"); return; } string currentScene = val.CurrentScene; if (currentScene != null) { if (!_playerLastScene.TryGetValue(fromPlayerId, out string value) || value != currentScene) { _playerLastScene[fromPlayerId] = currentScene; SendDeadSnapshotToPlayer(fromPlayerId, currentScene); } CleanupSceneCaches(); } if (_entityHostMap.TryGetValue(packet.EntityId, out var value2) && value2 != fromPlayerId && _entityStateMap.TryGetValue(packet.EntityId, out EntityState value3) && !packet.IsDead && packet.Health > value3.Health) { Debug.LogWarning((object)$"[SSMP.EnemySync] Rejecting non-host update from {fromPlayerId} for entity {packet.EntityId} (host {value2}) because it looks like a heal: {packet.Health}>{value3.Health}"); return; } if (!_entityHostMap.ContainsKey(packet.EntityId)) { _entityHostMap[packet.EntityId] = fromPlayerId; } if (!_entityStateMap.TryGetValue(packet.EntityId, out EntityState value4)) { value4 = new EntityState(); _entityStateMap[packet.EntityId] = value4; } value4.Health = packet.Health; value4.MaxHealth = packet.MaxHealth; value4.IsDead = packet.IsDead; if (currentScene != null && (packet.IsDead || packet.Health <= 0)) { if (!_deadEntitiesByScene.TryGetValue(currentScene, out HashSet<ushort> value5)) { value5 = new HashSet<ushort>(); _deadEntitiesByScene[currentScene] = value5; } value5.Add(packet.EntityId); } RelayHealthUpdate(fromPlayerId, packet); } private static void OnSceneEnteredReceived(ushort fromPlayerId, SceneEnteredPacket packet) { string text = packet.SceneName ?? ""; if (text.Length != 0) { _playerLastScene[fromPlayerId] = text; CleanupSceneCaches(); SendDeadSnapshotToPlayer(fromPlayerId, text); } } private static void SendDeadSnapshotToPlayer(ushort playerId, string sceneName) { if (Server.networkSender == null || !_deadEntitiesByScene.TryGetValue(sceneName, out HashSet<ushort> value) || value.Count == 0) { return; } foreach (ushort item in value) { int maxHealth = 0; if (_entityStateMap.TryGetValue(item, out EntityState value2)) { maxHealth = value2.MaxHealth; } EnemyHealthPacket enemyHealthPacket = new EnemyHealthPacket { EntityId = item, Health = 0, MaxHealth = maxHealth, IsDead = true, DamageDealt = 0 }; Server.networkSender.SendSingleData(PacketId.EnemyHealthUpdate, (IPacketData)(object)enemyHealthPacket, playerId); } } private static void CleanupSceneCaches() { HashSet<string> activeScenes = (from p in Server.api.ServerManager.Players select p.CurrentScene into s where !string.IsNullOrEmpty(s) select s).Distinct().ToHashSet(); if (activeScenes.Count == 0) { _deadEntitiesByScene.Clear(); return; } string[] array = _deadEntitiesByScene.Keys.Where((string s) => !activeScenes.Contains(s)).ToArray(); string[] array2 = array; foreach (string key in array2) { _deadEntitiesByScene.Remove(key); } } private static void RelayHealthUpdate(ushort fromPlayerId, EnemyHealthPacket packet) { if (Server.networkSender == null) { Debug.LogWarning((object)"[SSMP.EnemySync] Cannot relay - networkSender is null!"); return; } IServerPlayer val = ((IEnumerable<IServerPlayer>)Server.api.ServerManager.Players).FirstOrDefault((Func<IServerPlayer, bool>)((IServerPlayer p) => p.Id == fromPlayerId)); if (val == null) { Debug.LogWarning((object)$"[SSMP.EnemySync] Cannot find sender player {fromPlayerId}"); return; } string senderScene = val.CurrentScene; ushort[] array = (from p in Server.api.ServerManager.Players where p.Id != fromPlayerId && p.CurrentScene == senderScene select p.Id).ToArray(); if (array.Length == 0) { Debug.Log((object)$"[SSMP.EnemySync] No other clients in scene '{senderScene}' to relay to for entity {packet.EntityId}"); return; } Debug.Log((object)string.Format("[SSMP.EnemySync] SERVER: Relaying entity {0} health {1}/{2} to players in scene '{3}' [{4}] (excluded sender {5})", packet.EntityId, packet.Health, packet.MaxHealth, senderScene, string.Join(",", array), fromPlayerId)); Server.networkSender.SendSingleData(PacketId.EnemyHealthUpdate, (IPacketData)(object)packet, array); Debug.Log((object)$"[SSMP.EnemySync] Relayed health update for entity {packet.EntityId} from player {fromPlayerId} to {array.Length} other clients in scene '{senderScene}'"); } internal static void OnPlayerDisconnect(ushort playerId) { List<ushort> list = new List<ushort>(); foreach (KeyValuePair<ushort, ushort> item in _entityHostMap) { if (item.Value == playerId) { list.Add(item.Key); } } foreach (ushort item2 in list) { _entityHostMap.Remove(item2); Debug.Log((object)$"[SSMP.EnemySync] Cleared entity {item2} host (player {playerId} disconnected)"); } } internal static void Clear() { _entityHostMap.Clear(); _entityStateMap.Clear(); _playerLastScene.Clear(); _deadEntitiesByScene.Clear(); Debug.Log((object)"[SSMP.EnemySync] Cleared all entity hosts"); } } internal class Server : ServerAddon { internal static IServerApi api; internal static Server instance; internal static IServerAddonNetworkSender<PacketId>? networkSender; internal static IServerAddonNetworkReceiver<PacketId>? networkReceiver; protected override string Name => "SSMP.EnemySync"; protected override string Version => "1.0.0"; public override uint ApiVersion => 1u; public override bool NeedsNetwork => true; public override void Initialize(IServerApi serverApi) { instance = this; api = serverApi; Debug.Log((object)"[SSMP.EnemySync] Server addon initializing..."); try { networkSender = api.NetServer.GetNetworkSender<PacketId>((ServerAddon)(object)this); Debug.Log((object)$"[SSMP.EnemySync] Server network sender obtained: {networkSender != null}"); } catch (Exception ex) { Debug.LogError((object)("[SSMP.EnemySync] Failed to get server network sender: " + ex.Message)); } try { networkReceiver = api.NetServer.GetNetworkReceiver<PacketId>((ServerAddon)(object)this, (Func<PacketId, IPacketData>)PacketInstantiator); Debug.Log((object)$"[SSMP.EnemySync] Server network receiver obtained: {networkReceiver != null}"); } catch (Exception ex2) { Debug.LogError((object)("[SSMP.EnemySync] Failed to get server network receiver: " + ex2.Message)); } PacketHandler.Init(); ((ServerAddon)this).Logger.Info("SSMP EnemySync Server Initialized"); } private static IPacketData PacketInstantiator(PacketId packetId) { if (1 == 0) { } IPacketData result = (IPacketData)(packetId switch { PacketId.EnemyHealthUpdate => new EnemyHealthPacket(), PacketId.SceneEntered => new SceneEnteredPacket(), _ => throw new ArgumentOutOfRangeException("packetId", $"Unknown packet ID: {packetId}"), }); if (1 == 0) { } return result; } } } namespace SSMPEnemySync.Networking { public class EnemyHealthPacket : IPacketData { public bool IsReliable => true; public bool DropReliableDataIfNewerExists => true; public ushort EntityId { get; set; } public int Health { get; set; } public int MaxHealth { get; set; } public bool IsDead { get; set; } public int DamageDealt { get; set; } public void WriteData(IPacket packet) { packet.Write(EntityId); packet.Write(Health); packet.Write(MaxHealth); packet.Write(IsDead); packet.Write(DamageDealt); } public void ReadData(IPacket packet) { EntityId = packet.ReadUShort(); Health = packet.ReadInt(); MaxHealth = packet.ReadInt(); IsDead = packet.ReadBool(); DamageDealt = packet.ReadInt(); } } public enum PacketId : byte { EnemyHealthUpdate, SceneEntered } public sealed class SceneEnteredPacket : IPacketData { public bool IsReliable => true; public bool DropReliableDataIfNewerExists => true; public string SceneName { get; set; } = ""; public void WriteData(IPacket packet) { packet.Write(SceneName ?? ""); } public void ReadData(IPacket packet) { SceneName = packet.ReadString(); } } } namespace SSMPEnemySync.Client { internal class Client : ClientAddon { internal static IClientApi api; internal static Client instance; internal static IClientAddonNetworkSender<PacketId>? networkSender; internal static IClientAddonNetworkReceiver<PacketId>? networkReceiver; private static int _hostCheckFrameDelay; private const int HOST_CHECK_DELAY_FRAMES = 30; protected override string Name => "SSMP.EnemySync"; protected override string Version => "1.0.0"; public override uint ApiVersion => 1u; public override bool NeedsNetwork => true; internal static bool IsSceneHost { get; private set; } internal static bool IsSceneHostDetermined { get; private set; } public override void Initialize(IClientApi clientApi) { instance = this; api = clientApi; Debug.Log((object)"[SSMP.EnemySync] Client addon initialized [v2-20250416]"); try { networkSender = api.NetClient.GetNetworkSender<PacketId>((ClientAddon)(object)this); Debug.Log((object)$"[SSMP.EnemySync] Network sender obtained: {networkSender != null}"); } catch (Exception ex) { Debug.LogError((object)("[SSMP.EnemySync] Failed to get network sender: " + ex.Message)); } try { networkReceiver = api.NetClient.GetNetworkReceiver<PacketId>((ClientAddon)(object)this, (Func<PacketId, IPacketData>)PacketInstantiator); Debug.Log((object)$"[SSMP.EnemySync] Network receiver obtained: {networkReceiver != null}"); } catch (Exception ex2) { Debug.LogError((object)("[SSMP.EnemySync] Failed to get network receiver: " + ex2.Message)); } PacketReceiver.Init(); PacketSender.Init(); EnemySyncManager.Init(); api.ClientManager.PlayerEnterSceneEvent += OnPlayerEnterScene; api.ClientManager.PlayerLeaveSceneEvent += OnPlayerLeaveScene; ((ClientAddon)this).Logger.Info("SSMP EnemySync Client Initialized"); } private void OnPlayerEnterScene(IClientPlayer player) { if (player.IsInLocalScene && !IsSceneHostDetermined) { IsSceneHost = false; IsSceneHostDetermined = true; Debug.Log((object)"[SSMP.EnemySync] We are NOT scene host (other player already in scene when we arrived)"); } else if (player.IsInLocalScene && IsSceneHostDetermined && IsSceneHost) { EnemySyncManager.OnOtherPlayerEnteredLocalScene(); } } private void OnPlayerLeaveScene(IClientPlayer player) { } internal static void ResetSceneHost() { IsSceneHost = false; IsSceneHostDetermined = false; _hostCheckFrameDelay = 0; Debug.Log((object)"[SSMP.EnemySync] Reset scene host status for new scene"); } internal static void OnSceneLoadedAndEnemiesScanned() { if (IsSceneHostDetermined) { return; } IClientApi obj = api; object obj2; if (obj == null) { obj2 = null; } else { IClientManager clientManager = obj.ClientManager; obj2 = ((clientManager != null) ? clientManager.Players : null); } if (obj2 == null) { Debug.Log((object)"[SSMP.EnemySync] Delaying host check - API not ready yet"); return; } _hostCheckFrameDelay++; if (_hostCheckFrameDelay < 30) { return; } bool flag = false; foreach (IClientPlayer player in api.ClientManager.Players) { if (player.IsInLocalScene) { flag = true; break; } } if (flag) { IsSceneHost = false; IsSceneHostDetermined = true; Debug.Log((object)"[SSMP.EnemySync] We are NOT scene host (other players already present)"); } else { IsSceneHost = true; IsSceneHostDetermined = true; Debug.Log((object)"[SSMP.EnemySync] We are scene host (first in scene)"); } } private static IPacketData PacketInstantiator(PacketId packetId) { if (1 == 0) { } IPacketData result = (IPacketData)(packetId switch { PacketId.EnemyHealthUpdate => new EnemyHealthPacket(), PacketId.SceneEntered => new SceneEnteredPacket(), _ => throw new ArgumentOutOfRangeException("packetId", packetId, null), }); if (1 == 0) { } return result; } } internal static class EnemySyncManager { private sealed class TrackedEnemy { internal ushort EntityId; internal HealthManager HealthManager = null; internal int LastHealth; internal int MaxHealth; internal bool LastIsDead; } private static readonly Dictionary<ushort, TrackedEnemy> _trackedEnemies = new Dictionary<ushort, TrackedEnemy>(); private static readonly Dictionary<string, HashSet<ushort>> _deadEnemiesByScene = new Dictionary<string, HashSet<ushort>>(StringComparer.Ordinal); private static HashSet<ushort> _deadEnemies = new HashSet<ushort>(); private static FieldInfo? _hpField; private static FieldInfo? _isDeadField; private static readonly List<MethodInfo> _dieMethods = new List<MethodInfo>(); private static string _lastSceneName = ""; private static bool _initialized; private static int _scanRetryFrame; private const int SCAN_RETRY_INTERVAL_FRAMES = 60; internal static void Init() { if (_initialized) { return; } _initialized = true; _hpField = typeof(HealthManager).GetField("hp", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _isDeadField = typeof(HealthManager).GetField("isDead", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); _dieMethods.Clear(); try { MethodInfo[] methods = typeof(HealthManager).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { if (!(methodInfo.Name != "Die")) { _dieMethods.Add(methodInfo); } } _dieMethods.Sort((MethodInfo a, MethodInfo b) => a.GetParameters().Length.CompareTo(b.GetParameters().Length)); } catch { _dieMethods.Clear(); } SceneManager.sceneLoaded += OnSceneLoaded; } private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (Config.EnableEnemySync) { _trackedEnemies.Clear(); _lastSceneName = ((Scene)(ref scene)).name; if (!_deadEnemiesByScene.TryGetValue(_lastSceneName, out _deadEnemies)) { _deadEnemies = new HashSet<ushort>(); _deadEnemiesByScene[_lastSceneName] = _deadEnemies; } _scanRetryFrame = 0; Client.ResetSceneHost(); PacketSender.SendSceneEntered(((Scene)(ref scene)).name); ScanForEnemies(); } } internal static void OnLocalSceneEmptied(string sceneName) { if (Config.EnableEnemySync) { _deadEnemiesByScene.Remove(sceneName); if (string.Equals(_lastSceneName, sceneName, StringComparison.Ordinal)) { _deadEnemies = new HashSet<ushort>(); } } } internal static void OnOtherPlayerEnteredLocalScene() { if (!Config.EnableEnemySync || !Config.EnableHealthSync || !Client.IsSceneHost || !Client.IsSceneHostDetermined || _deadEnemies.Count == 0) { return; } foreach (ushort deadEnemy in _deadEnemies) { if (_trackedEnemies.TryGetValue(deadEnemy, out TrackedEnemy value) && (Object)(object)value.HealthManager != (Object)null) { PacketSender.SendEnemyHealthUpdate(deadEnemy, 0, value.MaxHealth, isDead: true, 0); } else { PacketSender.SendEnemyHealthUpdate(deadEnemy, 0, 0, isDead: true, 0); } } } private static void ScanForEnemies() { if (!Config.EnableEnemySync) { return; } HealthManager[] array = Object.FindObjectsByType<HealthManager>((FindObjectsSortMode)0); HealthManager[] array2 = array; foreach (HealthManager val in array2) { if ((Object)(object)val == (Object)null || (Object)(object)((Component)val).gameObject == (Object)null) { continue; } ushort num = ComputeEntityId(_lastSceneName, ((Component)val).gameObject); if (_trackedEnemies.ContainsKey(num)) { continue; } int healthViaReflection = GetHealthViaReflection(val); bool isDeadViaReflection = GetIsDeadViaReflection(val); _trackedEnemies[num] = new TrackedEnemy { EntityId = num, HealthManager = val, LastHealth = healthViaReflection, MaxHealth = healthViaReflection, LastIsDead = isDeadViaReflection }; if (_deadEnemies.Contains(num)) { SetHealthViaReflection(val, 0); if (Config.EnableDeathSync) { TryCallDie(val); } _trackedEnemies[num].LastHealth = 0; _trackedEnemies[num].LastIsDead = true; } } } internal static void Update() { if (!Config.EnableEnemySync || !Config.EnableHealthSync) { return; } if (!Client.IsSceneHostDetermined) { Client.OnSceneLoadedAndEnemiesScanned(); } _scanRetryFrame++; if (_scanRetryFrame >= 60) { _scanRetryFrame = 0; if (_trackedEnemies.Count == 0) { ScanForEnemies(); } } foreach (TrackedEnemy value in _trackedEnemies.Values) { if ((Object)(object)value.HealthManager == (Object)null) { continue; } if (_deadEnemies.Contains(value.EntityId)) { int healthViaReflection = GetHealthViaReflection(value.HealthManager); if (healthViaReflection > 0) { SetHealthViaReflection(value.HealthManager, 0); if (Config.EnableDeathSync) { TryCallDie(value.HealthManager); } PacketSender.SendEnemyHealthUpdate(value.EntityId, 0, value.MaxHealth, isDead: true, healthViaReflection); value.LastHealth = 0; value.LastIsDead = true; continue; } } int healthViaReflection2 = GetHealthViaReflection(value.HealthManager); bool flag = GetIsDeadViaReflection(value.HealthManager); if (healthViaReflection2 <= 0) { flag = true; } if (flag || healthViaReflection2 <= 0) { _deadEnemies.Add(value.EntityId); } else { _deadEnemies.Remove(value.EntityId); } if (healthViaReflection2 != value.LastHealth || flag != value.LastIsDead) { int damageDealt = Math.Max(0, value.LastHealth - healthViaReflection2); PacketSender.SendEnemyHealthUpdate(value.EntityId, healthViaReflection2, value.MaxHealth, flag, damageDealt); value.LastHealth = healthViaReflection2; value.LastIsDead = flag; } } } internal static void HandleEnemyHealthUpdate(ushort entityId, int health, int maxHealth, bool isDead) { if (!Config.EnableEnemySync || !Config.EnableHealthSync) { return; } if (isDead || health <= 0) { _deadEnemies.Add(entityId); } else { _deadEnemies.Remove(entityId); } if (!_trackedEnemies.TryGetValue(entityId, out TrackedEnemy value) || (Object)(object)value.HealthManager == (Object)null) { ScanForEnemies(); if (!_trackedEnemies.TryGetValue(entityId, out value) || (Object)(object)value.HealthManager == (Object)null) { return; } } value.MaxHealth = maxHealth; SetHealthViaReflection(value.HealthManager, health); if (Config.EnableDeathSync && isDead) { SetHealthViaReflection(value.HealthManager, 0); TryCallDie(value.HealthManager); } value.LastHealth = health; value.LastIsDead = isDead; } private static int GetHealthViaReflection(HealthManager hm) { try { if (_hpField != null) { return (int)_hpField.GetValue(hm); } } catch { } return hm.hp; } private static bool GetIsDeadViaReflection(HealthManager hm) { try { if (_isDeadField != null) { return (bool)_isDeadField.GetValue(hm); } } catch { } return false; } private static void SetHealthViaReflection(HealthManager hm, int health) { try { if (_hpField != null) { _hpField.SetValue(hm, health); return; } } catch { } hm.hp = health; } private static void SetDeathStateViaReflection(HealthManager hm, bool isDead) { try { if (_isDeadField != null) { _isDeadField.SetValue(hm, isDead); } } catch { } } private static void TryCallDie(HealthManager hm) { if (_dieMethods.Count == 0) { return; } foreach (MethodInfo dieMethod in _dieMethods) { try { ParameterInfo[] parameters = dieMethod.GetParameters(); object[] array = null; if (parameters.Length != 0) { array = new object[parameters.Length]; for (int i = 0; i < parameters.Length; i++) { array[i] = GetDefaultValue(parameters[i].ParameterType); } } dieMethod.Invoke(hm, array); break; } catch { } } } private static object? GetDefaultValue(Type t) { if (!t.IsValueType) { return null; } if (t.IsEnum) { Array values = Enum.GetValues(t); return (values.Length > 0) ? values.GetValue(0) : Activator.CreateInstance(t); } return Activator.CreateInstance(t); } private static ushort ComputeEntityId(string sceneName, GameObject go) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) uint hash = 2166136261u; Action<string> action = delegate(string s) { for (int i = 0; i < s.Length; i++) { hash ^= s[i]; hash *= 16777619u; } }; action(sceneName ?? ""); action("|"); action(((Object)go).name ?? ""); action("|"); Vector3 position = go.transform.position; action(position.x.ToString("F2")); action(","); action(position.y.ToString("F2")); action(","); action(position.z.ToString("F2")); return (ushort)(hash & 0xFFFFu); } } internal static class PacketReceiver { internal static void Init() { if (Client.networkReceiver != null) { Client.networkReceiver.RegisterPacketHandler<EnemyHealthPacket>(PacketId.EnemyHealthUpdate, (GenericClientPacketHandler<EnemyHealthPacket>)OnHealthPacketReceived); } Debug.Log((object)"[SSMP.EnemySync] PacketReceiver initialized [v2-20250416]"); } private static void OnHealthPacketReceived(EnemyHealthPacket packet) { if (packet.EntityId == 0 || packet.MaxHealth > 10000 || packet.Health < -100000) { Debug.LogWarning((object)$"[SSMP.EnemySync] CORRUPTED PACKET DETECTED! Entity={packet.EntityId}, Health={packet.Health}, Max={packet.MaxHealth}, IsDead={packet.IsDead}, Dmg={packet.DamageDealt}"); } else { Debug.Log((object)$"[SSMP.EnemySync] Client received health update for entity {packet.EntityId}: {packet.Health}/{packet.MaxHealth}, isDead={packet.IsDead}"); EnemySyncManager.HandleEnemyHealthUpdate(packet.EntityId, packet.Health, packet.MaxHealth, packet.IsDead); } } internal static void OnEnemyHealthUpdate(ushort entityId, int health, int maxHealth, bool isDead) { Debug.Log((object)$"[SSMP.EnemySync] Received health update for entity {entityId}: {health}/{maxHealth}, dead={isDead}"); EnemySyncManager.HandleEnemyHealthUpdate(entityId, health, maxHealth, isDead); } } internal static class PacketSender { internal static void Init() { Debug.Log((object)"[SSMP.EnemySync] PacketSender initialized"); } internal static void SendSceneEntered(string sceneName) { IClientApi api = Client.api; if (api == null) { return; } INetClient netClient = api.NetClient; if (!((netClient != null) ? new bool?(netClient.IsConnected) : null).GetValueOrDefault() || Client.networkSender == null) { return; } SceneEnteredPacket sceneEnteredPacket = new SceneEnteredPacket { SceneName = (sceneName ?? "") }; try { Client.networkSender.SendSingleData(PacketId.SceneEntered, (IPacketData)(object)sceneEnteredPacket); } catch { } } internal static void SendEnemyHealthUpdate(ushort entityId, int health, int maxHealth, bool isDead, int damageDealt) { IClientApi api = Client.api; if (api != null) { INetClient netClient = api.NetClient; if (((netClient != null) ? new bool?(netClient.IsConnected) : null).GetValueOrDefault()) { if (Client.networkSender == null) { Debug.LogWarning((object)"[SSMP.EnemySync] Cannot send health update - networkSender is null!"); return; } EnemyHealthPacket enemyHealthPacket = new EnemyHealthPacket { EntityId = entityId, Health = health, MaxHealth = maxHealth, IsDead = isDead, DamageDealt = damageDealt }; Debug.Log((object)$"[SSMP.EnemySync] About to send health update for entity {entityId}: {health}/{maxHealth}"); try { Client.networkSender.SendSingleData(PacketId.EnemyHealthUpdate, (IPacketData)(object)enemyHealthPacket); Debug.Log((object)$"[SSMP.EnemySync] Successfully sent health update for entity {entityId}"); return; } catch (Exception ex) { Debug.LogError((object)("[SSMP.EnemySync] Failed to send health update: " + ex.Message)); return; } } } Debug.LogWarning((object)"[SSMP.EnemySync] Cannot send - client not connected to server"); } } }