Decompiled source of SSMPEnemyHealthSync v1.0.1

SSMPEnemyHealthSync.dll

Decompiled a day ago
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");
		}
	}
}