371 lines
12 KiB
C#
371 lines
12 KiB
C#
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// After Client.unity loads, re-bind all canvas/HUD references because
|
|
/// those GameObjects don't exist in the Art menu scenes.
|
|
/// </summary>
|
|
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<Canvas>() : null;
|
|
}
|
|
|
|
// ── Build HUD BEFORE BindClientScene so FindTMP/Find can locate new elements ──
|
|
var inGameGO = FindGO("InGame");
|
|
if (inGameGO != null)
|
|
{
|
|
var builder = inGameGO.GetComponent<InGameHUDBuilder>()
|
|
?? inGameGO.AddComponent<InGameHUDBuilder>();
|
|
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<MapCameraController>()
|
|
?? mainCamGO.AddComponent<MapCameraController>();
|
|
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("");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by the ActionButton. Routes to kill / report / emergency / use-task
|
|
/// depending on current proximity state.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Called by Impostor sabotage buttons.</summary>
|
|
public void StartSabotage(int typeIndex)
|
|
{
|
|
gameClient?.Send(new GeoSus.Client.StartSabotage { SabotageType = (SabotageType)typeIndex });
|
|
}
|
|
|
|
/// <summary>Called by the meeting vote buttons. Pass null to skip.</summary>
|
|
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}.");
|
|
}
|
|
}
|