261 lines
10 KiB
C#
261 lines
10 KiB
C#
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; }
|
|
|
|
// P13b: per-check distances pulled from the server-snapshotted lobby
|
|
// settings (null-fallback to 5m matches the old hardcoded behavior).
|
|
// Different actions use different fields so a host can tune e.g. a
|
|
// long-range "spotter" task radius without also widening kill range.
|
|
private const float ProximityRadiusFallback = 5f;
|
|
|
|
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;
|
|
|
|
// P13b: distances now come from the per-lobby settings snapshot
|
|
// instead of one hardcoded 5m radius for everything. ?? fallback
|
|
// matches the old behavior when running against an old server.
|
|
var state = GameManager.Instance?.networkSubsystem?.State;
|
|
var settings = state?.Settings;
|
|
double taskDist = settings?.TaskStartDistanceM ?? ProximityRadiusFallback;
|
|
double reportDist = settings?.ReportDistanceM ?? ProximityRadiusFallback;
|
|
double emergencyDist = settings?.EmergencyMeetingCallRadiusM?? ProximityRadiusFallback;
|
|
double killDist = settings?.KillDistanceM ?? ProximityRadiusFallback;
|
|
|
|
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 <= taskDist)
|
|
{
|
|
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(reportDist);
|
|
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 <= emergencyDist)
|
|
{
|
|
ui.SetActionButton("EMERGENCY", true, () => GameManager.Instance?.PerformAction());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Impostor kill
|
|
if (isImpostor)
|
|
{
|
|
var target = _gameClient.FindNearbyPlayer(killDist);
|
|
if (!string.IsNullOrEmpty(target))
|
|
{
|
|
ui.SetActionButton("KILL", true, () => GameManager.Instance?.PerformAction());
|
|
// Hide sabotage menu while a kill is on offer (cleaner HUD).
|
|
ui.SetSabotageMenuVisible(false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Nothing nearby
|
|
ui.SetActionButton("", false);
|
|
|
|
// P13g: persistent sabotage menu for impostors when no proximity
|
|
// action is on offer. Hidden when state isn't suitable - dead,
|
|
// not-impostor, in meeting, sabotage already active, or comms
|
|
// blackout (the impostor's own sabotage triggers a UI lock).
|
|
bool inPlayingPhase = state != null && state.Phase == GeoSus.Client.GamePhase.Playing;
|
|
bool sabotageActive = state?.ActiveSabotage != null;
|
|
bool showSabMenu = isImpostor && !ui.IsPlayerDead && inPlayingPhase &&
|
|
!sabotageActive && !ui.IsCommsBlackout;
|
|
ui.SetSabotageMenuVisible(showSabMenu);
|
|
}
|
|
|
|
/// <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}'");
|
|
|
|
// Validate that the scene name resolves to a build-included scene.
|
|
// LoadSceneAsync silently returns null when the scene name doesn't
|
|
// match (case-sensitive) or isn't in EditorBuildSettings, which
|
|
// leaves the action button looking dead from the player's POV.
|
|
if (string.IsNullOrEmpty(entry.MinigameScene) ||
|
|
!Application.CanStreamedLevelBeLoaded(entry.MinigameScene))
|
|
{
|
|
Debug.LogError($"[Tasks] Minigame scene '{entry.MinigameScene}' is not loadable. " +
|
|
$"Check the scene name (case-sensitive) and that it's enabled in Build Settings.");
|
|
GameManager.Instance?.uiSubsystem?.ShowToast(
|
|
$"Task scene missing: {entry.MinigameScene}");
|
|
_minigameOpen = false;
|
|
yield break;
|
|
}
|
|
|
|
// Inform server that task started
|
|
_gameClient.Send(new TaskStart { TaskId = entry.ServerTask.TaskId });
|
|
|
|
var op = SceneManager.LoadSceneAsync(entry.MinigameScene, LoadSceneMode.Additive);
|
|
if (op == null)
|
|
{
|
|
Debug.LogError($"[Tasks] LoadSceneAsync returned null for '{entry.MinigameScene}'.");
|
|
GameManager.Instance?.uiSubsystem?.ShowToast(
|
|
$"Task scene failed to load: {entry.MinigameScene}");
|
|
_minigameOpen = false;
|
|
yield break;
|
|
}
|
|
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;
|
|
taskComponent.Initialize(t => { done = true; });
|
|
|
|
// Wait for completion or exit
|
|
yield return new WaitUntil(() => done);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|