using GeoSus.Client; using System.Collections; using System.Threading.Tasks; using UnityEngine; using System.Collections.Generic; using Subsystems; using System.Linq; using UnityEngine.SceneManagement; namespace Subsystems { public class GameManager_Network { private const string _serverAddress = "geosus.honzuvkod.dev"; private const int _serverPort = 7777; private GameClient _gameClient; private GameManager _manager; private bool _pendingMapBuild; /// /// Authoritative game state; written here, read by GameManager_UI. /// public GameState State { get; } = new GameState(); public GameManager_Network(GameClient gameClient, GameManager manager) { _gameClient = gameClient; _manager = manager; RegisterEventHandlers(); } public async void OpenConnection() { int retries = 0; int delayMs = 5000; while (true) { Task state = _gameClient.ConnectAsync(_serverAddress, _serverPort); await state; if (state.Result) { Debug.Log("Connected to server."); break; } retries++; if (retries >= 10) { Debug.LogError("Failed to connect after 10 attempts. Giving up."); break; } Debug.Log($"Failed to connect (attempt {retries}). Retrying in {delayMs / 1000}s..."); await Task.Delay(delayMs); delayMs = Mathf.Min(delayMs * 2, 30000); } } public void RegisterEventHandlers() { _gameClient.OnConnected += OnConnected; _gameClient.OnDisconnected += OnDisconnected; _gameClient.OnError += OnError; _gameClient.OnMessage += OnMessage; _gameClient.OnGameEvent += OnGameEvent; } private void OnConnected() => Debug.Log("Successfully connected to the server."); private void OnError(string e) => Debug.LogError($"Network error: {e}"); private void OnDisconnected(string reason) { Debug.Log($"Disconnected: {reason}"); if (reason != "Disposed" && _manager != null) _manager.StartCoroutine(ReconnectAfterDelay(3f)); } private IEnumerator ReconnectAfterDelay(float seconds) { yield return new UnityEngine.WaitForSeconds(seconds); Debug.Log("Attempting to reconnect..."); OpenConnection(); } private void OnMessage(Message message) { switch (message.Type) { case "CreateLobbyResponse": HandleCreateLobbyResponse(message as CreateLobbyResponse); break; case "JoinLobbyResponse": HandleJoinLobbyResponse(message as JoinLobbyResponse); break; case "PositionBroadcast": HandlePositionBroadcast(message as PositionBroadcast); break; case "Ack": case "GameEvent": break; default: Debug.Log("Received message of type: " + message.Type); break; } } private void OnGameEvent(GameEvent gameEvent) { // Always sync player list from lobby state after any event SyncPlayersFromLobby(); switch (gameEvent.EventType) { case "PlayerJoined": case "PlayerLeft": case "HostChanged": _manager?.uiSubsystem?.NotifyLobbyChanged(); break; case "GameStarting": State.Phase = GamePhase.Loading; HandleGameStarting(); break; case "MapDataReady": HandleMapDataReady(); break; case "GameStarted": State.Phase = GamePhase.Playing; break; case "RoleAssigned": HandleRoleAssigned(gameEvent); break; case "TaskCompleted": HandleTaskCompleted(gameEvent); break; case "PlayerKilled": HandlePlayerKilled(gameEvent); break; case "BodyReported": case "EmergencyMeetingCalled": Toast("Meeting called! Head to the meeting point."); break; case "MeetingStarted": HandleMeetingStarted(gameEvent); break; case "PlayerVoted": HandlePlayerVoted(gameEvent); break; case "VotingClosed": HandleVotingClosed(gameEvent); break; case "GameEnded": HandleGameEnded(gameEvent); break; case "ReturnedToLobby": HandleReturnedToLobby(); break; case "SabotageStarted": HandleSabotageStarted(gameEvent); break; case "SabotageRepaired": case "SabotageMeltdown": State.ActiveSabotage = null; _manager?.uiSubsystem?.HideSabotageTimer(); _manager?.mapSubsystem?.ClearSabotageMarkers(); break; case "MapDataError": Debug.LogError("Server could not generate map data."); break; default: Debug.Log("GameEvent: " + gameEvent.EventType); break; } } // ── Lobby responses ─────────────────────────────────────────────────── private void HandleCreateLobbyResponse(CreateLobbyResponse message) { if (message == null) return; if (message.Success) { Debug.Log($"Lobby created. Code: {message.JoinCode}"); SceneManager.LoadScene("create", LoadSceneMode.Single); _manager?.uiSubsystem?.NotifyLobbyChanged(); } else { Debug.LogError("Failed to create lobby: " + message.Error); } } private void HandleJoinLobbyResponse(JoinLobbyResponse message) { if (message == null) return; if (message.Success) { Debug.Log($"Joined lobby: {message.LobbyId}"); SceneManager.LoadScene("join loading", LoadSceneMode.Single); _manager?.uiSubsystem?.NotifyLobbyChanged(); } else { Debug.LogError("Failed to join lobby: " + message.Error); } } // ── Game flow ───────────────────────────────────────────────────────── private void HandleGameStarting() { _pendingMapBuild = false; // Reset per-game state State.MyRole = null; State.IsDead = false; State.MyTasks = new List(); State.MyCompletedTaskIds = new HashSet(); State.TotalCompleted = 0; State.TotalRequired = 0; State.ActiveMeeting = null; State.LastVoteResult = null; State.VotedPlayerIds = new HashSet(); State.ActiveSabotage = null; State.GameEndData = null; State.KillCooldownRemaining = 0; SceneManager.LoadScene("Client", LoadSceneMode.Single); } private void HandleMapDataReady() { _pendingMapBuild = true; TryBuildMapAndMarkers(); } public void OnClientSceneReady() { TryBuildMapAndMarkers(); } private void TryBuildMapAndMarkers() { if (!_pendingMapBuild) return; if (_manager?.mapSubsystem == null || !_manager.mapSubsystem.IsSceneReady) return; if (_gameClient?.CurrentLobbyState?.MapData == null) return; _manager.mapSubsystem.BuildMap(); _manager.mapSubsystem.CreateTaskMarkers(_gameClient.MyTasks); _pendingMapBuild = false; Debug.Log("[Network] Map built."); } private void HandleRoleAssigned(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null || payload.ClientUuid != _gameClient.ClientUuid) return; State.MyRole = payload.Role; State.MyTasks = payload.Tasks ?? new List(); State.MyCompletedTaskIds.Clear(); Debug.Log($"Role: {payload.Role}, Tasks: {State.MyTasks.Count}"); _manager?.taskSubsystem?.Initialize(State.MyTasks); } private void HandleTaskCompleted(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; // Track if it's our task if (payload.ClientUuid == _gameClient.ClientUuid) State.MyCompletedTaskIds.Add(payload.TaskId); State.TotalCompleted = payload.TotalCompleted; State.TotalRequired = payload.TotalTasks; _manager?.uiSubsystem?.UpdateTaskProgress(payload.TotalCompleted, payload.TotalTasks); _manager?.mapSubsystem?.RemoveTaskMarker(payload.TaskId); } private void HandlePlayerKilled(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; _manager?.mapSubsystem?.CreateBodyMarker(payload.BodyId, payload.Location); if (payload.VictimId == _gameClient.ClientUuid) { State.IsDead = true; _manager?.uiSubsystem?.OnLocalPlayerDied(); } // Update player state in our list var p = State.Players.Find(x => x.ClientUuid == payload.VictimId); if (p != null) p.State = PlayerState.Dead; } private void HandleMeetingStarted(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; State.Phase = GamePhase.Meeting; State.ActiveMeeting = payload; State.VotedPlayerIds = new HashSet(); State.LastVoteResult = null; SyncPlayersFromLobby(); _manager?.uiSubsystem?.ShowMeetingPanel(State.Players, payload); } private void HandlePlayerVoted(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; State.VotedPlayerIds.Add(payload.VoterId); } private void HandleVotingClosed(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; State.Phase = GamePhase.Playing; State.ActiveMeeting = null; State.LastVoteResult = payload; // Mark ejected player dead in our list if (!string.IsNullOrEmpty(payload.EjectedPlayerId)) { var p = State.Players.Find(x => x.ClientUuid == payload.EjectedPlayerId); if (p != null) p.State = PlayerState.Dead; } _manager?.uiSubsystem?.ShowVoteResult(payload, State.Players); _manager?.mapSubsystem?.ClearBodyMarkers(); } private void HandleGameEnded(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; State.Phase = GamePhase.Ended; State.GameEndData = payload; _manager?.uiSubsystem?.ShowGameEndPanel(payload, _gameClient.ClientUuid); } private void HandleReturnedToLobby() { State.Phase = GamePhase.Lobby; if (_gameClient.IsOwner) SceneManager.LoadScene("create", LoadSceneMode.Single); else SceneManager.LoadScene("join loading", LoadSceneMode.Single); } private void HandleSabotageStarted(GameEvent evt) { var payload = evt.GetPayload(); if (payload == null) return; State.ActiveSabotage = payload; _manager?.mapSubsystem?.CreateSabotageMarkers(payload.RepairStations); if (payload.Type == SabotageType.CriticalMeltdown && payload.Deadline.HasValue) _manager?.uiSubsystem?.ShowSabotageTimer(payload.Deadline.Value); if (payload.Type == SabotageType.CommsBlackout) _manager?.uiSubsystem?.SetCommsBlackout(true); } private void HandlePositionBroadcast(PositionBroadcast broadcast) { if (broadcast == null) return; _manager?.mapSubsystem?.UpdatePlayerAvatars(_gameClient.PlayerPositions, _gameClient.ClientUuid); } // ── Helpers ─────────────────────────────────────────────────────────── private void SyncPlayersFromLobby() { var lobby = _gameClient.CurrentLobbyState; if (lobby?.Players != null) State.Players = lobby.Players; } private void Toast(string message) { State.ToastMessage = message; State.ToastExpiry = UnityEngine.Time.time + 4f; } // ── Send helpers ────────────────────────────────────────────────────── public void CreateLobby(double lat, double lon, double radius = 500, int impostorCount = 1, int taskCount = 5) { _gameClient.CreateLobby(new Position(lat, lon), impostorCount, taskCount, null, radius); } public void JoinLobby(string joinCode) { try { _gameClient.JoinLobby(joinCode); } catch (System.Exception ex) { Debug.LogError("JoinLobby error: " + ex.Message); } } public void LeaveLobby() { _gameClient.LeaveLobby(); State.Phase = GamePhase.Lobby; SceneManager.LoadScene(_manager?.firstMenuScene ?? "main menu asi idk lol", LoadSceneMode.Single); } public void StartGame() { _gameClient.StartGame(); } } }