diff --git a/Assets/Scripts/Network/MatchManager.cs b/Assets/Scripts/Network/MatchManager.cs index 47b8327..481a608 100644 --- a/Assets/Scripts/Network/MatchManager.cs +++ b/Assets/Scripts/Network/MatchManager.cs @@ -243,6 +243,8 @@ public class MatchManager : MonoBehaviour // barrier round is dropped). The server holds the Hide phase until every expected client has replied. private const string MsgBeginLoad = "Match_BeginLoad"; private const string MsgReady = "Match_Ready"; + // Client -> server (authority only): DEBUG request to skip the rest of the current sub-phase. + private const string MsgDebugSkip = "Match_DebugSkip"; // Server-only sub-phase machine. _pendingHiders holds the hiders not yet found; seek ends the instant it // empties. _phaseRoutine is the running Hide->Seek->Resolution coroutine (stopped if the match is ended @@ -255,6 +257,9 @@ public class MatchManager : MonoBehaviour // clientId is ephemeral (reused after a disconnect); the value is stable across that client's restarts. private readonly Dictionary _persistentIds = new Dictionary(); private Coroutine _phaseRoutine; + // DEBUG: set by DebugSkipToNextSubPhase; the running phase-wait loop consumes it once to break out early, + // so RunMatchPhases advances to the next SetPhaseServer without waiting out the timer/barrier. + private bool _skipPhase; private readonly List _phaseRecipients = new List(); // reused scratch for the phase broadcast private readonly List _radarPingIds = new List(); // reused scratch: hider NetworkObjectIds for a sweep private readonly List _radarRecipients = new List(); // reused scratch: hunter client recipients for a sweep @@ -330,6 +335,7 @@ public class MatchManager : MonoBehaviour _net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgIdentity); _net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgBeginLoad); _net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgReady); + _net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgDebugSkip); } } @@ -356,6 +362,7 @@ public class MatchManager : MonoBehaviour _net.CustomMessagingManager.RegisterNamedMessageHandler(MsgReportHit, OnReportHitMessage); _net.CustomMessagingManager.RegisterNamedMessageHandler(MsgIdentity, OnIdentityMessage); _net.CustomMessagingManager.RegisterNamedMessageHandler(MsgReady, OnReadyMessage); + _net.CustomMessagingManager.RegisterNamedMessageHandler(MsgDebugSkip, OnDebugSkipMessage); } _subPhase = MatchSubPhase.None; @@ -402,6 +409,7 @@ public class MatchManager : MonoBehaviour StopAllCoroutines(); _phaseRoutine = null; + _skipPhase = false; _hooked = false; _sceneReady = false; _hasAuthorityClient = false; @@ -656,11 +664,13 @@ public class MatchManager : MonoBehaviour // found - identical early-out to the plain WaitPhase(_pendingHiders.Count == 0) it replaces for Seek. private IEnumerator RunSeekPhase(float seconds) { + _skipPhase = false; // drop a stale debug-skip from the previous phase; only this phase may set it float t = seconds; float pingTimer = radarPingInterval; // first sweep one interval into Seek while (t > 0f) { if (_pendingHiders.Count == 0) yield break; + if (ConsumeSkip()) yield break; // DEBUG: jump straight to Resolution if (radarPingInterval > 0f) { @@ -680,26 +690,69 @@ public class MatchManager : MonoBehaviour // Waits up to 'seconds' of server real time, returning early the moment 'until' (if supplied) is true. private IEnumerator WaitPhase(float seconds, System.Func until) { + _skipPhase = false; // drop a stale debug-skip from the previous phase; only this phase may set it float t = seconds; while (t > 0f) { if (until != null && until()) yield break; + if (ConsumeSkip()) yield break; // DEBUG: jump to the next sub-phase t -= Time.deltaTime; yield return null; } } + // DEBUG: true (once) if a skip-to-next-sub-phase was requested; consumed so a single request fires exactly + // one transition. Only ever set server-side (DebugSkipServer) and only these server-run wait loops read it. + private bool ConsumeSkip() + { + if (!_skipPhase) return false; + _skipPhase = false; + return true; + } + + /// DEBUG. Instantly advances the running match to the next sub-phase + /// (Loading -> Hide -> Seek -> Resolution -> back to lobby), skipping the rest of the current phase's timer + /// or loading barrier. Routed through match authority: the host/server acts directly, a remote authority + /// client asks the server. No-op if this machine is not the authority or no sub-phase is running. + [ContextMenu("Debug/Skip To Next Sub-Phase")] + public void DebugSkipToNextSubPhase() + { + if (_net == null) return; + if (_net.IsServer) { DebugSkipServer(); return; } + if (_isLocalAuthority) SendEmptyNamedMessage(MsgDebugSkip, NetworkManager.ServerClientId); + } + + // Server-only. Flags the running phase-wait loop to break early so RunMatchPhases moves to the next sub-phase. + private void DebugSkipServer() + { + if (_net == null || !_net.IsServer) return; + if (_subPhase == MatchSubPhase.None) return; // nothing running to skip + _skipPhase = true; + Debug.Log($"[MatchManager] DEBUG: skipping the rest of {_subPhase}.", this); + } + + // Server handler: an authority client asked to skip to the next sub-phase. + private void OnDebugSkipMessage(ulong senderClientId, FastBufferReader reader) + { + if (_net == null || !_net.IsServer) return; + if (!IsMatchAuthority(senderClientId)) return; + DebugSkipServer(); + } + // --- Pre-match loading barrier --------------------------------------------------------------------- // Server-only. Opens the barrier (snapshot the just-respawned avatars + the clients that must load them, // switch everyone to Loading, tell each client which avatars to wait for), then spins until every still- // connected expected client has reported ready - or matchLoadTimeout elapses. private IEnumerator RunLoadingBarrier() { + _skipPhase = false; // drop any debug-skip left over from the previous phase/match; only this phase may set it BeginLoadingBarrierServer(); float t = matchLoadTimeout; while (!AllClientsReady()) { + if (ConsumeSkip()) break; // DEBUG: force the match to start now, without waiting for every client + // The host is a client too and never messages itself: fold its own local readiness in directly. if (LocalClientReady(_matchAvatarIds)) _readyClients.Add(_net.LocalClientId);