using System.Collections.Generic; using Game.Scripts.Runtime.Data; using Game.Scripts.Runtime.Game; using Game.Scripts.Runtime.Pooling; using Game.Scripts.Runtime.Spawning; using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { [DefaultExecutionOrder(-120)] [DisallowMultipleComponent] public class SteamEnemySpawnerNetworkBridge : MonoBehaviour { [Header("References")] [SerializeField] private EnemySpawner spawner; [SerializeField] private GameController gameController; [SerializeField] private ObjectPooler pooler; [SerializeField] private List additionalDefinitions = new(); [Header("Behaviour")] [SerializeField] private bool autoFindReferences = true; private SteamCoopNetworkManager networkManager; private bool handlersRegistered; private bool spawnerSubscribed; private bool poolerSubscribed; private bool cachedAuthority; private bool hasPendingRemoteSpawn; private EnemySpawnMessage pendingRemoteSpawn; private void Awake() { if (autoFindReferences) { spawner ??= GetComponent(); gameController ??= GetComponent(); pooler ??= spawner != null ? spawner.Pool : ObjectPooler.SharedInstance; } cachedAuthority = DetermineAuthority(); ApplyAuthorityOverride(cachedAuthority); RegisterDefinitionsFromSpawner(); RegisterAdditionalDefinitions(); } private void OnEnable() { RefreshNetworkManager(); RegisterHandlers(); SubscribeEvents(); cachedAuthority = DetermineAuthority(); ApplyAuthorityOverride(cachedAuthority); RegisterDefinitionsFromSpawner(); } private void OnDisable() { UnsubscribeEvents(); UnregisterHandlers(); } private void Update() { RefreshNetworkManager(); bool authority = DetermineAuthority(); if (authority != cachedAuthority) { cachedAuthority = authority; ApplyAuthorityOverride(authority); } if (!handlersRegistered) { RegisterHandlers(); } } private void SubscribeEvents() { if (spawner != null && !spawnerSubscribed) { spawner.OnEnemySpawned += HandleEnemySpawned; spawnerSubscribed = true; } if (pooler != null && !poolerSubscribed) { pooler.InstanceDespawned += HandleInstanceDespawned; poolerSubscribed = true; } } private void UnsubscribeEvents() { if (spawner != null && spawnerSubscribed) { spawner.OnEnemySpawned -= HandleEnemySpawned; spawnerSubscribed = false; } if (pooler != null && poolerSubscribed) { pooler.InstanceDespawned -= HandleInstanceDespawned; poolerSubscribed = false; } } private void RegisterHandlers() { if (handlersRegistered) { return; } RefreshNetworkManager(); if (networkManager == null) { return; } networkManager.RegisterHandler(NetworkMessageType.EnemySpawn, HandleEnemySpawnMessage); networkManager.RegisterHandler(NetworkMessageType.EnemyDespawn, HandleEnemyDespawnMessage); handlersRegistered = true; } private void UnregisterHandlers() { if (!handlersRegistered || networkManager == null) { return; } networkManager.UnregisterHandler(NetworkMessageType.EnemySpawn, HandleEnemySpawnMessage); networkManager.UnregisterHandler(NetworkMessageType.EnemyDespawn, HandleEnemyDespawnMessage); handlersRegistered = false; } private void RefreshNetworkManager() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; } } private bool DetermineAuthority() { RefreshNetworkManager(); if (networkManager == null) { return true; } if (!networkManager.IsConnected) { return true; } return networkManager.IsHost; } private void ApplyAuthorityOverride(bool authority) { spawner?.SetAuthorityOverride(authority); } private void RegisterDefinitionsFromSpawner() { if (spawner == null) { return; } var table = spawner.ActiveTable; if (table?.Entries == null) { return; } foreach (var entry in table.Entries) { if (entry?.Def == null) { continue; } EnemyDefinitionRegistry.Register(entry.Def); } } private void RegisterAdditionalDefinitions() { if (additionalDefinitions == null) { return; } foreach (var definition in additionalDefinitions) { EnemyDefinitionRegistry.Register(definition); } } private void HandleEnemySpawned(GameObject instance, EnemyDefinition definition) { if (definition != null) { EnemyDefinitionRegistry.Register(definition); } if (!IsHostClient() || networkManager == null) { if (hasPendingRemoteSpawn) { ApplyPendingRemoteSpawn(instance, definition); } return; } if (instance == null || definition == null) { return; } string definitionId = EnemyDefinitionRegistry.ResolveId(definition); var identity = instance.GetComponent(); int networkId = identity != null ? identity.NetworkId : 0; Vector3 spawnRootPosition = instance.transform.position - definition.PrefabPivotOffset; Quaternion rotation = instance.transform.rotation; bool isBoss = definition.IsBoss; int waveIndex = spawner != null ? spawner.CurrentWaveIndex : -1; float timestamp = gameController != null ? gameController.Elapsed : Time.time; var message = new EnemySpawnMessage(definitionId, spawnRootPosition, rotation, networkId, isBoss, waveIndex, timestamp); byte[] payload = EnemySpawnMessage.Serialize(message); networkManager.SendToAll(NetworkMessageType.EnemySpawn, payload, EP2PSend.k_EP2PSendReliable); } private void HandleInstanceDespawned(GameObject instance, EnemyDefinition definition) { if (!IsHostClient() || networkManager == null) { return; } if (instance == null) { return; } var identity = instance.GetComponent(); if (identity == null || identity.NetworkId == 0) { return; } var message = new EnemyDespawnMessage(identity.NetworkId); byte[] payload = EnemyDespawnMessage.Serialize(message); SteamCharacterStateCache.RemoveState(identity.NetworkId); networkManager.SendToAll(NetworkMessageType.EnemyDespawn, payload, EP2PSend.k_EP2PSendReliable); } private void HandleEnemySpawnMessage(NetworkMessage message) { if (IsHostClient()) { return; } pendingRemoteSpawn = EnemySpawnMessage.Deserialize(message.Payload); hasPendingRemoteSpawn = true; if (!EnemyDefinitionRegistry.TryGet(pendingRemoteSpawn.DefinitionId, out var definition)) { Debug.LogWarning($"[SteamEnemySpawnerNetworkBridge] Missing EnemyDefinition for id '{pendingRemoteSpawn.DefinitionId}'."); hasPendingRemoteSpawn = false; return; } EnemyDefinitionRegistry.Register(definition); if (pendingRemoteSpawn.NetworkId != 0 && NetworkIdentity.TryGet(pendingRemoteSpawn.NetworkId, out var existingIdentity) && existingIdentity != null) { var existingInstance = existingIdentity.gameObject; if (existingInstance != null) { existingInstance.SetActive(true); existingInstance.transform.SetPositionAndRotation(pendingRemoteSpawn.Position + definition.PrefabPivotOffset, pendingRemoteSpawn.Rotation); hasPendingRemoteSpawn = false; } return; } bool success = spawner != null && spawner.TrySpawn(definition, pendingRemoteSpawn.Position); if (success) { return; } GameObject instance = pooler != null ? pooler.Spawn(definition, pendingRemoteSpawn.Position, pendingRemoteSpawn.Rotation) : null; if (instance == null) { Debug.LogWarning($"[SteamEnemySpawnerNetworkBridge] Fallback spawn failed for '{pendingRemoteSpawn.DefinitionId}'."); hasPendingRemoteSpawn = false; return; } ApplyPendingRemoteSpawn(instance, definition); } private void HandleEnemyDespawnMessage(NetworkMessage message) { if (IsHostClient()) { return; } EnemyDespawnMessage despawnMessage = EnemyDespawnMessage.Deserialize(message.Payload); if (despawnMessage.NetworkId == 0) { return; } if (!NetworkIdentity.TryGet(despawnMessage.NetworkId, out var identity) || identity == null) { return; } SteamCharacterStateCache.RemoveState(identity.NetworkId); var pooled = identity.GetComponent(); if (pooled != null) { pooled.ReturnToPool(); } else { identity.gameObject.SetActive(false); } } private void ApplyPendingRemoteSpawn(GameObject instance, EnemyDefinition definition) { if (!hasPendingRemoteSpawn || instance == null) { return; } hasPendingRemoteSpawn = false; instance.transform.rotation = pendingRemoteSpawn.Rotation; if (definition != null) { EnemyDefinitionRegistry.Register(definition); } var identity = instance.GetComponent(); if (identity != null && pendingRemoteSpawn.NetworkId != 0 && identity.NetworkId != pendingRemoteSpawn.NetworkId) { identity.SetNetworkId(pendingRemoteSpawn.NetworkId); } } private bool IsHostClient() { RefreshNetworkManager(); return networkManager != null && networkManager.IsHost; } } }