Toggle session panel with Escape as in-game pause overlay

- Panel shows in Main while disconnected, auto-hides once a connection is
  established, and toggles open/closed with Escape in the Lobby or on a Map.
- Escape now pauses gameplay (active mode exits, cursor freed, paint and
  movement disabled) instead of ending the match directly; closing restores
  the prior mode via SwitchTo.
- Disconnect button is context-aware: leaves the session in the Lobby, acts
  as "Back to Lobby" (EndMatch) for the match authority on a Map, and stays a
  plain disconnect for non-authority clients on a Map.
- Removed the Escape->EndMatch handler from MatchManager; match end now routes
  through RequestEndMatch with the same authority routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-28 11:34:13 +02:00
1 parent d27ee16004
commit cdfec364b2
4 files changed
+248 -51

No files matched your search

+37 -38
View File
@@ -4,9 +4,6 @@ using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.SceneManagement;
#if ENABLE_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
/// <summary>
/// Server-authoritative match flow. Lives on the persistent NetworkManager object in the boot scene
@@ -37,17 +34,19 @@ using UnityEngine.InputSystem;
/// (strictly serialized - an NGO constraint, only one scene op in flight); then every player object is
/// despawned and respawned as its role's prefab variant.
///
/// Match-control AUTHORITY: the client allowed to start (walk into the trigger) and end (Esc) the match.
/// Match-control AUTHORITY: the client allowed to start (walk into the trigger) and end the match.
/// A host has its own player, so the host is the authority. A DEDICATED server (StartServer, no local
/// player) hands authority to the FIRST client that connects, so a server nobody plays on is still
/// controllable. The id is server-local; the designated remote client is told via a named message so it
/// reads Esc locally and asks the server (which validates the sender) to end. If the authority leaves,
/// it is reassigned to another connected client.
/// can ask the server (which validates the sender) to end. If the authority leaves, it is reassigned to
/// another connected client.
///
/// End / join / teardown:
/// - The match authority presses Esc -> <see cref="EndMatch"/>: Test Map is swapped back to Lobby and
/// everyone is respawned as the neutral Player. The host/server ends directly; a remote authority
/// client requests it over a named message that only the authority's id is honored for.
/// - The match authority ends the match via <see cref="RequestEndMatch"/> -> <see cref="EndMatch"/>
/// (driven by the online session panel's "Back to Lobby" button - Esc opens that panel rather than
/// ending directly). Test Map is swapped back to Lobby and everyone is respawned as the neutral Player.
/// The host/server ends directly; a remote authority client requests it over a named message that only
/// the authority's id is honored for. A non-authority client's panel button is a plain disconnect.
/// - A client that joins MID-MATCH spawns as a SPECTATOR: a dedicated EMPTY prefab (no body/collider)
/// bound to free-cam-only, so it flies around the running map and watches but does not play. It
/// respawns as a neutral lobby Player when the match ends.
@@ -244,35 +243,6 @@ public class MatchManager : MonoBehaviour
ReturnToMainOnly();
}
// Esc ends the running match (sends everyone back to the lobby) - but only for the match AUTHORITY.
// The host/server reads it locally and ends directly. A remote authority client (dedicated server)
// can't end the match itself (the flow is server-authoritative), so it asks the server over a named
// message; the server validates the sender is the authority and that a match is running.
private void Update()
{
if (_net == null) return;
if (_net.IsServer)
{
if (Phase == MatchPhase.InMatch && IsMatchAuthority(_net.LocalClientId) && EscapePressed())
EndMatch();
return;
}
// Pure client: Phase is server-local, so we don't gate on it here - the server checks it on receipt.
if (_isLocalAuthority && EscapePressed())
RequestEndMatchFromServer();
}
private static bool EscapePressed()
{
#if ENABLE_INPUT_SYSTEM
return Keyboard.current != null && Keyboard.current.escapeKey.wasPressedThisFrame;
#else
return Input.GetKeyDown(KeyCode.Escape);
#endif
}
private void HookSceneManager()
{
if (_hooked || _net == null || _net.SceneManager == null) return;
@@ -527,6 +497,35 @@ public class MatchManager : MonoBehaviour
/// <summary>True if the given client currently holds match-control authority (may start and end the match).</summary>
public bool IsMatchAuthority(ulong clientId) => _hasAuthorityClient && clientId == _authorityClientId;
/// <summary>True if THIS machine holds match authority - the host (whose own client id is the authority)
/// or the granted authority client on a dedicated server. Drives the online panel's Disconnect button:
/// on the map the authority's button means "Back to Lobby" (end the match); everyone else disconnects.</summary>
public bool IsLocalMatchAuthority
{
get
{
if (_net == null) return false;
if (_net.IsServer) return IsMatchAuthority(_net.LocalClientId);
return _isLocalAuthority;
}
}
/// <summary>Ends the running match for everyone (back to the Lobby), routed through authority: the
/// host/server ends directly; a remote authority client asks the server. No-op if this machine is not
/// the authority or no match is running. Replaces the old Esc handler - now driven by the panel button.</summary>
public void RequestEndMatch()
{
if (_net == null) return;
if (_net.IsServer) { EndMatch(); return; } // EndMatch itself guards Phase == InMatch
if (_isLocalAuthority) RequestEndMatchFromServer();
}
// Local active-scene kind. Every peer sets its active scene to Lobby/Map as those load (see OnSceneEvent)
// and back to Main on teardown, so a local UI can tell where it is without the server-local Phase.
public bool IsLocalSceneMain => SceneManager.GetActiveScene().name == mainSceneName;
public bool IsLocalSceneLobby => SceneManager.GetActiveScene().name == lobbySceneName;
public bool IsLocalSceneMap => SceneManager.GetActiveScene().name == matchSceneName;
// Server -> client: tell the designated client it is the match authority (so it reads Esc locally).
private void SendAuthorityGrant(ulong clientId)
{