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