using Game.Scripts.Runtime.Data; using Game.Scripts.Runtime.Game; using Game.Scripts.Runtime.Spawning; using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { [DefaultExecutionOrder(-140)] [DisallowMultipleComponent] public class SteamGameControllerNetworkBridge : MonoBehaviour { [Header("References")] [SerializeField] private GameController gameController; [SerializeField] private EnemySpawner enemySpawner; [Header("Heartbeat")] [SerializeField, Min(0.1f)] private float heartbeatInterval = 0.5f; private SteamCoopNetworkManager networkManager; private bool handlersRegistered; private bool controllerSubscribed; private bool cachedAuthority; private float heartbeatTimer; private int lastWaveBroadcast = -1; private void Awake() { gameController ??= GetComponent(); enemySpawner ??= GetComponent(); cachedAuthority = DetermineAuthority(); ApplyAuthorityOverride(cachedAuthority); RegisterBossDefinitions(); } private void OnEnable() { RefreshNetworkManager(); RegisterHandlers(); SubscribeControllerEvents(); cachedAuthority = DetermineAuthority(); ApplyAuthorityOverride(cachedAuthority); heartbeatTimer = heartbeatInterval; RegisterBossDefinitions(); } private void OnDisable() { UnsubscribeControllerEvents(); UnregisterHandlers(); } private void Update() { RefreshNetworkManager(); bool authority = DetermineAuthority(); if (authority != cachedAuthority) { cachedAuthority = authority; ApplyAuthorityOverride(authority); } if (authority) { RunHeartbeat(Time.deltaTime); } } private void RunHeartbeat(float deltaTime) { heartbeatTimer -= deltaTime; if (heartbeatTimer > 0f) { return; } heartbeatTimer = Mathf.Max(0.1f, heartbeatInterval); SendState(GameStateEvent.Heartbeat, lastWaveBroadcast); } private void SubscribeControllerEvents() { if (gameController == null || controllerSubscribed) { return; } gameController.OnGameStarted += HandleGameStarted; gameController.OnGamePaused += HandleGamePaused; gameController.OnGameResumed += HandleGameResumed; gameController.OnGameStopped += HandleGameStopped; gameController.OnWaveStarted += HandleWaveStarted; gameController.OnBossSpawned += HandleBossSpawned; controllerSubscribed = true; } private void UnsubscribeControllerEvents() { if (gameController == null || !controllerSubscribed) { return; } gameController.OnGameStarted -= HandleGameStarted; gameController.OnGamePaused -= HandleGamePaused; gameController.OnGameResumed -= HandleGameResumed; gameController.OnGameStopped -= HandleGameStopped; gameController.OnWaveStarted -= HandleWaveStarted; gameController.OnBossSpawned -= HandleBossSpawned; controllerSubscribed = false; } private void HandleGameStarted() { if (!DetermineAuthority()) { return; } SendState(GameStateEvent.Started, -1); lastWaveBroadcast = gameController != null ? gameController.CurrentWaveIndex : -1; } private void HandleGamePaused() { if (!DetermineAuthority()) { return; } SendState(GameStateEvent.Paused, gameController != null ? gameController.CurrentWaveIndex : -1); } private void HandleGameResumed() { if (!DetermineAuthority()) { return; } SendState(GameStateEvent.Resumed, gameController != null ? gameController.CurrentWaveIndex : -1); } private void HandleGameStopped() { if (!DetermineAuthority()) { return; } SendState(GameStateEvent.Stopped, gameController != null ? gameController.CurrentWaveIndex : -1); lastWaveBroadcast = -1; } private void HandleWaveStarted(int waveIndex) { if (!DetermineAuthority()) { return; } int previousWave = lastWaveBroadcast >= 0 ? lastWaveBroadcast : waveIndex - 1; lastWaveBroadcast = waveIndex; SendState(GameStateEvent.WaveAdvanced, previousWave); } private void HandleBossSpawned(EnemyDefinition boss, int count) { if (!DetermineAuthority()) { return; } EnemyDefinitionRegistry.Register(boss); string definitionId = EnemyDefinitionRegistry.ResolveId(boss); SendState(GameStateEvent.BossSpawned, lastWaveBroadcast, definitionId, count); } private void SendState(GameStateEvent evt, int previousWave, string definitionId = "", int count = 0) { if (networkManager == null || gameController == null) { return; } float elapsed = gameController.Elapsed; int currentWave = gameController.CurrentWaveIndex; bool isPaused = gameController.IsPaused; bool isRunning = gameController.IsGameActive; var message = new GameStateMessage(evt, elapsed, currentWave, previousWave, isPaused, isRunning, definitionId, count); byte[] payload = GameStateMessage.Serialize(message); var sendType = evt == GameStateEvent.Heartbeat ? EP2PSend.k_EP2PSendUnreliableNoDelay : EP2PSend.k_EP2PSendReliable; networkManager.SendToAll(NetworkMessageType.GameState, payload, sendType); } private void RegisterHandlers() { if (handlersRegistered) { return; } RefreshNetworkManager(); if (networkManager == null) { return; } networkManager.RegisterHandler(NetworkMessageType.GameState, HandleGameStateMessage); handlersRegistered = true; } private void UnregisterHandlers() { if (!handlersRegistered || networkManager == null) { return; } networkManager.UnregisterHandler(NetworkMessageType.GameState, HandleGameStateMessage); handlersRegistered = false; } private void HandleGameStateMessage(NetworkMessage message) { if (DetermineAuthority()) { return; } GameStateMessage state = GameStateMessage.Deserialize(message.Payload); ApplyRemoteState(state); } private void ApplyRemoteState(GameStateMessage state) { if (gameController == null) { return; } switch (state.Event) { case GameStateEvent.Started: if (!gameController.IsGameActive) { gameController.StartGame(); } gameController.SetElapsedFromRemote(state.Elapsed); gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused); gameController.ApplyRemoteWaveStarted(state.CurrentWave); lastWaveBroadcast = state.CurrentWave; break; case GameStateEvent.Paused: if (!gameController.IsPaused && gameController.IsGameActive) { gameController.PauseGame(); } gameController.SetElapsedFromRemote(state.Elapsed); gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused); break; case GameStateEvent.Resumed: if (gameController.IsPaused) { gameController.ResumeGame(); } gameController.SetElapsedFromRemote(state.Elapsed); gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused); break; case GameStateEvent.Stopped: if (gameController.IsGameActive) { gameController.StopGame(); } lastWaveBroadcast = -1; gameController.SetElapsedFromRemote(state.Elapsed); gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused); break; case GameStateEvent.WaveAdvanced: gameController.SetElapsedFromRemote(state.Elapsed); gameController.ApplyRemoteWaveAdvance(state.PreviousWave, state.CurrentWave); lastWaveBroadcast = state.CurrentWave; break; case GameStateEvent.BossSpawned: gameController.SetElapsedFromRemote(state.Elapsed); if (EnemyDefinitionRegistry.TryGet(state.DefinitionId, out var bossDefinition)) { gameController.ApplyRemoteBossSpawned(bossDefinition, state.Count); } break; case GameStateEvent.Heartbeat: gameController.SetElapsedFromRemote(state.Elapsed); gameController.SetRunningFlagsFromRemote(state.IsRunning, state.IsPaused); if (state.CurrentWave >= 0 && state.CurrentWave != gameController.CurrentWaveIndex) { gameController.ApplyRemoteWaveAdvance(gameController.CurrentWaveIndex, state.CurrentWave); lastWaveBroadcast = state.CurrentWave; } break; } } 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) { gameController?.SetAuthorityOverride(authority); enemySpawner?.SetAuthorityOverride(authority); } private void RegisterBossDefinitions() { if (gameController?.BossSchedule?.Events == null) { return; } foreach (var bossEvent in gameController.BossSchedule.Events) { if (bossEvent?.Boss == null) { continue; } EnemyDefinitionRegistry.Register(bossEvent.Boss); } } } }