Add persistent per-install player identity
Introduce PersistentPlayerId: a stable id that survives an app restart and is unique per install (built on the UGS install-path-namespaced anonymous PlayerId, with a local PlayerPrefs GUID fallback for loopback). Clones on the same machine stay distinct. Each client reports its id to the server on connect via a new Match_Identity named message (mirroring Match_ReportHit, so no ConnectionApproval timing risk); the host records its own directly. The server keeps an ephemeral clientId -> persistent id map, exposed via PersistentIdOf(clientId) as the foundation for later reconnect recognition. Map entries are cleared on disconnect and net stop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
17bb37cb92
commit
a59a5e53ab
3 files changed
+123
No files matched your search
@@ -183,11 +183,18 @@ public class MatchManager : MonoBehaviour
|
||||
// reports a hit on a hider (ulong target client id); the server validates and marks the hider found.
|
||||
private const string MsgPhase = "Match_Phase";
|
||||
private const string MsgReportHit = "Match_ReportHit";
|
||||
// Client -> server, right after the client connects: the sender's persistent per-install id (survives its
|
||||
// app restarts). Lets the server map the ephemeral NGO clientId to a stable identity - the reconnect
|
||||
// foundation. See PersistentPlayerId.
|
||||
private const string MsgIdentity = "Match_Identity";
|
||||
|
||||
// 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
|
||||
// early via the authority's Back-to-Lobby).
|
||||
private readonly HashSet<ulong> _pendingHiders = new HashSet<ulong>();
|
||||
// Server-only. Live NGO clientId -> persistent per-install id, reported by each client on connect. The
|
||||
// clientId is ephemeral (reused after a disconnect); the value is stable across that client's restarts.
|
||||
private readonly Dictionary<ulong, string> _persistentIds = new Dictionary<ulong, string>();
|
||||
private Coroutine _phaseRoutine;
|
||||
private readonly List<ulong> _phaseRecipients = new List<ulong>(); // reused scratch for the phase broadcast
|
||||
|
||||
@@ -237,6 +244,7 @@ public class MatchManager : MonoBehaviour
|
||||
private void UnhookSessionCallbacks()
|
||||
{
|
||||
_net.OnClientConnectedCallback -= OnClientConnected;
|
||||
_net.OnClientConnectedCallback -= OnLocalClientConnected;
|
||||
_net.OnClientDisconnectCallback -= OnClientDisconnected;
|
||||
if (_net.SceneManager != null) _net.SceneManager.OnSceneEvent -= OnSceneEvent;
|
||||
if (_net.CustomMessagingManager != null)
|
||||
@@ -245,6 +253,7 @@ public class MatchManager : MonoBehaviour
|
||||
_net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgRequestEndMatch);
|
||||
_net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgPhase);
|
||||
_net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgReportHit);
|
||||
_net.CustomMessagingManager.UnregisterNamedMessageHandler(MsgIdentity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,10 +278,12 @@ public class MatchManager : MonoBehaviour
|
||||
{
|
||||
_net.CustomMessagingManager.RegisterNamedMessageHandler(MsgRequestEndMatch, OnRequestEndMatchMessage);
|
||||
_net.CustomMessagingManager.RegisterNamedMessageHandler(MsgReportHit, OnReportHitMessage);
|
||||
_net.CustomMessagingManager.RegisterNamedMessageHandler(MsgIdentity, OnIdentityMessage);
|
||||
}
|
||||
|
||||
_subPhase = MatchSubPhase.None;
|
||||
_pendingHiders.Clear();
|
||||
_persistentIds.Clear();
|
||||
|
||||
// Spawn late joiners on connect (server-side callback). Re-hook defensively in case of re-host.
|
||||
_net.OnClientConnectedCallback -= OnClientConnected;
|
||||
@@ -295,6 +306,11 @@ public class MatchManager : MonoBehaviour
|
||||
_net.CustomMessagingManager.RegisterNamedMessageHandler(MsgAuthorityGranted, OnAuthorityGrantedMessage);
|
||||
_net.CustomMessagingManager.RegisterNamedMessageHandler(MsgPhase, OnPhaseMessage);
|
||||
}
|
||||
|
||||
// Report our persistent id to the server the moment our own connection completes (host records its own
|
||||
// directly). Re-hook defensively in case of re-connect.
|
||||
_net.OnClientConnectedCallback -= OnLocalClientConnected;
|
||||
_net.OnClientConnectedCallback += OnLocalClientConnected;
|
||||
}
|
||||
|
||||
// Fired on shutdown / disconnect for both the server and the client (a host gets both, hence the
|
||||
@@ -314,6 +330,7 @@ public class MatchManager : MonoBehaviour
|
||||
Phase = MatchPhase.Lobby;
|
||||
ApplyPhaseLocal(MatchSubPhase.None, 0f, _localMode);
|
||||
_pendingHiders.Clear();
|
||||
_persistentIds.Clear();
|
||||
if (_net) UnhookSessionCallbacks();
|
||||
|
||||
ReturnToMainOnly();
|
||||
@@ -384,6 +401,8 @@ public class MatchManager : MonoBehaviour
|
||||
|
||||
// A hider that leaves mid-match can never be found - drop it so "all hiders found" can still trigger.
|
||||
_pendingHiders.Remove(clientId);
|
||||
// Free the ephemeral clientId -> identity mapping (the clientId will be reused by a later joiner).
|
||||
_persistentIds.Remove(clientId);
|
||||
|
||||
if (!_hasAuthorityClient || clientId != _authorityClientId) return;
|
||||
if (_net.IsHost && clientId == _net.LocalClientId) return; // host shutting down; teardown handles it
|
||||
@@ -400,6 +419,50 @@ public class MatchManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
// Fired locally when OUR own client connection completes (host and pure client alike). The host is already
|
||||
// the server, so it records its own identity directly; a pure client reports it to the server by message.
|
||||
private void OnLocalClientConnected(ulong clientId)
|
||||
{
|
||||
if (_net == null || clientId != _net.LocalClientId) return;
|
||||
|
||||
if (_net.IsServer) RegisterIdentity(_net.LocalClientId, PersistentPlayerId.Value);
|
||||
else SendIdentityToServer(PersistentPlayerId.Value);
|
||||
}
|
||||
|
||||
// Client -> server: report this install's persistent id so the server can map it to our ephemeral clientId.
|
||||
private void SendIdentityToServer(string persistentId)
|
||||
{
|
||||
var cmm = _net != null ? _net.CustomMessagingManager : null;
|
||||
if (cmm == null || string.IsNullOrEmpty(persistentId)) return;
|
||||
// Grows up to the cap if a future id source is longer than the initial buffer (persistent ids are short).
|
||||
using (var writer = new FastBufferWriter(256, Allocator.Temp, 1024))
|
||||
{
|
||||
writer.WriteValueSafe(persistentId);
|
||||
cmm.SendNamedMessage(MsgIdentity, NetworkManager.ServerClientId, writer);
|
||||
}
|
||||
}
|
||||
|
||||
// Server handler: a client reported its persistent per-install id right after connecting.
|
||||
private void OnIdentityMessage(ulong senderClientId, FastBufferReader reader)
|
||||
{
|
||||
reader.ReadValueSafe(out string persistentId);
|
||||
if (_net == null || !_net.IsServer) return;
|
||||
RegisterIdentity(senderClientId, persistentId);
|
||||
}
|
||||
|
||||
// Server-only. Records the ephemeral clientId -> persistent id mapping (idempotent; ignores empty ids).
|
||||
private void RegisterIdentity(ulong clientId, string persistentId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(persistentId)) return;
|
||||
_persistentIds[clientId] = persistentId;
|
||||
Debug.Log($"[MatchManager] Client {clientId} persistent id: {persistentId}", this);
|
||||
}
|
||||
|
||||
/// <summary>Server-only. The stable per-install id a client reported on connect (survives that client's
|
||||
/// app restarts), or null if unknown / not yet received. Foundation for reconnect recognition.</summary>
|
||||
public string PersistentIdOf(ulong clientId) =>
|
||||
_persistentIds.TryGetValue(clientId, out var id) ? id : null;
|
||||
|
||||
// Spawns the given prefab as a client's player object into the currently active gameplay scene. No-op
|
||||
// if the client already owns a player object (double-spawn guard) or has since disconnected.
|
||||
private void SpawnFor(ulong clientId, GameObject prefab, Vector3 at)
|
||||
|
||||
Reference in new issue
Block a user