Files
GeoSusGame/Assets/Scripts/GameState.cs
Bandwidth d886f97e14 GeoSus
2026-04-26 20:49:32 +02:00

124 lines
6.8 KiB
C#

using GeoSus.Client;
using System;
using System.Collections.Generic;
/// <summary>
/// Sub-phase derived client-side from the timestamps in MeetingStartedPayload.
/// Server doesn't broadcast a discrete "discussion ended" event - it embeds
/// DiscussionEndTime and VotingEndTime in the meeting-start event and gates
/// vote acceptance on those timestamps. We compute the matching client view
/// by comparing UtcNow to those values every frame.
/// </summary>
public enum MeetingSubPhase
{
Arrival, // before ArrivalDeadline; players are still en route to meeting point
Discussion, // arrival deadline passed; talk only, votes server-rejected
Voting, // discussion ended; votes accepted until VotingEndTime
Resolved // VotingClosed received OR votingEndTime in the past
}
/// <summary>
/// Single source of truth for all in-game state on the client.
/// Updated exclusively by GameManager_Network; read by GameManager_UI.
/// </summary>
public class GameState
{
// ── Phase / Role ──────────────────────────────────────────────────────────
public GamePhase Phase { get; set; } = GamePhase.Lobby;
public PlayerRole? MyRole { get; set; }
public bool IsDead { get; set; }
// ── Settings (P13b) ───────────────────────────────────────────────────────
/// <summary>
/// Per-lobby settings snapshot from the server. Populated on
/// LobbyJoined / LobbyCreated; immutable for the lifetime of the lobby.
/// Null on old server builds - callers must use null-coalescing fallbacks
/// to whatever default they previously hardcoded.
/// </summary>
public GameSettings Settings { get; set; }
// ── Tasks ─────────────────────────────────────────────────────────────────
public List<GameTask> MyTasks { get; set; } = new List<GameTask>();
public HashSet<string> MyCompletedTaskIds { get; set; } = new HashSet<string>();
public int TotalCompleted { get; set; }
public int TotalRequired { get; set; }
// ── Players ───────────────────────────────────────────────────────────────
public List<PlayerInfo> Players { get; set; } = new List<PlayerInfo>();
// ── Meeting ───────────────────────────────────────────────────────────────
public MeetingStartedPayload ActiveMeeting { get; set; }
public VotingClosedPayload LastVoteResult { get; set; }
public HashSet<string> VotedPlayerIds { get; set; } = new HashSet<string>();
public HashSet<string> ArrivedPlayerIds { get; set; } = new HashSet<string>();
/// <summary>Per-voter latest vote target. Voter ClientUuid -> target ClientUuid, or VoteSkip for skip.</summary>
public Dictionary<string, string> VoterTargets { get; set; } = new Dictionary<string, string>();
/// <summary>Live vote tallies, keyed by target ClientUuid or VoteSkip. Derived from VoterTargets.</summary>
public Dictionary<string, int> VoteTallies { get; set; } = new Dictionary<string, int>();
/// <summary>Local player's latest vote target. Null = haven't voted; VoteSkip = skip; otherwise target ClientUuid.</summary>
public string MyVoteTarget { get; set; }
/// <summary>Sentinel for "skip" votes in VoterTargets / VoteTallies / MyVoteTarget.</summary>
public const string VoteSkip = "__SKIP__";
/// <summary>
/// Derive the current meeting sub-phase from ActiveMeeting + LastVoteResult.
/// Returns Arrival when no meeting is active (caller should also gate on Phase).
/// </summary>
public MeetingSubPhase GetMeetingSubPhase()
{
if (LastVoteResult != null) return MeetingSubPhase.Resolved;
var m = ActiveMeeting;
if (m == null) return MeetingSubPhase.Arrival;
var now = DateTime.UtcNow;
if (now >= m.VotingEndTime) return MeetingSubPhase.Resolved;
if (m.DiscussionEndTime.HasValue && now < m.DiscussionEndTime.Value)
{
// Server enforces: arrival deadline AND discussion-end gate voting.
// While arrival is still open we surface "Arrival" so players know
// others may still be travelling; once arrival deadline passes we
// surface "Discussion" until the voting window opens.
return now < m.ArrivalDeadline ? MeetingSubPhase.Arrival : MeetingSubPhase.Discussion;
}
return MeetingSubPhase.Voting;
}
/// <summary>End-of-current-sub-phase boundary as a UTC DateTime, used for countdown rendering.</summary>
public DateTime GetMeetingSubPhaseDeadline(MeetingSubPhase sub)
{
var m = ActiveMeeting;
if (m == null) return DateTime.UtcNow;
switch (sub)
{
case MeetingSubPhase.Arrival:
return m.ArrivalDeadline;
case MeetingSubPhase.Discussion:
return m.DiscussionEndTime ?? m.VotingEndTime;
case MeetingSubPhase.Voting:
return m.VotingEndTime;
default:
return m.VotingEndTime;
}
}
// ── Sabotage ──────────────────────────────────────────────────────────────
public SabotageStartedPayload ActiveSabotage { get; set; }
/// <summary>StationIds currently being repaired (server broadcasts RepairStarted/RepairStopped).</summary>
public HashSet<string> ActiveRepairs { get; set; } = new HashSet<string>();
// ── End game ──────────────────────────────────────────────────────────────
public GameEndedPayload GameEndData { get; set; }
// ── Kill cooldown (tracked by GameManager, reflected here for UI) ─────────
public float KillCooldownRemaining { get; set; }
// ── Notification (toast) ─────────────────────────────────────────────────
public string ToastMessage { get; set; }
public float ToastExpiry { get; set; } // UnityEngine.Time.time
}