Party
Ephemeral parties: leader, ready checks, kicks.
Seams you implement 1
Crossplay calls these; your game supplies them. Each ships an inert or permissive default, so register yours before services.AddCrossplayParty(); and it wins.
IPartyReadySink game SPI
The post-ready-check seam the Party piece OWNS — the analog of Matchmaking's IMatchFormedSink and Lobbies' ILobbyStartSink: fired once when a party's ready check completes with EVERY member ready, so a composition layer can start a match / instance for exactly that group of that size — without Party ever referencing Matches or the Director. The shipped default (NullPartyReadySink) does nothing; a game or a Hosting bridge registers its own BEFORE AddCrossplayParty (TryAdd) to win the seam. Removable: with no sink a passed ready check just broadcasts PartyReadyState exactly as before.
-
void PartyReady(long partyId, IReadOnlyList<PartyReadyMember> members)A party's ready check passed with all members ready. members carries each member's account/name and their LocalSession when local.
Providers you can swap 1
Infrastructure seams. Crossplay ships a working implementation of each — replacing one changes where data lives or how it moves, never a game rule.
IPartyStore provider
Persistence SPI for live parties and pending invites. One party per account (a member belongs to at most one). Abstracted so a game can swap in a durable implementation; the framework ships an in-memory default (InMemoryPartyStore) for dev and single-node servers.
-
void AddInvite(long accountId, long partyId)Records a pending invite of accountId to partyId.
-
bool AddMember(long partyId, long accountId)Adds a member to a party. Returns false if the party is missing or already a member.
-
PartyRecord Create(long leaderAccountId)Creates a new party led by (and containing) leaderAccountId.
-
PartyRecord GetByAccount(long accountId)Finds the party containing accountId, or null.
-
PartyRecord GetById(long partyId)Looks up a party by id, or null.
-
bool HasInvite(long accountId, long partyId)True if there is a pending invite of accountId to partyId.
-
IReadOnlyList<long> Members(long partyId)The current member account ids of a party (empty if missing).
-
bool Remove(long partyId)Removes (disbands) a party. Returns false if it did not exist.
-
void RemoveInvite(long accountId, long partyId)Clears a pending invite of accountId to partyId.
-
bool RemoveMember(long partyId, long accountId)Removes a member from a party. Returns false if the party is missing or not a member.
Services you call 1
Crossplay implements these. Resolve them from DI and call them from your own systems.
IPartyService
The party service the game calls to drive group membership. The same instance also runs the disconnect-cleanup lifecycle (it is registered as a Core ISessionLifecycleListener), so a leaving player is pulled from their party whether they used the leave message or simply dropped their connection. Every Try* operation returns a PartyResult and, on failure, sends the caller a coded notification (a PartyNotificationCodes value); any membership change broadcasts an updated PartyRoster to the party room.
-
void BroadcastRoster(long partyId)Broadcasts the current roster of partyId to its room.
-
PartyResult TryAccept(ISession session, long partyId)Accepts a pending invite to partyId and joins that party.
-
PartyResult TryCreate(ISession session)Creates a party led by (and containing) the session, if it is not already in one.
-
bool TryGetPartyOf(long accountId, out IReadOnlyList<long> members)The members of accountId's party (donor-gap E2: the membership READ seam reward-sharing composition builds on — XP splits, party-aware loot, assist credit). False when the account is not in a party; members includes the account itself.
-
PartyResult TryInvite(ISession inviter, string targetName)Invites the online player named targetName to the inviter's party, sending them a PartyInviteNotification.
-
PartyResult TryKick(ISession session, long targetAccountId)Kicks a member from the caller's party (leader only). The target — online or offline — is removed; an online target is notified with Kicked.
-
PartyResult TryLeave(ISession session)Removes the session from its current party (the explicit leave).
-
PartyResult TryReady(ISession session, bool ready)Records the caller's ready answer for the active round and broadcasts the updated PartyReadyState (which is the client's feedback).
-
PartyResult TryStartReadyCheck(ISession session)Starts a ready check across the party (leader only; a new round replaces any active one). Broadcasts a PartyReadyCheck plus the initial PartyReadyState.
Configuration 1
Every tunable lives in an options object — there are no magic numbers to hunt for.
PartyOptions
Configurable party rules, passed to AddCrossplayParty. Defaults live here, never inline in handlers/services (the no-hardcoded-values rule).
-
int MaxMembers { get; set; }Maximum members in a party, leader included. Invites past this cap are rejected with PartyFull.
Wire messages 17
The protocol this piece speaks. Ids are allocated per piece so they can never collide.
PartyCreateRequest Client -> server: create a new party led by the caller.
PartyCreateResponse Server -> client: result of a PartyCreateRequest.
PartyInviteRequest Client -> server: invite a player (by display name) to the caller's party.
PartyInviteResponse Server -> client: result of a PartyInviteRequest.
PartyInviteNotification Server -> client: the invited player is told about a pending invite.
PartyAcceptRequest Client -> server: accept a pending invite to a party.
PartyAcceptResponse Server -> client: result of a PartyAcceptRequest.
PartyLeaveRequest Client -> server: leave the caller's current party.
PartyLeaveResponse Server -> client: result of a PartyLeaveRequest.
PartyRoster Server -> party members: the current roster. Sent to the party room on every membership change and as the reply to a successful create/accept.
PartyKickRequest Client → server: kick a member (leader only).
PartyKickResponse Server → client: kick result.
PartyReadyCheckStartRequest Client → server: start a ready check (leader only; replaces any active round).
PartyReadyCheckStartResponse Server → client: ready-check start result.
PartyReadyCheck Server → client: a ready check began (broadcast to the party).
PartyReadyResponse Client → server: my ready answer.
PartyReadyState Server → client: the round state after every answer (broadcast to the party).
Unity services you inject 1
Crossplay binds these in the client context. Inject and call them from your own MonoBehaviours and presenters.
IPartyClient
Data-layer party service (no UI): async create/invite/accept/leave RPCs plus push events for inbound invites and roster changes. Self-registers under the Crossplay client context.
-
event Action<PartyInviteNotification> InviteReceivedRaised when this client receives a pending party invite.
-
event Action<PartyRoster> RosterChangedRaised whenever the party roster changes (server push on every membership change).
-
UniTask<PartyCreateResponse> CreateAsync(CancellationToken ct = default)Creates a new party led by the caller.
-
UniTask<PartyInviteResponse> InviteAsync(string targetName, CancellationToken ct = default)Invites a player (by display name) to the caller's party.
-
UniTask<PartyAcceptResponse> AcceptAsync(long partyId, CancellationToken ct = default)Accepts a pending invite to the given party.
-
UniTask<PartyLeaveResponse> LeaveAsync(CancellationToken ct = default)Leaves the caller's current party.
-
UniTask<PartyKickResponse> KickAsync(long targetAccountId, CancellationToken ct = default)Kicks a member (leader only).
-
UniTask<PartyReadyCheckStartResponse> StartReadyCheckAsync(CancellationToken ct = default)Starts a ready check (leader only).
-
void Ready(bool ready)Answers the active ready check. Fire-and-forget — the broadcast state is the feedback.
-
event Action<PartyReadyCheck> ReadyCheckStartedRaised when a ready check begins.
-
event Action<PartyReadyState> ReadyStateChangedRaised as members answer (Complete/AllReady flags close the round).
UI views you can replace 1
Each ships a working uGUI panel you can drag into a scene. Substitute your own view to keep the logic and change the look — the presenter never knows.
ICrossplayPartyView
Narrow contract the presenter drives. UI-only: render calls in, user intents out.