356 lines
11 KiB
C#
356 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 (!DetermineAuthority() || 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 (!DetermineAuthority() || 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 (DetermineAuthority())
|
|
{
|
|
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 (DetermineAuthority())
|
|
{
|
|
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)
|
|
{
|
|
identity.SetNetworkId(pendingRemoteSpawn.NetworkId);
|
|
}
|
|
}
|
|
}
|
|
}
|