using GeoSus.Client; using System; using System.Collections.Generic; /// /// 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. /// 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 } /// /// Single source of truth for all in-game state on the client. /// Updated exclusively by GameManager_Network; read by GameManager_UI. /// public class GameState { // ── Phase / Role ────────────────────────────────────────────────────────── public GamePhase Phase { get; set; } = GamePhase.Lobby; public PlayerRole? MyRole { get; set; } public bool IsDead { get; set; } // ── Settings (P13b) ─────────────────────────────────────────────────────── /// /// 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. /// public GameSettings Settings { get; set; } // ── Tasks ───────────────────────────────────────────────────────────────── public List MyTasks { get; set; } = new List(); public HashSet MyCompletedTaskIds { get; set; } = new HashSet(); public int TotalCompleted { get; set; } public int TotalRequired { get; set; } // ── Players ─────────────────────────────────────────────────────────────── public List Players { get; set; } = new List(); // ── Meeting ─────────────────────────────────────────────────────────────── public MeetingStartedPayload ActiveMeeting { get; set; } public VotingClosedPayload LastVoteResult { get; set; } public HashSet VotedPlayerIds { get; set; } = new HashSet(); public HashSet ArrivedPlayerIds { get; set; } = new HashSet(); /// Per-voter latest vote target. Voter ClientUuid -> target ClientUuid, or VoteSkip for skip. public Dictionary VoterTargets { get; set; } = new Dictionary(); /// Live vote tallies, keyed by target ClientUuid or VoteSkip. Derived from VoterTargets. public Dictionary VoteTallies { get; set; } = new Dictionary(); /// Local player's latest vote target. Null = haven't voted; VoteSkip = skip; otherwise target ClientUuid. public string MyVoteTarget { get; set; } /// Sentinel for "skip" votes in VoterTargets / VoteTallies / MyVoteTarget. public const string VoteSkip = "__SKIP__"; /// /// Derive the current meeting sub-phase from ActiveMeeting + LastVoteResult. /// Returns Arrival when no meeting is active (caller should also gate on Phase). /// 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; } /// End-of-current-sub-phase boundary as a UTC DateTime, used for countdown rendering. 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; } /// StationIds currently being repaired (server broadcasts RepairStarted/RepairStopped). public HashSet ActiveRepairs { get; set; } = new HashSet(); // ── 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 }