feat(rooms): real server-side private games with friend invites (no bot swap)
Private rooms were 100% client-simulated (the "friend" auto-accepted then bots filled invited seats). Now they're server-authoritative over SignalR: Server (GameManager.PrivateRooms + GameHub): - Room registry with create/invite/accept/decline/addBot/clearSeat/start/leave. - Invite pushes a `roomInvite` to that user (Clients.User); the seat stays "invited" (a pending guest with their real profile, resolved server-side) — it is NEVER replaced by a bot. - StartPrivate refuses while any invite is pending; only EMPTY seats fill with bots. Then it spins up a live GameRoom and matchFound → both devices enter. - Host leave / disconnect closes the room (roomClosed); members free their seat. Client: - signalr-service implements the room methods over the hub (+ room/roomInvite/ roomClosed events, room mapping, onRoomInvite); mock keeps offline no-ops. - online-store accept/declineInvite; RoomScreen blocks "Start" while an invite is pending and auto-enters the live game on matchFound (host + friend). - New global InviteModal (accept/decline) + i18n (invite.*, room.waitAccept). Addresses: (1) no bot replacement, (2) game waits for acceptance, (3) invited friend shown as a pending guest with their name/avatar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,8 @@ interface OnlineStore {
|
||||
clearSeat: (seat: 1 | 2 | 3) => Promise<void>;
|
||||
startRoom: () => Promise<void>;
|
||||
leaveRoom: () => Promise<void>;
|
||||
acceptInvite: () => Promise<void>;
|
||||
declineInvite: () => Promise<void>;
|
||||
|
||||
startMatchmaking: (opts: MatchmakingOptions) => Promise<void>;
|
||||
cancelMatchmaking: () => Promise<void>;
|
||||
@@ -133,6 +135,15 @@ export const useOnlineStore = create<OnlineStore>((set, get) => ({
|
||||
}
|
||||
set({ room: null });
|
||||
},
|
||||
acceptInvite: async () => {
|
||||
const svc = getService();
|
||||
if (roomUnsub) roomUnsub();
|
||||
roomUnsub = svc.onRoom((r) => set({ room: { ...r } })); // subscribe first so we catch the room push
|
||||
await svc.acceptInvite();
|
||||
},
|
||||
declineInvite: async () => {
|
||||
await getService().declineInvite();
|
||||
},
|
||||
|
||||
startMatchmaking: async (opts) => {
|
||||
const svc = getService();
|
||||
|
||||
Reference in New Issue
Block a user