Zabiju je

This commit is contained in:
2026-04-26 13:30:33 +02:00
parent 208696487e
commit 700e6bfbfc
143 changed files with 11027 additions and 1298 deletions

View File

@@ -4,43 +4,53 @@ using Subsystems;
using System.Collections;
using System;
using TMPro;
/*
GameManager - hlavní tøida pro správu hry
GameManager_Network - subsystém pro správu komunikace se serverem
GameManager_Game - subsystém pro správu logiky hry (sabotáže, tasky, atd.)
GameManager_Map - subsystém pro správu mapy a prostøedí
GameManager_Input - subsystém pro správu vstupu od hráèe
GameManager_UI - subsystém pro správu uživatelského rozhraní
GamaManager_Stats - subsystém pro správu statistik pro server
*/
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
[Header("Subsystems")]
protected GameManager_Network networkSubsystem;
protected GameManager_UI uiSubsystem;
protected GameManager_Map mapSubsystem;
protected GameManager_Input inputSubsystem;
// Singleton
public static GameManager Instance { get; private set; }
protected GameClient gameClient;
[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("UI Elements")]
public Canvas JoinCreateLobby;
public Canvas InLobby;
public Canvas LoadingScreen;
public Canvas GameScreen;
[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")]
public GameObject MapCenterPoint;
// 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("GPS")]
public GameObject Player;
[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;
@@ -49,100 +59,307 @@ public class GameManager : MonoBehaviour
private GameManager_Network _secondNetwork;
private GameManager_Network _thirdNetwork;
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
void Start()
{
DontDestroyOnLoad(this);
if (displayName == null || displayName == "")
{
displayName = GenerateUsername();
}
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);
_secondNetwork = new GameManager_Network(_secondClient, null);
_thirdClient = new GameClient(GenerateUUID(), GenerateUsername());
_thirdNetwork = new GameManager_Network(_thirdClient);
_secondNetwork.OpenConection();
_thirdNetwork.OpenConection();
_thirdNetwork = new GameManager_Network(_thirdClient, null);
_secondNetwork.OpenConnection();
_thirdNetwork.OpenConnection();
}
gameClient = new GameClient(GenerateUUID(), displayName);
uiSubsystem = new GameManager_UI(gameClient, JoinCreateLobby, InLobby, LoadingScreen, GameScreen);
networkSubsystem = new GameManager_Network(gameClient);
mapSubsystem = new GameManager_Map(gameClient, MapCenterPoint, buildingSettings, pathwaySettings, areaSettings);
inputSubsystem = new GameManager_Input(gameClient, Player, testMode);
networkSubsystem.OpenConection();
networkSubsystem.OpenConnection();
// Load main menu after GameManager is ready
if (!string.IsNullOrEmpty(firstMenuScene))
SceneManager.LoadScene(firstMenuScene, LoadSceneMode.Single);
}
private void Update()
{
if (gameClient.CurrentLobbyState != null)
{
uiSubsystem.UpdateLobbyUI();
}
try
{
if (gameClient.CurrentLobbyState.MapDataReady)
{
mapSubsystem.BuildMap();
gameClient.CurrentLobbyState.MapDataReady = false;
}
}
catch (NullReferenceException ex) { }
inputSubsystem.positionCheck();
}
protected string GenerateUUID()
{
string UUID = System.Guid.NewGuid().ToString();
Debug.Log(UUID);
return UUID;
}
protected string GenerateUsername()
{
string Username = UnityEngine.Random.Range(0,10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString() + UnityEngine.Random.Range(0, 10).ToString();
Debug.Log(Username);
return Username;
}
public void CreateLobbyButton()
{
networkSubsystem.CrateLobby(50.7727264, 15.0719876);
// Tick the SDK dispatcher so callbacks fire on main thread
gameClient?.Update();
if (testMode)
{
StartCoroutine(ConnectTestClients());
_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();
}
}
public void JoinLobbyButton()
// ── Kill cooldown ─────────────────────────────────────────────────────────
private float _killCooldownSeconds = 0f;
private const float KillCooldownDuration = 20f;
private void UpdateKillCooldown()
{
TMP_InputField joinCode = JoinCreateLobby.transform.Find("InputCode").GetComponent<TMP_InputField>();
if (joinCode.text != null && joinCode.text != "")
if (_killCooldownSeconds > 0)
{
networkSubsystem.JoinLobby(joinCode.text);
_killCooldownSeconds -= Time.deltaTime;
uiSubsystem?.SetKillCooldownText($"Kill: {Mathf.CeilToInt(_killCooldownSeconds)}s");
}
else
{
Debug.Log("Join code is empty!");
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();
gameClient?.Disconnect();
_secondClient?.Disconnect();
_thirdClient?.Disconnect();
_thirdClient?.Disconnect();
}
IEnumerator ConnectTestClients()
{
yield return new WaitForSeconds(2f);
_secondNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
_thirdNetwork.JoinLobby(gameClient.CurrentLobbyState.JoinCode);
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}.");
}
}

View File

