using UnityEngine; using GeoSus.Client; using Subsystems; using System.Collections; using System; using TMPro; using UnityEngine.SceneManagement; public class GameManager : MonoBehaviour { // Singleton public static GameManager Instance { get; private set; } [Header("Subsystems")] public GameManager_Network networkSubsystem; public GameManager_UI uiSubsystem; public GameManager_Map mapSubsystem; public GameManager_Input inputSubsystem; public GameManager_Tasks taskSubsystem; public GameClient gameClient; [Header("Player Info")] public string displayName; [Header("Scene Management")] [SerializeField] public string firstMenuScene = "main menu asi idk lol"; [Header("UI Elements (Client.unity)")] // Canvas names in Client.unity — found at runtime in OnSceneLoaded private const string CanvasNameJoinCreate = "LobbySelector"; private const string CanvasNameInLobby = "InLobby"; private const string CanvasNameLoading = "LoadingScreen"; private const string CanvasNameGame = "InGame"; [Header("Map")] // MapCenterPoint and Player are in Client.unity — wired at runtime in OnSceneLoaded. // buildingSettings/pathwaySettings/areaSettings must be assigned in SampleScene Inspector. public BuildingSettings buildingSettings; public PathwaySettings pathwaySettings; public AreaSettings areaSettings; [Header("Lobby Settings")] public double pendingRadius = 500; public int pendingImpostorCount = 1; public int pendingTaskCount = 5; [Header("Task Minigames (round-robin)")] [SerializeField] public string[] minigameScenes = { "MiniGame-Kabely", "MiniGame-InsertKeys", "MiniGame-FlappyBird", "MiniGame-ThrowInHole" }; [Header("Debug")] public bool testMode = false; private GameClient _secondClient; private GameClient _thirdClient; private GameManager_Network _secondNetwork; private GameManager_Network _thirdNetwork; void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); } void Start() { if (string.IsNullOrEmpty(displayName)) displayName = PlayerPrefs.GetString("PlayerName", GenerateUsername()); gameClient = new GameClient(GenerateUUID(), displayName); networkSubsystem = new GameManager_Network(gameClient, this); mapSubsystem = new GameManager_Map(gameClient, null, buildingSettings, pathwaySettings, areaSettings); uiSubsystem = new GameManager_UI(gameClient); inputSubsystem = new GameManager_Input(gameClient, null, testMode); taskSubsystem = new GameManager_Tasks(gameClient, minigameScenes, this); if (testMode) { _secondClient = new GameClient(GenerateUUID(), GenerateUsername()); _secondNetwork = new GameManager_Network(_secondClient, null); _thirdClient = new GameClient(GenerateUUID(), GenerateUsername()); _thirdNetwork = new GameManager_Network(_thirdClient, null); _secondNetwork.OpenConnection(); _thirdNetwork.OpenConnection(); } networkSubsystem.OpenConnection(); // Load main menu after GameManager is ready if (!string.IsNullOrEmpty(firstMenuScene)) SceneManager.LoadScene(firstMenuScene, LoadSceneMode.Single); } private void Update() { // Tick the SDK dispatcher so callbacks fire on main thread gameClient?.Update(); if (testMode) { _secondClient?.Update(); _thirdClient?.Update(); } if (gameClient?.CurrentLobbyState != null) { uiSubsystem?.UpdateLobbyUI(); taskSubsystem?.UpdateProximity(); } if (gameClient?.MyRole == PlayerRole.Impostor) UpdateKillCooldown(); inputSubsystem?.positionCheck(); } void OnEnable() { SceneManager.sceneLoaded += OnSceneLoaded; } void OnDisable() { SceneManager.sceneLoaded -= OnSceneLoaded; } /// /// After Client.unity loads, re-bind all canvas/HUD references because /// those GameObjects don't exist in the Art menu scenes. /// private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { if (scene.name == "Client") { var roots = scene.GetRootGameObjects(); // Find a root or deep GameObject by name in the loaded scene GameObject FindGO(string n) { foreach (var go in roots) { if (go.name == n) return go; var found = go.transform.Find(n); if (found != null) return found.gameObject; } return null; } Canvas FindCanvas(string n) { var go = FindGO(n); return go != null ? go.GetComponent() : null; } // ── Build HUD BEFORE BindClientScene so FindTMP/Find can locate new elements ── var inGameGO = FindGO("InGame"); if (inGameGO != null) { var builder = inGameGO.GetComponent() ?? inGameGO.AddComponent(); builder.BuildNow(); } // ── Wire canvases (after HUD is built) ── uiSubsystem?.BindClientScene( FindCanvas(CanvasNameJoinCreate), FindCanvas(CanvasNameInLobby), FindCanvas(CanvasNameLoading), FindCanvas(CanvasNameGame)); // ── Wire map center point and player capsule ── var mapCenter = FindGO("MapCenterPoint"); var player = FindGO("Capsule"); mapSubsystem?.SetMapCenterPoint(mapCenter); inputSubsystem?.SetPlayerObject(player); // ── Attach camera controller to Main Camera ── var mainCamGO = FindGO("Main Camera"); if (mainCamGO != null) { var camCtrl = mainCamGO.GetComponent() ?? mainCamGO.AddComponent(); camCtrl.SetTarget(player); } // If MapDataReady arrived before Client scene finished loading, // this will build the map now that scene references are valid. networkSubsystem?.OnClientSceneReady(); } else if (scene.name == "create" || scene.name == "join loading") { // Lobby scene just loaded — ensure LobbyDisplayUI refreshes once // its Start() has run and registered itself (happens before Update). uiSubsystem?.NotifyLobbyChanged(); } } private float _killCooldownSeconds = 0f; private const float KillCooldownDuration = 20f; private void UpdateKillCooldown() { if (_killCooldownSeconds > 0) { _killCooldownSeconds -= Time.deltaTime; // Mirror into GameState so UI reads from the single source of truth if (networkSubsystem?.State != null) networkSubsystem.State.KillCooldownRemaining = _killCooldownSeconds; uiSubsystem?.SetKillCooldownText($"Kill: {Mathf.CeilToInt(_killCooldownSeconds)}s"); } else { _killCooldownSeconds = 0f; if (networkSubsystem?.State != null) networkSubsystem.State.KillCooldownRemaining = 0; uiSubsystem?.SetKillCooldownText(""); } } /// /// Called by the ActionButton. Routes to kill / report / emergency / use-task /// depending on current proximity state. /// public void PerformAction() { if (uiSubsystem == null || uiSubsystem.IsPlayerDead) return; bool isImpostor = gameClient?.MyRole == PlayerRole.Impostor; // 1. Nearby task → USE var nearbyTask = taskSubsystem?.NearbyTask; if (nearbyTask != null && !isImpostor) { taskSubsystem.TriggerNearbyTask(); return; } // 2. Nearby body → REPORT if (!uiSubsystem.IsCommsBlackout) { var nearbyBody = gameClient?.FindNearbyBody(5.0); if (nearbyBody != null) { gameClient.ReportBody(nearbyBody.BodyId); return; } // 3. Near map centre → EMERGENCY if (gameClient?.CurrentLobbyState?.MapData != null) { double distToCenter = gameClient.MyPosition.DistanceTo(gameClient.CurrentLobbyState.MapData.Center); if (distToCenter <= 5.0) { gameClient.CallEmergencyMeeting(); return; } } } // 4. Impostor kill if (isImpostor && _killCooldownSeconds <= 0) { var targetUuid = gameClient?.FindNearbyPlayer(5.0); if (!string.IsNullOrEmpty(targetUuid)) { gameClient.Kill(targetUuid); _killCooldownSeconds = KillCooldownDuration; } } } /// Called by Impostor sabotage buttons. public void StartSabotage(int typeIndex) { gameClient?.Send(new GeoSus.Client.StartSabotage { SabotageType = (SabotageType)typeIndex }); } /// Called by the meeting vote buttons. Pass null to skip. public void CastVote(string targetUuid) { gameClient?.Vote(targetUuid); } protected string GenerateUUID() { return System.Guid.NewGuid().ToString(); } protected string GenerateUsername() { return "Player" + UnityEngine.Random.Range(1000, 9999).ToString(); } // Called by HostLobbyUI public void CreateLobbyButton() { // Use current GPS position if available, else hardcoded fallback double lat = 50.7727264, lon = 15.0719876; if (inputSubsystem?.LastKnownPosition != null) { lat = inputSubsystem.LastKnownPosition.Value.Lat; lon = inputSubsystem.LastKnownPosition.Value.Lon; } networkSubsystem.CreateLobby(lat, lon, pendingRadius, pendingImpostorCount, pendingTaskCount); if (testMode) StartCoroutine(ConnectTestClients()); } // Called by JoinLobbyUI with the code from the input field public void JoinLobbyButton(string code) { if (!string.IsNullOrEmpty(code)) networkSubsystem.JoinLobby(code); else Debug.LogWarning("Join code is empty!"); } public void LeaveLobbyButton() { networkSubsystem.LeaveLobby(); } public void StartGameButton() { networkSubsystem.StartGame(); } void OnApplicationQuit() { gameClient?.Disconnect(); _secondClient?.Disconnect(); _thirdClient?.Disconnect(); } IEnumerator ConnectTestClients() { // Wait until host lobby code exists float wait = 0f; while ((gameClient?.CurrentLobbyState == null || string.IsNullOrEmpty(gameClient.CurrentLobbyState.JoinCode)) && wait < 20f) { wait += 0.25f; yield return new WaitForSeconds(0.25f); } var joinCode = gameClient?.CurrentLobbyState?.JoinCode; if (string.IsNullOrEmpty(joinCode)) { Debug.LogWarning("[TestMode] Could not join test clients: join code not available."); yield break; } // Wait until helper clients are connected and handshake-complete wait = 0f; while (((_secondClient == null || !_secondClient.IsReady) || (_thirdClient == null || !_thirdClient.IsReady)) && wait < 20f) { wait += 0.25f; yield return new WaitForSeconds(0.25f); } if (_secondClient == null || _thirdClient == null || !_secondClient.IsReady || !_thirdClient.IsReady) { Debug.LogWarning("[TestMode] Helper clients are not ready, skipping auto-join."); yield break; } _secondNetwork?.JoinLobby(joinCode); _thirdNetwork?.JoinLobby(joinCode); Debug.Log($"[TestMode] Helper clients joined lobby with code {joinCode}."); } }