docs(game-server): study PlayFab Groups/Party emulation for squad invites
Build Game Server / build (push) Successful in 18s
Build Game Server / build (push) Successful in 18s
This commit is contained in:
@@ -0,0 +1,103 @@
|
|||||||
|
# Étude — Émulation PlayFab Groups/Party pour l'invite de squad
|
||||||
|
|
||||||
|
> Objectif : permettre à un joueur d'**inviter un ami** à faire équipe (squad) sur le serveur
|
||||||
|
> privé. Étude du mécanisme, de l'état actuel, d'une conception d'émulation et d'un plan.
|
||||||
|
> Branche : `game-server`.
|
||||||
|
|
||||||
|
## 1. État actuel (ce qui existe déjà)
|
||||||
|
|
||||||
|
**Roster de squad en mémoire** — `Services/Squad/SquadService.cs` :
|
||||||
|
- Squads clés par un **`squadId` fourni par le client** (`JoinOrCreate(squadId, userId, …)`).
|
||||||
|
- Membre = profil (playerId, displayName), `onlineState`, `isReady`, `selectedMap`, `isLeader`.
|
||||||
|
- `_userToSquad` permet à `TryGetCompleteSquadInfo` (qui ne porte pas de squadId) de retrouver la squad du joueur.
|
||||||
|
|
||||||
|
**Fonctions CloudScript squad présentes** : `TryGetCompleteSquadInfo`, `SquadMemberReadyForMatch`
|
||||||
|
(pousse déjà via SignalR), `SquadMemberSelectedMap`, `SquadMemberStartingDeployFlow`,
|
||||||
|
`GetFriendList`, `ClientsideFriendsImport`.
|
||||||
|
|
||||||
|
**Ce qui MANQUE** (cœur du problème) : aucune fonction de **création de squad**, d'**invitation**,
|
||||||
|
d'**acceptation/refus**, de **join/leave/kick**. Le flux actuel suppose que le client **connaît déjà
|
||||||
|
un `squadId`** (party formée ailleurs) et se contente d'y rattacher les membres.
|
||||||
|
|
||||||
|
**Infra de push temps-réel DISPONIBLE** — `Hubs/CycleHub.cs` + `Hubs/SignalRConnectionRegistry.cs` :
|
||||||
|
le client se connecte au hub avec `?uid=<playerId>` ; le registry mappe `uid → connectionId`. Le
|
||||||
|
serveur peut donc **pousser un message ciblé** à un joueur précis (`registry.GetConnection(uid)` +
|
||||||
|
`IHubContext<CycleHub>.Clients.Client(conn).SendAsync(<event>, payload)`). C'est déjà utilisé par
|
||||||
|
`SquadMemberReadyForMatch` et par le signal « travel to match » du matchmaking. **→ Le canal de
|
||||||
|
livraison de l'invite existe déjà.**
|
||||||
|
|
||||||
|
## 2. Comment The Cycle forme-t-il les squads ? (hypothèses + preuves)
|
||||||
|
|
||||||
|
Le `squadId` étant **fourni par le client**, la party est créée là où le client obtient cet id.
|
||||||
|
Quatre mécanismes candidats côté PlayFab/Steam :
|
||||||
|
|
||||||
|
| # | Mécanisme | Ce que ça implique côté serveur | Indice |
|
||||||
|
|---|---|---|---|
|
||||||
|
| A | **PlayFab Entity Groups** (`/Group/CreateGroup`, `/Group/InviteToGroup`, `/Group/AcceptGroupInvitation`, `/Group/ListGroupMembers`…) | Implémenter le sous-ensemble Groups (entités déjà émulées). `squadId` = group entity id. | Fort : squads persistantes chez PlayFab passent par Groups ; l'id opaque partagé colle. |
|
||||||
|
| B | **PlayFab Lobby** (Multiplayer : `CreateLobby`/`JoinLobby`/`InviteToLobby` via connection string) | Implémenter l'API Lobby. `squadId` = lobbyId. | Moyen : plutôt matchmaking S3+. |
|
||||||
|
| C | **Lobby Steam** (invite via overlay Steam) + backend qui suit | Rien à créer pour l'invite (100% Steam) ; le serveur ne fait que suivre le `squadId` rapporté. | Moyen : S2 = Steam-only ; explique le `squadId` client. Mais alors l'invite n'est pas pilotable serveur. |
|
||||||
|
| D | **Fonctions CloudScript d'invite** (non observées) | Implémenter `InvitePlayerToSquad`/`RespondToSquadInvite`/… | Faible : aucune trace. |
|
||||||
|
|
||||||
|
**Preuve manquante (bloquant)** : les logs prod de la session à 2 ont été **écrasés** par nos
|
||||||
|
redéploiements. Impossible aujourd'hui de voir l'appel exact d'invite. De plus, l'émulateur peut
|
||||||
|
**404 silencieusement** un endpoint PlayFab natif non routé (ex. `/Group/CreateGroup`) → un tel
|
||||||
|
appel n'apparaît pas comme fonction CloudScript. **Il faut donc capturer l'appel réel** (voir §5,
|
||||||
|
Phase 0) — d'autant que le **bouton « inviter » n'apparaît pas tant que la tuile ami est vide**
|
||||||
|
(bug de forme `GetFriendList`, corrigé PR #15, à valider en jeu). Tant que la liste d'amis ne
|
||||||
|
s'affiche pas, aucune invite ne peut même être tentée.
|
||||||
|
|
||||||
|
## 3. Conception d'émulation proposée (indépendante du mécanisme exact)
|
||||||
|
|
||||||
|
Quel que soit A/B/C/D, les briques serveur à construire sont les mêmes :
|
||||||
|
|
||||||
|
**3.1 Étendre `SquadService`** (états d'invite) :
|
||||||
|
- `CreateSquad(leaderUserId) → squadId` (GUID serveur si le client n'en impose pas).
|
||||||
|
- `Invite(squadId, fromUserId, toUserId)` → enregistre une **invitation en attente** `{squadId, from, to, expiresAt}`.
|
||||||
|
- `RespondToInvite(toUserId, squadId, accept)` → si accept : `JoinOrCreate` ; sinon purge.
|
||||||
|
- `Leave` / `Kick(byLeader)` / transfert de leadership à la sortie du leader.
|
||||||
|
- `GetPendingInvites(userId)` (fallback poll).
|
||||||
|
|
||||||
|
**3.2 Surface d'API** — deux options selon la capture (§5) :
|
||||||
|
- **Si Groups (A)** : router `POST /Group/CreateGroup`, `/Group/InviteToGroup`,
|
||||||
|
`/Group/AcceptGroupInvitation`, `/Group/ListGroupMembers`, `/Group/RemoveMembers` vers
|
||||||
|
`SquadService` (les EntityToken sont déjà émulés). C'est le plus « natif ».
|
||||||
|
- **Si CloudScript (D) / custom** : ajouter les fonctions `[CloudScriptFunction(...)]`
|
||||||
|
correspondantes (noms/shapes calqués sur la capture).
|
||||||
|
- **Si Steam (C)** : rien pour l'invite (Steam) ; s'assurer seulement que le roster se peuple bien
|
||||||
|
quand les deux clients rapportent le même `squadId` (déjà géré par `SquadService`).
|
||||||
|
|
||||||
|
**3.3 Livraison temps-réel de l'invite** (réutilise l'infra existante) :
|
||||||
|
- À l'invitation, pousser à l'invité : `registry.GetConnection(toUserId)` +
|
||||||
|
`hub.Clients.Client(conn).SendAsync("<SquadInviteReceived>", payload)`.
|
||||||
|
- **Nom d'event + payload = à confirmer côté client** (le client doit écouter cet event). Fallback :
|
||||||
|
`GetPendingSquadInvites` (CloudScript) que le client poll à l'ouverture du menu social.
|
||||||
|
|
||||||
|
**3.4 Persistance** : le roster peut rester en mémoire (petit serveur), mais les invitations
|
||||||
|
gagnent à être **persistées** (UserData `PendingSquadInvites`) pour survivre à un reload/redeploy et
|
||||||
|
au cas « ami à la station » — combiné au push SignalR.
|
||||||
|
|
||||||
|
## 4. Limite structurelle importante (à dire clairement)
|
||||||
|
|
||||||
|
Émuler l'invite permet de **former une squad et de se mettre prêt** ensemble (lobby/ready-up).
|
||||||
|
Mais **déployer réellement ensemble dans le même raid** nécessite le **serveur de jeu dédié**
|
||||||
|
(`Prospect.Server.Game`), qui est bloqué au mur **DTLS-PSK** (cf. reste de cette branche). Les raids
|
||||||
|
S2 sont **hébergés côté client** (solo) : sans serveur dédié, deux joueurs ne peuvent pas partager
|
||||||
|
la même instance de raid. Donc :
|
||||||
|
- **Invite + squad + ready-up** : émulable via l'API (cette étude).
|
||||||
|
- **Co-op en raid** : dépend du serveur dédié (autre chantier, DTLS-PSK).
|
||||||
|
|
||||||
|
## 5. Plan par phases
|
||||||
|
|
||||||
|
- **Phase 0 — Capture (débloque tout)** : valider le fix `GetFriendList` en jeu (la tuile ami doit
|
||||||
|
afficher le pseudo) → le bouton « inviter » réapparaît → tenter une invite et **capturer les
|
||||||
|
appels** (ajouter un logging *catch-all* des requêtes non routées + corps, pour voir un éventuel
|
||||||
|
`/Group/…` 404). Résultat : on sait A/B/C/D.
|
||||||
|
- **Phase 1 — Invite** : implémenter la surface identifiée + états `SquadService` + push SignalR ;
|
||||||
|
persister les invitations.
|
||||||
|
- **Phase 2 — Ready-up/déploiement groupé** : compléter (déjà amorcé) ; le déploiement co-op réel
|
||||||
|
reste gated par le serveur dédié.
|
||||||
|
|
||||||
|
## 6. Prochaine action concrète
|
||||||
|
Repro en jeu (liste d'amis corrigée) + logging catch-all → capturer l'appel d'invite. Sans cette
|
||||||
|
capture, toute implémentation d'invite serait une supposition sur les noms/shapes attendus par le
|
||||||
|
client (paks chiffrés → pas de RE statique).
|
||||||
Reference in New Issue
Block a user