@@ -50,74 +50,131 @@ namespace Subsystems
private Position _lastSentPosition;
private GameObject _player;
private bool _testMode;
private GPSState _GPSState = GPSState.Uninitialized;
private float _speed = 0.00001f;
private Position _mapCenter;
private CoroutineHost _coroutineHost = new CoroutineHost();
private CoroutineHost _coroutineHost;
private int _gpsRetryCount = 0;
private const int _maxGpsRetries = 5;
private float _lastPositionSendTime;
private const float _positionKeepAliveSeconds = 1.0f;
/// <summary>Last known GPS position (for CreateLobby centre point)</summary>
public Position? LastKnownPosition => _currentPosition.Lat != 0 || _currentPosition.Lon != 0 ? _currentPosition : (Position?)null;
public GameManager_Input(GameClient gameClient, GameObject player, bool testMode)
{
_gameClient = gameClient;
_player = player;
_testMode = testMode;
// CoroutineHost needs a MonoBehaviour on a real GameObject
var hostGO = new UnityEngine.GameObject("_CoroutineHost");
UnityEngine.Object.DontDestroyOnLoad(hostGO);
_coroutineHost = hostGO.AddComponent<CoroutineHost>();
}
/// <summary>Called from OnSceneLoaded when Client.unity loads so the
/// Player capsule (which lives in Client.unity) can be wired at runtime.</summary>
public void SetPlayerObject(GameObject player) { _player = player; }
public void positionCheck()
{
var state = _gameClient?.CurrentLobbyState;
if (state == null || state.Phase != GamePhase.Playing)
return;
try
{
if (_gameClient.CurrentLobbyState.Phase == GamePhase.Playing)
if (_testMode)
{
if (_testMode)
if (_currentPosition == new Position(0, 0))
{
if (state.MapData == null)
return;
if (_currentPosition == null || _currentPosition == new Position(0, 0))
{
//Init blok
_currentPosition = _gameClient.CurrentLobbyState.MapData.Center;
_mapCenter = _gameClient.CurrentLobbyState.MapData.Center;
_lastSentPosition = _currentPosition;
}
//Init blok
_currentPosition = state.MapData.Center;
_mapCenter = state.MapData.Center;
_lastSentPosition = _currentPosition;
}
TestPlayerPosition();
TestPlayerPosition();
}
else
{
if (_GPSState == GPSState.Uninitialized)
{
_coroutineHost.StartCoroutine(InitiallizeGPS());
return;
}
else if (_GPSState == GPSState.Initializing)
{
return;
}
else if (_GPSState == GPSState.Running)
{
EnsureMapCenter();
TrySendCurrentPosition();
}
else
{
if (_GPSState == GPSState.Uninitialized)
Debug.Log("GPS failed, trying again...");
if (_gpsRetryCount < _maxGpsRetries)
{
_coroutineHost.StartCoroutine(InitiallizeGPS());
return;
}
else if (_GPSState == GPSState.Initializing)
{
return;
}
else if (_GPSState == GPSState.Running)
{
try
{
if (_currentPosition != _lastSentPosition)
{
_gameClient.UpdatePosition(_currentPosition);
_lastSentPosition = _currentPosition;
_player.transform.position = _currentPosition.ToLocalVector3(_mapCenter);
_player.transform.rotation = Quaternion.Euler(0, (float)CalculateHeading(_lastSentPosition.ToLocalVector3(_mapCenter), _currentPosition.ToLocalVector3(_mapCenter)), 0);
}
}
catch (Exception ex)
{
Debug.Log(ex);
}
_gpsRetryCount++;
_GPSState = GPSState.Uninitialized;
}
else
{
Debug.Log("GPS failed, trying again...");)
_GPSState = GPSState.Uninitialized;
Debug.LogWarning("GPS unavailable after max retries. Using last known position.");
// Keep _GPSState = Failed so we stop retrying
}
}
}
}
catch (NullReferenceException ex) { Debug.Log(ex); }
catch (Exception ex)
{
Debug.LogWarning($"[Input] positionCheck failed: {ex.Message}");
}
}
private void EnsureMapCenter()
{
if (_mapCenter.Lat != 0 || _mapCenter.Lon != 0)
return;
var md = _gameClient?.CurrentLobbyState?.MapData;
if (md != null)
_mapCenter = md.Center;
}
private void TrySendCurrentPosition()
{
bool moved = _currentPosition != _lastSentPosition;
bool keepAliveDue = (Time.time - _lastPositionSendTime) >= _positionKeepAliveSeconds;
if (!moved && !keepAliveDue)
return;
var previous = _lastSentPosition;
_gameClient.UpdatePosition(_currentPosition);
_lastSentPosition = _currentPosition;
_lastPositionSendTime = Time.time;
if (_player == null || (_mapCenter.Lat == 0 && _mapCenter.Lon == 0))
return;
var localCurrent = _currentPosition.ToLocalVector3(_mapCenter);
_player.transform.position = localCurrent;
if (previous.Lat == 0 && previous.Lon == 0)
return;
var heading = CalculateHeading(previous.ToLocalVector3(_mapCenter), localCurrent);
if (heading.HasValue)
_player.transform.rotation = Quaternion.Euler(0, (float)heading.Value, 0);
}
private void TestPlayerPosition()
{
double x = Input.GetAxis("Horizontal");
@@ -136,11 +193,7 @@ namespace Subsystems
_player.transform.position = localCurrent;
try
{
if (_currentPosition != _lastSentPosition)
{
_gameClient.UpdatePosition(_currentPosition);
_lastSentPosition = _currentPosition;
}
TrySendCurrentPosition();
}
catch
{
@@ -150,63 +203,42 @@ namespace Subsystems
}
private double? CalculateHeading(Vector3 first, Vector3 second)
{
double? heading = null;
if ((first - second).magnitude == 0)
{
return null;
}
else if (first.x == second.x && first.z < second.z)
{
return 0;
}
else if (first.x == second.x && first.z > second.z)
{
return 180;
}
else if (first.x > second.x && first.z == second.z)
{
return 270;
}
else if (first.x < second.x && first.z == second.z)
{
return 90;
}
else if (first.x < second.x && first.z < second.z)
{
heading = Math.Asin((second.z - first.z) / first.DistanceTo(second));
return (heading * 180) / Math.PI;
}
else if (first.x < second.x && first.z > second.z)
{
heading = Math.Asin((second.z - first.z) / first.DistanceTo(second));
return (heading * 180) / Math.PI + 180;
}
else if (first.x > second.x && first.z < second.z)
{
heading = Math.Asin((second.z - first.z) / first.DistanceTo(second));
return (heading * 180) / Math.PI - 90;
}
else if (first.x > second.x && first.z > second.z)
{
heading = Math.Asin((second.z - first.z) / first.DistanceTo(second));
return (heading * 180) / Math.PI - 90;
}
else
{
return heading;
}
if ((first - second).magnitude < 0.0001f) return null;
float dx = second.x - first.x;
float dz = second.z - first.z;
float heading = Mathf.Atan2(dx, dz) * Mathf.Rad2Deg;
if (heading < 0) heading += 360f;
return heading;
}
IEnumerator InitiallizeGPS()
{
_GPSState = GPSState.Initializing;
#if UNITY_ANDROID
// Request fine location permission if not already granted
if (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.FineLocation))
{
UnityEngine.Android.Permission.RequestUserPermission(UnityEngine.Android.Permission.FineLocation);
// Wait up to 10 seconds for user to respond to the permission dialog
float waited = 0f;
while (!UnityEngine.Android.Permission.HasUserAuthorizedPermission(UnityEngine.Android.Permission.FineLocation) && waited < 10f)
{
yield return new WaitForSeconds(0.5f);
waited += 0.5f;
}
}
#endif
if (!Input.location.isEnabledByUser)
{
Debug.LogError("Location not enabled on device or app does not have permission to access location");
_GPSState = GPSState.Failed;
yield break;
}
// Starts the location service.
float desiredAccuracyInMeters = 10f;
float updateDistanceInMeters = 10f;
float desiredAccuracyInMeters = 5f;
float updateDistanceInMeters = 1f;
Input.location.Start(desiredAccuracyInMeters, updateDistanceInMeters);
@@ -225,30 +257,34 @@ namespace Subsystems
Debug.LogError("Timed out");
yield break;
}
_GPSState = GPSState.Running;
yield return _coroutineHost.StartCoroutine(GPSService());
}
IEnumerator GPSService()
{
// Check if the user has location service enabled.
// If the connection failed this cancels location service use.
if (Input.location.status == LocationServiceStatus.Failed)
{
_GPSState = GPSState.Failed;
Debug.LogError("Unable to determine device location");
yield break;
}
else
{
// If the connection succeeded, this retrieves the device's current location and displays it in the Console window.
_currentPosition = new Position(Input.location.lastData.latitude, Input.location.lastData.longitude);
Debug.Log("Location: " + Input.location.lastData.latitude + " " + Input.location.lastData.longitude + " " + Input.location.lastData.altitude + " " + Input.location.lastData.horizontalAccuracy + " " + Input.location.lastData.timestamp);
yield return new WaitForSeconds(5f);
}
// Stops the location service if there is no need to query location updates continuously.
yield return _coroutineHost.StartCoroutine(GPSService());
_GPSState = GPSState.Running;
_gpsRetryCount = 0;
_coroutineHost.StartCoroutine(GPSService());
}
IEnumerator GPSService()
{
while (_GPSState == GPSState.Running)
{
if (Input.location.status == LocationServiceStatus.Failed)
{
_GPSState = GPSState.Failed;
Debug.LogError("Unable to determine device location");
yield break;
}
// Keep current GPS position fresh; sending is throttled in positionCheck().
var data = Input.location.lastData;
_currentPosition = new Position(data.latitude, data.longitude);
yield return new WaitForSeconds(0.5f);
}
}
}
}

