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
}