diff --git a/Game/Scripts/Enemy/SteamEnemyController.cs b/Game/Scripts/Enemy/SteamEnemyController.cs index 0752118..937e71b 100644 --- a/Game/Scripts/Enemy/SteamEnemyController.cs +++ b/Game/Scripts/Enemy/SteamEnemyController.cs @@ -22,9 +22,6 @@ namespace MegaKoop.Game.Enemy public class SteamEnemyController : MonoBehaviour { private static readonly List SharedHealthBuffer = new(32); - private static int nextEnemyNetworkId = StartingEnemyNetworkId; - - private const int StartingEnemyNetworkId = 10000; [Header("Movement")] [SerializeField] private float moveSpeed = 3.5f; @@ -118,7 +115,7 @@ namespace MegaKoop.Game.Enemy [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void ResetNetworkIdCounter() { - nextEnemyNetworkId = StartingEnemyNetworkId; + NetworkIdAllocator.Reset(); } private void EnsureIdentity() @@ -143,7 +140,8 @@ namespace MegaKoop.Game.Enemy return; } - identity.SetNetworkId(nextEnemyNetworkId++); + int allocatedId = NetworkIdAllocator.AllocateEnemyId(); + identity.SetNetworkId(allocatedId); } private void OnEnable() diff --git a/Game/Scripts/Networking/LobbyGameSceneCoordinator.cs b/Game/Scripts/Networking/LobbyGameSceneCoordinator.cs index fe962c7..18a3253 100644 --- a/Game/Scripts/Networking/LobbyGameSceneCoordinator.cs +++ b/Game/Scripts/Networking/LobbyGameSceneCoordinator.cs @@ -256,7 +256,7 @@ namespace MegaKoop.Game.Networking { // Ensure network identity is deterministic across clients. var identity = clone.GetComponent() ?? clone.AddComponent(); - identity.SetNetworkId(index + 1); + identity.SetNetworkId(NetworkIdAllocator.PlayerIdStart + index); var bridge = clone.GetComponent() ?? clone.AddComponent(); bridge.AssignOwner(ParseSteamId(info.SteamId), info.IsLocal); diff --git a/Game/Scripts/Networking/NetworkIdAllocator.cs b/Game/Scripts/Networking/NetworkIdAllocator.cs new file mode 100644 index 0000000..c41aef3 --- /dev/null +++ b/Game/Scripts/Networking/NetworkIdAllocator.cs @@ -0,0 +1,65 @@ +using UnityEngine; + +namespace MegaKoop.Game.Networking +{ + internal static class NetworkIdAllocator + { + public const int PlayerIdStart = 1; + public const int EnemyIdStart = 10000; + private const int EnemyIdRange = 10000; + + private static int nextEnemyId = EnemyIdStart; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + private static void ResetOnLoad() + { + Reset(); + } + + public static void Reset() + { + nextEnemyId = EnemyIdStart; + } + + public static int AllocateEnemyId() + { + if (nextEnemyId < EnemyIdStart) + { + nextEnemyId = EnemyIdStart; + } + + const int maxAttempts = EnemyIdRange; + int attempts = 0; + while ((NetworkIdRegistry.IsIdRegistered(nextEnemyId) || NetworkIdRegistry.IsIdReserved(nextEnemyId)) && attempts < maxAttempts) + { + AdvanceEnemyCursor(); + attempts++; + } + + int allocated = nextEnemyId; + AdvanceEnemyCursor(); + return allocated; + } + + public static bool IsPlayerId(int id) => id >= PlayerIdStart && id < EnemyIdStart; + + public static bool IsEnemyId(int id) => id >= EnemyIdStart; + + public static void SyncEnemyCursor(int id) + { + if (id >= EnemyIdStart && id >= nextEnemyId) + { + nextEnemyId = id + 1; + } + } + + private static void AdvanceEnemyCursor() + { + nextEnemyId++; + if (nextEnemyId >= EnemyIdStart + EnemyIdRange) + { + nextEnemyId = EnemyIdStart; + } + } + } +} diff --git a/Game/Scripts/Networking/NetworkIdRegistry.cs b/Game/Scripts/Networking/NetworkIdRegistry.cs index 48b0493..849b9d1 100644 --- a/Game/Scripts/Networking/NetworkIdRegistry.cs +++ b/Game/Scripts/Networking/NetworkIdRegistry.cs @@ -242,6 +242,7 @@ namespace MegaKoop.Game.Networking instance.registry.Clear(); instance.steamIdToNetworkId.Clear(); instance.reservedIds.Clear(); + NetworkIdAllocator.Reset(); Debug.Log($"[NetworkIdRegistry] Registry cleared. Removed {registryCount} identities, {mappingCount} Steam ID mappings, and {reservedCount} reserved IDs."); } diff --git a/Game/Scripts/Networking/SteamCharacterNetworkBridge.cs b/Game/Scripts/Networking/SteamCharacterNetworkBridge.cs index 76ebe2c..c4614c2 100644 --- a/Game/Scripts/Networking/SteamCharacterNetworkBridge.cs +++ b/Game/Scripts/Networking/SteamCharacterNetworkBridge.cs @@ -30,12 +30,14 @@ namespace MegaKoop.Game.Networking private Vector3 remoteTargetVelocity; private bool haveRemoteState; private bool localOverrideSet; + private int expectedNetworkId; public void AssignOwner(ulong steamId, bool localPlayer) { ownerSteamId = steamId; isLocalPlayer = localPlayer; localOverrideSet = true; + CaptureExpectedNetworkId(); UpdateAuthority(); ConfigureController(); } @@ -87,6 +89,7 @@ namespace MegaKoop.Game.Networking private void Start() { networkManager = SteamCoopNetworkManager.Instance; + CaptureExpectedNetworkId(); UpdateAuthority(); ConfigureController(); } @@ -94,6 +97,7 @@ namespace MegaKoop.Game.Networking private void OnEnable() { networkManager = SteamCoopNetworkManager.Instance; + CaptureExpectedNetworkId(); RegisterHandlers(); } @@ -221,6 +225,11 @@ namespace MegaKoop.Game.Networking return; } + if (identity.NetworkId == 0) + { + return; + } + var unityController = GetComponent(); Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero; SteamCharacterStateCache.ReportLocalState(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity); @@ -238,6 +247,11 @@ namespace MegaKoop.Game.Networking return; } + if (identity.NetworkId == 0) + { + return; + } + float moveX = animator.GetFloat("MoveX"); float moveZ = animator.GetFloat("MoveZ"); float speed = animator.GetFloat("Speed"); @@ -285,24 +299,9 @@ namespace MegaKoop.Game.Networking } CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload); - if (identity != null && transformMessage.NetworkId != identity.NetworkId) + if (!EnsureMatchingNetworkId(transformMessage.NetworkId, message.Sender)) { - if (ownerSteamId != 0 && message.Sender != ownerSteamId) - { - return; - } - - var existing = NetworkIdRegistry.GetById(transformMessage.NetworkId); - if (existing != null && existing != identity) - { - return; - } - - identity.SetNetworkId(transformMessage.NetworkId); - if (identity.NetworkId != transformMessage.NetworkId) - { - return; - } + return; } remoteTargetPosition = transformMessage.Position; @@ -319,24 +318,9 @@ namespace MegaKoop.Game.Networking } CharacterAnimMessage anim = CharacterAnimMessage.Deserialize(message.Payload); - if (identity != null && anim.NetworkId != identity.NetworkId) + if (!EnsureMatchingNetworkId(anim.NetworkId, message.Sender)) { - if (ownerSteamId != 0 && message.Sender != ownerSteamId) - { - return; - } - - var existing = NetworkIdRegistry.GetById(anim.NetworkId); - if (existing != null && existing != identity) - { - return; - } - - identity.SetNetworkId(anim.NetworkId); - if (identity.NetworkId != anim.NetworkId) - { - return; - } + return; } if (animator == null) @@ -358,6 +342,93 @@ namespace MegaKoop.Game.Networking animator.SetBool("IsJumping", anim.IsJumping); } + private void CaptureExpectedNetworkId() + { + if (identity == null) + { + identity = GetComponent(); + } + + if (identity == null) + { + return; + } + + if (identity.NetworkId != 0) + { + expectedNetworkId = identity.NetworkId; + if (ownerSteamId != 0) + { + var mapped = NetworkIdRegistry.GetNetworkIdForSteamId(ownerSteamId); + if (mapped != expectedNetworkId) + { + NetworkIdRegistry.MapSteamIdToNetworkId(ownerSteamId, expectedNetworkId); + } + } + } + } + + private bool EnsureMatchingNetworkId(int incomingId, ulong sender) + { + if (identity == null) + { + return false; + } + + if (incomingId == 0) + { + return false; + } + + int currentId = identity.NetworkId; + if (currentId == incomingId) + { + return true; + } + + if (currentId != 0) + { + return false; + } + + if (ownerSteamId != 0 && sender != ownerSteamId) + { + return false; + } + + int mappedId = ownerSteamId != 0 ? NetworkIdRegistry.GetNetworkIdForSteamId(ownerSteamId) : expectedNetworkId; + if (mappedId != 0) + { + identity.SetNetworkId(mappedId); + if (identity.NetworkId == mappedId) + { + expectedNetworkId = mappedId; + return mappedId == incomingId; + } + + return false; + } + + if (!NetworkIdAllocator.IsPlayerId(incomingId)) + { + return false; + } + + identity.SetNetworkId(incomingId); + if (identity.NetworkId != incomingId) + { + return false; + } + + expectedNetworkId = incomingId; + if (ownerSteamId != 0) + { + NetworkIdRegistry.MapSteamIdToNetworkId(ownerSteamId, incomingId); + } + + return true; + } + public void SendLocalInput(Vector2 moveInput, bool jump) { if (networkManager == null || identity == null || !isAuthority) @@ -370,6 +441,11 @@ namespace MegaKoop.Game.Networking return; } + if (identity.NetworkId == 0) + { + return; + } + networkManager.SendToAll(NetworkMessageType.PlayerInput, PlayerInputMessage.Serialize(new PlayerInputMessage(identity.NetworkId, moveInput, jump)), EP2PSend.k_EP2PSendUnreliableNoDelay); } } diff --git a/Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs b/Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs index 7f47c1e..2bc33f2 100644 --- a/Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs +++ b/Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs @@ -348,6 +348,7 @@ namespace MegaKoop.Game.Networking var identity = instance.GetComponent(); if (identity != null && pendingRemoteSpawn.NetworkId != 0 && identity.NetworkId != pendingRemoteSpawn.NetworkId) { + NetworkIdAllocator.SyncEnemyCursor(pendingRemoteSpawn.NetworkId); identity.SetNetworkId(pendingRemoteSpawn.NetworkId); } }