View File

@@ -3,9 +3,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using UnityEditor;
using UnityEngine;
using UnityEngine.Localization.Pseudo;
using UnityEngine.UI;
@@ -13,8 +11,8 @@ namespace Subsystems{
[System.Serializable]
public class BuildingSettings
{
public Material ResidentalBuildingsMat;
public float ResidentalBuildingHeight;
public Material ResidentialBuildingsMat;
public float ResidentialBuildingHeight;
public Material CommercialBuildingsMat;
public float CommercialBuildingHeight;
public Material IndustrialBuildingsMat;
@@ -65,7 +63,14 @@ namespace Subsystems{
private BuildingSettings _buildingSettings;
private PathwaySettings _pathwaySettings;
private AreaSettings _areaSettings;
private const float _metersPerUnit = 1f;
private const float _metersPerUnit = 1f;
// Runtime marker collections
private Dictionary<string, GameObject> _taskMarkers = new Dictionary<string, GameObject>();
private Dictionary<string, GameObject> _bodyMarkers = new Dictionary<string, GameObject>();
private Dictionary<string, GameObject> _playerAvatars = new Dictionary<string, GameObject>();
private List<GameObject> _sabotageMarkers = new List<GameObject>();
public GameManager_Map(GameClient gameClient, GameObject mapCenterPoint, BuildingSettings buildingSettings, PathwaySettings pathwaySettings, AreaSettings areaSettings)
{
_gameClient = gameClient;
@@ -74,8 +79,25 @@ namespace Subsystems{
_pathwaySettings = pathwaySettings;
_areaSettings = areaSettings;
}
public bool IsSceneReady => _mapCenterPoint != null;
/// <summary>Called from OnSceneLoaded when Client.unity is loaded so the
/// MapCenterPoint (which lives in Client.unity) can be wired at runtime.</summary>
public void SetMapCenterPoint(GameObject go) { _mapCenterPoint = go; }
public void BuildMap()
{
if (_mapCenterPoint == null)
{
Debug.LogWarning("[Map] BuildMap skipped: MapCenterPoint is not yet bound.");
return;
}
if (_gameClient?.CurrentLobbyState?.MapData == null)
{
Debug.LogWarning("[Map] BuildMap skipped: no MapData in CurrentLobbyState.");
return;
}
ClearChildren();
_centerPosition = _gameClient.CurrentLobbyState.MapData.Center;
GameObject buildingsRoot = new GameObject("Buildings");
@@ -139,8 +161,8 @@ namespace Subsystems{
switch (b.BuildingType.ToLower())
{
case "residential":
mat = _buildingSettings.ResidentalBuildingsMat;
height = _buildingSettings.ResidentalBuildingHeight;
mat = _buildingSettings.ResidentialBuildingsMat;
height = _buildingSettings.ResidentialBuildingHeight;
break;
case "commercial":
mat = _buildingSettings.CommercialBuildingsMat;
@@ -372,5 +394,117 @@ namespace Subsystems{
return mesh;
}
#endregion
#region Markers
public void CreateTaskMarkers(List<GeoSus.Client.GameTask> tasks)
{
if (_mapCenterPoint == null) return;
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
{
var md = _gameClient?.CurrentLobbyState?.MapData;
if (md != null) _centerPosition = md.Center;
}
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
foreach (var task in tasks)
{
if (_taskMarkers.ContainsKey(task.TaskId)) continue;
var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.name = $"Task_{task.TaskId}";
go.transform.parent = _mapCenterPoint.transform;
go.transform.position = task.Location.ToLocalVector3(_centerPosition) + Vector3.up * 0.3f;
go.transform.localScale = Vector3.one * 0.5f;
var mr = go.GetComponent<MeshRenderer>();
if (mr) mr.material.color = Color.yellow;
_taskMarkers[task.TaskId] = go;
}
}
public void RemoveTaskMarker(string taskId)
{
if (_taskMarkers.TryGetValue(taskId, out var go))
{
UnityEngine.Object.Destroy(go);
_taskMarkers.Remove(taskId);
}
}
public void CreateBodyMarker(string bodyId, Position location)
{
if (_mapCenterPoint == null) return;
if (_bodyMarkers.ContainsKey(bodyId)) return;
var go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
go.name = $"Body_{bodyId}";
go.transform.parent = _mapCenterPoint?.transform;
go.transform.position = location.ToLocalVector3(_centerPosition) + Vector3.up * 0.15f;
go.transform.localScale = new Vector3(0.3f, 0.5f, 0.3f);
go.transform.rotation = Quaternion.Euler(90, 0, 0); // lying down
var mr = go.GetComponent<MeshRenderer>();
if (mr) mr.material.color = Color.red;
_bodyMarkers[bodyId] = go;
}
public void ClearBodyMarkers()
{
foreach (var go in _bodyMarkers.Values)
if (go) UnityEngine.Object.Destroy(go);
_bodyMarkers.Clear();
}
public void UpdatePlayerAvatars(Dictionary<string, PlayerPositionInfo> positions, string myUuid)
{
if (_mapCenterPoint == null) return;
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0)
{
var md = _gameClient?.CurrentLobbyState?.MapData;
if (md != null) _centerPosition = md.Center;
}
if (_centerPosition.Lat == 0 && _centerPosition.Lon == 0) return;
foreach (var kvp in positions)
{
string uuid = kvp.Key;
var info = kvp.Value;
if (!_playerAvatars.TryGetValue(uuid, out var go) || go == null)
{
go = GameObject.CreatePrimitive(PrimitiveType.Capsule);
go.name = $"Player_{uuid.Substring(0, Mathf.Min(8, uuid.Length))}";
go.transform.parent = _mapCenterPoint?.transform;
go.transform.localScale = Vector3.one * 0.4f;
_playerAvatars[uuid] = go;
}
go.transform.position = info.Position.ToLocalVector3(_centerPosition) + Vector3.up * 1f;
var mr = go.GetComponent<MeshRenderer>();
if (mr)
{
if (uuid == myUuid) mr.material.color = Color.green;
else if (info.State == GeoSus.Client.PlayerState.Dead) mr.material.color = Color.grey;
else mr.material.color = Color.white;
}
}
}
public void CreateSabotageMarkers(List<RepairStationInfo> stations)
{
foreach (var station in stations)
{
var go = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
go.name = $"Sabotage_{station.StationId}";
go.transform.parent = _mapCenterPoint?.transform;
go.transform.position = station.Location.ToLocalVector3(_centerPosition) + Vector3.up * 1f;
go.transform.localScale = new Vector3(0.5f, 2f, 0.5f);
var mr = go.GetComponent<MeshRenderer>();
if (mr) mr.material.color = new Color(1f, 0.5f, 0f); // orange
_sabotageMarkers.Add(go);
}
}
public void ClearSabotageMarkers()
{
foreach (var go in _sabotageMarkers)
if (go) UnityEngine.Object.Destroy(go);
_sabotageMarkers.Clear();
}
#endregion
}
}

View File

@@ -5,6 +5,7 @@ using UnityEngine;
using System.Collections.Generic;
using Subsystems;
using System.Linq;
using UnityEngine.SceneManagement;
namespace Subsystems
{
@@ -13,9 +14,20 @@ namespace Subsystems
private const string _serverAddress = "geosus.honzuvkod.dev";
private const int _serverPort = 7777;
private GameClient _gameClient;
private GameManager_Map _mapSubsystem;
public async void OpenConection()
private GameManager _manager; // may be null for test clients
private bool _pendingMapBuild;
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<bool> state = _gameClient.ConnectAsync(_serverAddress, _serverPort);
@@ -25,18 +37,18 @@ namespace Subsystems
Debug.Log("Connected to server.");
break;
}
else
retries++;
if (retries >= 10)
{
Debug.Log("Failed to connect to server");
Debug.LogError("Failed to connect after 10 attempts. Giving up.");
break;
}
await Task.Delay(5000);
Debug.Log($"Failed to connect (attempt {retries}). Retrying in {delayMs / 1000}s...");
await Task.Delay(delayMs);
delayMs = Mathf.Min(delayMs * 2, 30000); // exponential backoff, cap 30s
}
}
public GameManager_Network(GameClient gameClient)
{
_gameClient = gameClient;
RegisterEventHandlers();
}
public void RegisterEventHandlers()
{
_gameClient.OnConnected += OnConnected;
@@ -45,117 +57,307 @@ namespace Subsystems
_gameClient.OnMessage += OnMessage;
_gameClient.OnGameEvent += OnGameEvent;
}
private void OnConnected()
{
Debug.Log("Successfully connected to the server.");
}
private void OnDisconnected(string reason)
{
Debug.Log($"Host disconnected due to {reason}");
Debug.Log($"Disconnected: {reason}");
// Auto-reconnect unless the app is quitting
if (reason != "Disposed" && _manager != null)
_manager.StartCoroutine(ReconnectAfterDelay(3f));
}
private System.Collections.IEnumerator ReconnectAfterDelay(float seconds)
{
yield return new UnityEngine.WaitForSeconds(seconds);
Debug.Log("Attempting to reconnect...");
OpenConnection();
}
private void OnError(string error)
{
Debug.LogError($"Network error: {error}");
}
private void OnMessage(Message message)
{
switch (message.Type)
{
case "GameEvent":
OnGameEvent(message as GameEvent);
// handled via OnGameEvent
break;
case "CreateLobbyResponse":
Debug.Log("Received CreateLobbyResponse message");
HandleCreateLobbyResponse(message as CreateLobbyResponse);
break;
case "JoinLobbyResponse":
Debug.Log("Received JoinLobbyResponse message");
HandleJoinLobbyResponse(message as JoinLobbyResponse);
break;
case "PositionBroadcast":
HandlePositionBroadcast(message as PositionBroadcast);
break;
case "Ack":
Debug.Log("Received Ack message");
break;
default:
Debug.Log("Received message of type: " + message.Type);
break;
}
}
private void OnGameEvent(GameEvent gameEvent)
{
switch (gameEvent.EventType)
{
case "PlayerJoined":
Debug.Log($"Player {gameEvent.GetPayload<PlayerJoinedPayload>().DisplayName} joined");
break;
case "PlayerLeft":
Debug.Log($"Player {gameEvent.GetPayload<PlayerLeftPayload>()} left");
case "HostChanged":
// SDK already updates CurrentLobbyState; just refresh UI
_manager?.uiSubsystem?.NotifyLobbyChanged();
break;
case "GameStarting":
Debug.Log("Game is starting!");
break;
case "GameStarted":
Debug.Log("Game started");
HandleGameStarting();
break;
case "MapDataReady":
Debug.Log("Map data ready");
HandleMapDataReady();
break;
case "PlayerMapDataReceived":
Debug.Log("Player map data recieved");
case "GameStarted":
HandleGameStarted();
break;
case "RoleAssigned":
HandleRoleAssigned(gameEvent);
break;
case "TaskCompleted":
HandleTaskCompleted(gameEvent);
break;
case "PlayerKilled":
HandlePlayerKilled(gameEvent);
break;
case "BodyReported":
case "EmergencyMeetingCalled":
HandleMeetingCalled(gameEvent);
break;
case "MeetingStarted":
HandleMeetingStarted(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":
_manager?.uiSubsystem?.HideSabotageTimer();
_manager?.mapSubsystem?.ClearSabotageMarkers();
break;
case "MapDataError":
Debug.Log("Received MapData server error");
Debug.LogError("Server could not generate map data.");
break;
default:
Debug.Log("Received GameEvent of type: " + gameEvent.EventType);
Debug.Log("GameEvent: " + gameEvent.EventType);
break;
}
}
// ── Lobby responses ───────────────────────────────────────────────────
private void HandleCreateLobbyResponse(CreateLobbyResponse message)
{
if (message == null) return;
if (message.Success)
{
Debug.Log("Lobby created successfully. Join Code: " + message.JoinCode + ", Lobby ID: " + message.LobbyId);
Debug.Log($"Lobby created. Code: {message.JoinCode}, ID: {message.LobbyId}");
// Navigate to the create/waiting scene
SceneManager.LoadScene("create", LoadSceneMode.Single);
// Mark lobby UI dirty so LobbyDisplayUI refreshes once the scene is loaded
_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("Lobby created successfully." + ", Lobby ID: " + message.LobbyId);
Debug.Log($"Joined lobby: {message.LobbyId}");
SceneManager.LoadScene("join loading", LoadSceneMode.Single);
// Mark lobby UI dirty so LobbyDisplayUI refreshes once the scene is loaded
_manager?.uiSubsystem?.NotifyLobbyChanged();
}
else
{
Debug.LogError("Failed to create lobby: " + message.Error);
Debug.LogError("Failed to join lobby: " + message.Error);
}
}
public void CrateLobby(double lat, double lon)
// ── Game flow events ──────────────────────────────────────────────────
private void HandleGameStarting()
{
_gameClient.CreateLobby(new Position(lat, lon));
_pendingMapBuild = false;
// SDK sets Phase = Loading; load Client.unity
SceneManager.LoadScene("Client", LoadSceneMode.Single);
}
private void HandleMapDataReady()
{
_pendingMapBuild = true;
TryBuildMapAndMarkers();
}
/// <summary>
/// Called from GameManager.OnSceneLoaded("Client") after scene objects are bound.
/// Ensures map construction still happens even if MapDataReady arrived earlier.
/// </summary>
public void OnClientSceneReady()
{
TryBuildMapAndMarkers();
}
private void TryBuildMapAndMarkers()
{
if (!_pendingMapBuild) return;
if (_manager?.mapSubsystem == null) return;
if (!_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 and task markers refreshed.");
}
private void HandleGameStarted()
{
Debug.Log("Game started");
// Phase is now Playing; GPS loop will start sending positions
}
private void HandleRoleAssigned(GameEvent evt)
{
var payload = evt.GetPayload<RoleAssignedPayload>();
if (payload == null || payload.ClientUuid != _gameClient.ClientUuid) return;
Debug.Log($"Role: {payload.Role}, Tasks: {payload.Tasks?.Count ?? 0}");
_manager?.taskSubsystem?.Initialize(_gameClient.MyTasks);
}
private void HandleTaskCompleted(GameEvent evt)
{
var payload = evt.GetPayload<TaskCompletedPayload>();
if (payload == null) return;
_manager?.uiSubsystem?.UpdateTaskProgress(payload.TotalCompleted, payload.TotalTasks);
_manager?.mapSubsystem?.RemoveTaskMarker(payload.TaskId);
}
private void HandlePlayerKilled(GameEvent evt)
{
var payload = evt.GetPayload<PlayerKilledPayload>();
if (payload == null) return;
_manager?.mapSubsystem?.CreateBodyMarker(payload.BodyId, payload.Location);
if (payload.VictimId == _gameClient.ClientUuid)
_manager?.uiSubsystem?.OnLocalPlayerDied();
}
private void HandleMeetingCalled(GameEvent evt)
{
_manager?.uiSubsystem?.ShowMeetingAlert();
}
private void HandleMeetingStarted(GameEvent evt)
{
var payload = evt.GetPayload<MeetingStartedPayload>();
if (payload == null) return;
_manager?.uiSubsystem?.ShowMeetingPanel(_gameClient.CurrentLobbyState?.Players, payload);
}
private void HandleVotingClosed(GameEvent evt)
{
var payload = evt.GetPayload<VotingClosedPayload>();
if (payload == null) return;
_manager?.uiSubsystem?.ShowVoteResult(payload, _gameClient.CurrentLobbyState?.Players);
_manager?.mapSubsystem?.ClearBodyMarkers();
}
private void HandleGameEnded(GameEvent evt)
{
var payload = evt.GetPayload<GameEndedPayload>();
if (payload == null) return;
_manager?.uiSubsystem?.ShowGameEndPanel(payload, _gameClient.ClientUuid);
}
private void HandleReturnedToLobby()
{
if (_gameClient.IsOwner)
SceneManager.LoadScene("create", LoadSceneMode.Single);
else
SceneManager.LoadScene("join loading", LoadSceneMode.Single);
}
private void HandleSabotageStarted(GameEvent evt)
{
var payload = evt.GetPayload<SabotageStartedPayload>();
if (payload == null) return;
_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);
}
// ── 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("Error joining lobby: " + ex.Message);
}
try { _gameClient.JoinLobby(joinCode); }
catch (System.Exception ex) { Debug.LogError("JoinLobby error: " + ex.Message); }
}
public void LeaveLobby()
{
_gameClient.Disconnect();
Application.Quit();
_gameClient.LeaveLobby();
SceneManager.LoadScene(_manager?.firstMenuScene ?? "main menu asi idk lol", LoadSceneMode.Single);
}
public void StartGame()
{
_gameClient.StartGame();
}
}
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using GeoSus.Client;
namespace Subsystems
{
/// <summary>
/// Round-robin task-to-minigame assignment, proximity detection, additive scene launch.
/// </summary>
public class GameManager_Tasks
{
private class TaskEntry
{
public GeoSus.Client.GameTask ServerTask;
public string MinigameScene;
public bool Completed;
}
private GameClient _gameClient;
private string[] _minigameScenes;
private MonoBehaviour _host; // GameManager MonoBehaviour for coroutines
private List<TaskEntry> _tasks = new List<TaskEntry>();
private bool _minigameOpen;
private string _loadedMinigameScene;
// Proximity state (checked every frame in UpdateProximity)
public GeoSus.Client.GameTask NearbyTask { get; private set; }
private const float ProximityRadius = 5f; // metres / Unity units
public GameManager_Tasks(GameClient gameClient, string[] minigameScenes, MonoBehaviour host)
{
_gameClient = gameClient;
_minigameScenes = minigameScenes ?? new string[0];
_host = host;
}
/// <summary>Called by Network subsystem when RoleAssigned fires.</summary>
public void Initialize(List<GeoSus.Client.GameTask> serverTasks)
{
_tasks.Clear();
if (_minigameScenes.Length == 0) return;
for (int i = 0; i < serverTasks.Count; i++)
{
_tasks.Add(new TaskEntry
{
ServerTask = serverTasks[i],
MinigameScene = _minigameScenes[i % _minigameScenes.Length],
Completed = false
});
}
// Create map markers
GameManager.Instance?.mapSubsystem?.CreateTaskMarkers(serverTasks);
Debug.Log($"[Tasks] Initialized {_tasks.Count} tasks.");
}
/// <summary>Called every frame from GameManager.Update().</summary>
public void UpdateProximity()
{
if (_minigameOpen) return;
NearbyTask = null;
var myPos = _gameClient.MyPosition;
if (myPos.Lat == 0 && myPos.Lon == 0) return;
foreach (var entry in _tasks)
{
if (entry.Completed) continue;
double dist = myPos.DistanceTo(entry.ServerTask.Location);
if (dist <= ProximityRadius)
{
NearbyTask = entry.ServerTask;
break;
}
}
// Drive the action button in UI
var ui = GameManager.Instance?.uiSubsystem;
if (ui == null || ui.IsPlayerDead) return;
bool isImpostor = _gameClient.MyRole == GeoSus.Client.PlayerRole.Impostor;
if (!isImpostor && NearbyTask != null)
{
ui.SetActionButton("USE", true, () => GameManager.Instance?.PerformAction());
return;
}
// Check body proximity
if (!ui.IsCommsBlackout)
{
var body = _gameClient.FindNearbyBody(ProximityRadius);
if (body != null)
{
ui.SetActionButton("REPORT", true, () => GameManager.Instance?.PerformAction());
return;
}
// Emergency meeting proximity
if (_gameClient.CurrentLobbyState?.MapData != null)
{
double dist = myPos.DistanceTo(_gameClient.CurrentLobbyState.MapData.Center);
if (dist <= ProximityRadius)
{
ui.SetActionButton("EMERGENCY", true, () => GameManager.Instance?.PerformAction());
return;
}
}
}
// Impostor kill
if (isImpostor)
{
var target = _gameClient.FindNearbyPlayer(ProximityRadius);
if (!string.IsNullOrEmpty(target))
{
ui.SetActionButton("KILL", true, () => GameManager.Instance?.PerformAction());
return;
}
}
// Nothing nearby
ui.SetActionButton("", false);
}
/// <summary>Called externally (e.g., GameManager.PerformAction) to launch the nearby task.</summary>
public void TriggerNearbyTask()
{
OnUsePressed();
}
private void OnUsePressed()
{
if (NearbyTask == null || _minigameOpen) return;
var entry = _tasks.Find(t => t.ServerTask.TaskId == NearbyTask.TaskId);
if (entry != null) _host.StartCoroutine(LaunchMinigame(entry));
}
private IEnumerator LaunchMinigame(TaskEntry entry)
{
_minigameOpen = true;
Debug.Log($"[Tasks] Launching minigame '{entry.MinigameScene}' for task '{entry.ServerTask.Name}'");
// Inform server that task started
_gameClient.Send(new TaskStart { TaskId = entry.ServerTask.TaskId });
var op = SceneManager.LoadSceneAsync(entry.MinigameScene, LoadSceneMode.Additive);
yield return op;
_loadedMinigameScene = entry.MinigameScene;
// Find the ITask component in the newly loaded scene
Scene scene = SceneManager.GetSceneByName(entry.MinigameScene);
ITask taskComponent = null;
foreach (var root in scene.GetRootGameObjects())
{
taskComponent = root.GetComponentInChildren<ITask>();
if (taskComponent != null) break;
}
if (taskComponent == null)
{
Debug.LogWarning($"[Tasks] No ITask found in '{entry.MinigameScene}'. Auto-completing.");
yield return FinishMinigame(entry, true);
yield break;
}
// Set task metadata
taskComponent.TaskID = entry.ServerTask.TaskId;
taskComponent.TaskName = entry.ServerTask.Name;
taskComponent.TaskLocation = (entry.ServerTask.Location.Lat, entry.ServerTask.Location.Lon);
bool done = false;
bool exited = false;
taskComponent.Initialize(t => { done = true; });
taskComponent.ExitTask(t => { exited = true; });
// Wait for completion or exit
yield return new WaitUntil(() => done || exited);
yield return FinishMinigame(entry, done);
}
private IEnumerator FinishMinigame(TaskEntry entry, bool completed)
{
if (completed)
{
entry.Completed = true;
_gameClient.CompleteTask(entry.ServerTask.TaskId);
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' completed.");
}
else
{
Debug.Log($"[Tasks] Task '{entry.ServerTask.Name}' exited without completion.");
}
// Unload minigame scene
if (!string.IsNullOrEmpty(_loadedMinigameScene))
{
var unload = SceneManager.UnloadSceneAsync(_loadedMinigameScene);
yield return unload;
_loadedMinigameScene = null;
}
_minigameOpen = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 27a123dbda9eef8ba4815c0c0d30b6fb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,71 +1,346 @@
using UnityEngine;
using UnityEngine.UI;
using Subsystems;
using GeoSus.Client;
using System.ComponentModel;
using System.Threading;
using System.Collections.Generic;
using System;
using TMPro;
namespace Subsystems
{
/// <summary>
/// Manages UI for the GameManager. Canvas references are only valid in Client.unity;
/// Art-menu scenes use their own lightweight UI scripts that read from GameManager.Instance.
/// </summary>
public class GameManager_UI
{
private GameClient _gameClient;
private Canvas _CreateJoinLobby;
private Canvas _InLobby;
private Canvas _LoadingScreen;
private Canvas _GameScreen;
public GameManager_UI(GameClient gameClient, Canvas CreateJoinLobby, Canvas InLobby, Canvas LoadingScreen, Canvas GameScreen)
// Set by GameManager after Client.unity loads (called from GameManager.OnSceneLoaded)
public Canvas ClientCreateJoinLobby; // fallback join-code canvas in Client.unity
public Canvas ClientInLobby; // InLobby canvas in Client.unity (unused now, kept compat)
public Canvas ClientLoadingScreen;
public Canvas ClientGameScreen; // parent of all HUD elements
// HUD elements (children of ClientGameScreen, resolved at runtime)
private TMP_Text _roleText;
private TMP_Text _taskListText;
private TMP_Text _taskProgressText;
private Button _actionButton;
private TMP_Text _actionButtonText;
private TMP_Text _killCooldownText;
private GameObject _sabotagePanel;
private TMP_Text _sabotageTimerText;
private GameObject _meetingPanel;
private GameObject _gameEndPanel;
private TMP_Text _gameEndText;
// Runtime state
private bool _isDead;
private bool _commsBlackout;
private DateTime _sabotageMeltdownDeadline;
private bool _sabotageTimerActive;
// Lobby-changed flag — set from network thread, consumed in Update
private volatile bool _lobbyDirty;
public GameManager_UI(GameClient gameClient)
{
_gameClient = gameClient;
_CreateJoinLobby = CreateJoinLobby;
_LoadingScreen = LoadingScreen;
_GameScreen = GameScreen;
_InLobby = InLobby;
_CreateJoinLobby.enabled = true;
_InLobby.enabled = false;
_GameScreen.enabled = false;
_LoadingScreen.enabled = false;
}
/// <summary>Called by Network subsystem when lobby player list changes.</summary>
public void NotifyLobbyChanged() => _lobbyDirty = true;
// ── Called from GameManager after Client.unity loads ──────────────────
public void BindClientScene(Canvas createJoin, Canvas inLobby, Canvas loading, Canvas game)
{
ClientCreateJoinLobby = createJoin;
ClientInLobby = inLobby;
ClientLoadingScreen = loading;
ClientGameScreen = game;
EnsureCanvasReady(createJoin);
EnsureCanvasReady(inLobby);
EnsureCanvasReady(loading);
EnsureCanvasReady(game);
if (createJoin) createJoin.gameObject.SetActive(false);
if (inLobby) inLobby.gameObject.SetActive(false);
if (loading) loading.gameObject.SetActive(false);
if (game) game.gameObject.SetActive(false);
if (game != null)
{
_roleText = FindTMP(game.transform, "Role");
_taskListText = FindTMP(game.transform, "TaskList");
_taskProgressText = FindTMP(game.transform, "TaskProgress");
_killCooldownText = FindTMP(game.transform, "KillCooldown");
_sabotageTimerText = FindTMP(game.transform, "SabotageTimer");
_gameEndText = FindTMP(game.transform, "GameEndText");
var actionGO = game.transform.Find("ActionButton");
if (actionGO != null)
{
_actionButton = actionGO.GetComponent<Button>();
_actionButtonText = actionGO.GetComponentInChildren<TMP_Text>();
}
var sabGO = game.transform.Find("SabotagePanel");
_sabotagePanel = sabGO?.gameObject;
var meetGO = game.transform.Find("MeetingPanel");
_meetingPanel = meetGO?.gameObject;
if (_meetingPanel) _meetingPanel.SetActive(false);
var endGO = game.transform.Find("GameEndPanel");
_gameEndPanel = endGO?.gameObject;
if (_gameEndPanel) _gameEndPanel.SetActive(false);
}
}
// ── Main update (called every frame from GameManager.Update) ──────────
public void UpdateLobbyUI()
{
if (_gameClient.CurrentLobbyState == null)
var state = _gameClient.CurrentLobbyState;
if (state == null) return;
// Update any LobbyDisplayUI listeners in the current scene
if (_lobbyDirty)
{
_CreateJoinLobby.enabled = true;
_InLobby.enabled = false;
_GameScreen.enabled = false;
_LoadingScreen.enabled = false;
return;
_lobbyDirty = false;
LobbyDisplayUI.RefreshAll(state);
}
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Loading)
// Only do canvas switches if we are in Client.unity (canvases assigned)
if (ClientGameScreen == null) return;
switch (state.Phase)
{
_CreateJoinLobby.enabled = false;
_InLobby.enabled = false;
_GameScreen.enabled = false;
_LoadingScreen.enabled = true;
return;
case GamePhase.Loading:
SetCanvases(false, false, true, false);
break;
case GamePhase.Lobby:
SetCanvases(false, true, false, false);
break;
case GamePhase.Playing:
case GamePhase.Meeting:
case GamePhase.Voting:
SetCanvases(false, false, false, true);
UpdateGameHUD();
break;
case GamePhase.Ended:
// GameEndPanel shown by HandleGameEnded
break;
}
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Lobby)
}
private void UpdateGameHUD()
{
if (_roleText != null) _roleText.text = _gameClient.MyRole?.ToString() ?? "";
// Task list
if (_taskListText != null)
{
_InLobby.enabled = true;
_CreateJoinLobby.enabled = false;
var playerList = _InLobby.transform.Find("PlayerList").GetComponent<TMPro.TMP_Text>();
playerList.text = "";
foreach (var player in _gameClient.CurrentLobbyState.Players)
var sb = new System.Text.StringBuilder();
foreach (var t in _gameClient.MyTasks)
sb.AppendLine(t.Name);
_taskListText.text = sb.ToString();
}
// Kill cooldown (managed by GameManager_Tasks via Update)
// Sabotage timer
if (_sabotageTimerActive && _sabotageTimerText != null)
{
double remaining = (_sabotageMeltdownDeadline - DateTime.UtcNow).TotalSeconds;
_sabotageTimerText.text = remaining > 0 ? $"MELTDOWN: {remaining:F0}s" : "MELTDOWN!";
}
}
// ── Helpers called by Network handlers ────────────────────────────────
public void SetKillCooldownText(string text)
{
if (_killCooldownText != null)
{
_killCooldownText.text = text;
_killCooldownText.gameObject.SetActive(!string.IsNullOrEmpty(text));
}
}
public void UpdateTaskProgress(int completed, int total)
{
if (_taskProgressText != null)
_taskProgressText.text = $"Tasks: {completed}/{total}";
}
public void SetActionButton(string label, bool visible, UnityEngine.Events.UnityAction onClick = null)
{
if (_actionButton == null) return;
_actionButton.gameObject.SetActive(visible);
if (_actionButtonText != null) _actionButtonText.text = label;
if (onClick != null)
{
_actionButton.onClick.RemoveAllListeners();
_actionButton.onClick.AddListener(onClick);
}
}
public void OnLocalPlayerDied()
{
_isDead = true;
if (_roleText != null) _roleText.text = "GHOST";
}
public void ShowMeetingAlert()
{
Debug.Log("Meeting called! Run to meeting point.");
}
public void ShowMeetingPanel(List<PlayerInfo> players, MeetingStartedPayload payload)
{
if (_meetingPanel == null) return;
_meetingPanel.SetActive(true);
var header = FindTMP(_meetingPanel.transform, "MeetingHeader");
if (header != null)
header.text = payload.Type == MeetingType.BodyReport ? "BODY REPORTED!" : "EMERGENCY MEETING!";
// Build simple text list of players — full vote buttons need prefabs in Unity Editor
var playerList = FindTMP(_meetingPanel.transform, "MeetingPlayerList");
if (playerList != null && players != null)
{
var sb = new System.Text.StringBuilder();
foreach (var p in players)
sb.AppendLine($"{p.DisplayName} [{p.State}]");
playerList.text = sb.ToString();
}
// Wire skip button if it exists
var skipBtn = _meetingPanel.transform.Find("SkipButton")?.GetComponent<Button>();
if (skipBtn != null)
{
skipBtn.onClick.RemoveAllListeners();
skipBtn.onClick.AddListener(() => GameManager.Instance?.CastVote(null));
}
}
public void AppendVoteInstruction()
{
var playerList = FindTMP(_meetingPanel?.transform, "MeetingPlayerList");
if (playerList != null)
playerList.text += "\n[Tap a name] then press VOTE — or press SKIP";
}
public void ShowVoteResult(VotingClosedPayload payload, List<PlayerInfo> players)
{
if (_meetingPanel == null) return;
var resultText = FindTMP(_meetingPanel.transform, "VoteResult");
if (resultText != null)
{
if (payload.WasTie)
{
playerList.text += player.DisplayName + "\n";
resultText.text = "TIE — nobody ejected.";
}
else if (string.IsNullOrEmpty(payload.EjectedPlayerId))
{
resultText.text = "Skip — nobody ejected.";
}
else
{
var ejected = players?.Find(p => p.ClientUuid == payload.EjectedPlayerId);
resultText.text = $"{ejected?.DisplayName ?? payload.EjectedPlayerId} ejected!";
}
_InLobby.transform.Find("JoinCode").GetComponent<TMPro.TMP_Text>().text = _gameClient.CurrentLobbyState.JoinCode;
return;
}
else if (_gameClient.CurrentLobbyState.Phase == GamePhase.Playing)
// Close panel after 5 seconds — use coroutine via GameManager
GameManager.Instance?.StartCoroutine(CloseMeetingPanelAfterDelay(5f));
}
private System.Collections.IEnumerator CloseMeetingPanelAfterDelay(float delay)
{
yield return new UnityEngine.WaitForSeconds(delay);
if (_meetingPanel != null) _meetingPanel.SetActive(false);
}
public void ShowGameEndPanel(GameEndedPayload payload, string myUuid)
{
if (_gameEndPanel != null) _gameEndPanel.SetActive(true);
bool won = payload.Winners != null && payload.Winners.Contains(myUuid);
if (_gameEndText != null)
_gameEndText.text = $"{(won ? "VICTORY" : "DEFEAT")}\n{payload.WinningFaction} wins!\n{payload.Reason}";
}
public void ShowSabotageTimer(DateTime deadline)
{
_sabotageMeltdownDeadline = deadline;
_sabotageTimerActive = true;
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(true);
}
public void HideSabotageTimer()
{
_sabotageTimerActive = false;
if (_sabotageTimerText != null) _sabotageTimerText.gameObject.SetActive(false);
SetCommsBlackout(false);
}
public void SetCommsBlackout(bool active)
{
_commsBlackout = active;
}
public bool IsCommsBlackout => _commsBlackout;
public bool IsPlayerDead => _isDead;
// ── Utilities ─────────────────────────────────────────────────────────
private void SetCanvases(bool createJoin, bool inLobby, bool loading, bool game)
{
EnsureCanvasReady(ClientCreateJoinLobby);
EnsureCanvasReady(ClientInLobby);
EnsureCanvasReady(ClientLoadingScreen);
EnsureCanvasReady(ClientGameScreen);
if (ClientCreateJoinLobby) ClientCreateJoinLobby.gameObject.SetActive(createJoin);
if (ClientInLobby) ClientInLobby.gameObject.SetActive(inLobby);
if (ClientLoadingScreen) ClientLoadingScreen.gameObject.SetActive(loading);
if (ClientGameScreen) ClientGameScreen.gameObject.SetActive(game);
}
private static void EnsureCanvasReady(Canvas canvas)
{
if (canvas == null) return;
if (!canvas.enabled)
canvas.enabled = true;
// Some scene canvases are saved with zero scale, which makes UI invisible.
var t = canvas.transform;
if (t != null)
{
_CreateJoinLobby.enabled = false;
_InLobby.enabled = false;
_GameScreen.enabled = true;
_LoadingScreen.enabled = false;
_GameScreen.transform.Find("Role").GetComponent<TMPro.TMP_Text>().text = _gameClient.MyRole.ToString() ;
return;
var s = t.localScale;
if (Mathf.Abs(s.x) < 0.001f || Mathf.Abs(s.y) < 0.001f || Mathf.Abs(s.z) < 0.001f)
t.localScale = Vector3.one;
}
}
private static TMP_Text FindTMP(Transform root, string name)
{
if (root == null) return null;
foreach (var tmp in root.GetComponentsInChildren<TMP_Text>(true))
{
if (tmp != null && tmp.name == name)
return tmp;
}
return null;
}
}
}

View File

@@ -4,50 +4,18 @@ using UnityEngine;
public enum TaskType
{
Task //TODO: Typy úkolù
Task
}
public interface ITask
{
public string TaskID { get; } // Unikátní ID úkolu pro server
public TaskType TaskType { get; } // Typ úkolu
public string TaskName { get; } // Viditelný název úkolu
public (double, double) TaskLocation { get; } // Polohy na mapì
public bool IsCompleted { get; } // Stav dokonèení úkolu
public string TaskID { get; set; } // Unikátní ID úkolu pro server
public TaskType TaskType { get; set; } // Typ úkolu
public string TaskName { get; set; } // Viditelný název úkolu
public (double, double) TaskLocation { get; set; } // Poloha na mapě
public bool IsCompleted { get; } // Stav dokončení úkolu
void Initialize(Action<ITask> onCompleted); // Vytvoøení tasku + naètení postupu
void ExitTask(Action<ITask> onExit); // Pøi opuštìní úkolu poslat hotovo / uložit postup / reset
void Complete(); // Oznaèit úkol jako dokonèený, poslat na server a zavøít
}
/* Ukázoková implementace ITask
public class Wires : ITask{
public string TaskID { get; set; } // Unikátní ID úkolu pro server
public TaskType TaskType { get; set; } // Typ úkolu
public string TaskName { get; set; } // Viditelný název úkolu
public (double, double) TaskLocation { get; set; } // Poloha na mapì
public bool IsCompleted { get; private set; } // Stav dokonèení úkolu
private Action<ITask> _onCompleted;
public void Initialize(Action<ITask> onCompleted) // Vytvoøení tasku
{
IsCompleted = false;
_onCompleted = onCompleted;
}
public void ExitTask(Action<ITask> onExit) //Zavøení tasku
{
onExit?.Invoke(this);
}
public void Complete() // Dokonèení tasku a zavøení
{
IsCompleted = true;
_onCompleted?.Invoke(this);
ExitTask(null);
}
}
*/
void Initialize(Action<ITask> onCompleted); // Vytvoření tasku
void ExitTask(Action<ITask> onExit); // Při opuštění úkolu
void Complete(); // Označit úkol jako dokončený
}