Files
megakoop/Game/Scripts/Networking/SteamEnemySpawnerNetworkBridge.cs
2025-10-27 14:09:52 +01:00

363 lines
11 KiB
C#

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<EnemyDefinition> 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<EnemySpawner>();
gameController ??= GetComponent<GameController>();
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<NetworkIdentity>();
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<NetworkIdentity>();
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;
}
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<PooledInstance>();
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<NetworkIdentity>();
if (identity != null && pendingRemoteSpawn.NetworkId != 0 && identity.NetworkId != pendingRemoteSpawn.NetworkId)
{
NetworkIdAllocator.SyncEnemyCursor(pendingRemoteSpawn.NetworkId);
identity.SetNetworkId(pendingRemoteSpawn.NetworkId);
}
}
private bool IsHostClient()
{
RefreshNetworkManager();
return networkManager != null && networkManager.IsHost;
}
}
}