diff --git a/server/src/Hokm.Server/Game/GameManager.cs b/server/src/Hokm.Server/Game/GameManager.cs index c771678..ef39b66 100644 --- a/server/src/Hokm.Server/Game/GameManager.cs +++ b/server/src/Hokm.Server/Game/GameManager.cs @@ -67,9 +67,10 @@ public sealed partial class GameManager if (_waiting.Any(w => w.player.UserId == p.UserId)) return; var timer = new Timer(_ => FlushTicket(p.UserId), null, QueueWaitMs, Timeout.Infinite); _waiting.Add((p, timer, DateTime.UtcNow)); - _ = _hub.Clients.User(p.UserId).SendAsync("matchmaking", - new MatchmakingStateDto("searching", _waiting.Count, null)); if (_waiting.Count >= 4) FormGroupLocked(4); + // Tell EVERYONE still waiting the new count (so friends queuing together + // see each other join), not just the player who just joined. + BroadcastQueueLocked(); } } @@ -78,10 +79,24 @@ public sealed partial class GameManager lock (_mmLock) { var idx = _waiting.FindIndex(w => w.player.UserId == userId); - if (idx >= 0) { _waiting[idx].timer.Dispose(); _waiting.RemoveAt(idx); } + if (idx >= 0) + { + _waiting[idx].timer.Dispose(); + _waiting.RemoveAt(idx); + BroadcastQueueLocked(); + } } } + /// Push the current queue size to every waiting player (call inside _mmLock). + private void BroadcastQueueLocked() + { + int n = _waiting.Count; + foreach (var w in _waiting) + _ = _hub.Clients.User(w.player.UserId).SendAsync("matchmaking", + new MatchmakingStateDto("searching", n, null)); + } + /// Client safety net: re-send the current game state to a player who /// may have missed the initial broadcast (green-felt freeze guard). public void Resync(string userId) diff --git a/src/components/screens/MatchmakingScreen.tsx b/src/components/screens/MatchmakingScreen.tsx index 29752f9..6342595 100644 --- a/src/components/screens/MatchmakingScreen.tsx +++ b/src/components/screens/MatchmakingScreen.tsx @@ -1,7 +1,7 @@ "use client"; import { AnimatePresence, motion } from "framer-motion"; -import { Crown, Loader2 } from "lucide-react"; +import { Crown, Loader2, Users } from "lucide-react"; import { useEffect, useState } from "react"; import { ScreenShell } from "@/components/online/ScreenHeader"; import { Avatar } from "@/components/online/Avatar"; @@ -140,6 +140,16 @@ export function MatchmakingScreen() { {searching && ( <>
{elapsed}s
+ {(mm.waiting ?? 0) >= 2 && ( + + + {t("mm.inQueue", { n: mm.waiting! })} + + )} {elapsed >= 40 ? (

{t("mm.stuck")}

) : ( diff --git a/src/lib/i18n.tsx b/src/lib/i18n.tsx index 834f50b..0a7b0c4 100644 --- a/src/lib/i18n.tsx +++ b/src/lib/i18n.tsx @@ -256,6 +256,7 @@ const fa: Dict = { "mm.found": "بازیکنان پیدا شدند!", "mm.ready": "آماده شروع", "mm.fillHint": "منتظر پیوستن یک بازیکن آنلاین هستیم… می‌توانی همین حالا با ربات شروع کنی", + "mm.inQueue": "{n} بازیکن در صف", "mm.stuck": "اتصال به سرور طول کشید. لطفاً «لغو» را بزنید و دوباره تلاش کنید.", "intro.found": "بازیکنان آماده‌اند!", "intro.getReady": "بازی در حال شروع است…", @@ -644,6 +645,7 @@ const en: Dict = { "mm.found": "Players found!", "mm.ready": "Ready to start", "mm.fillHint": "Waiting for an online player to join… or start now with bots", + "mm.inQueue": "{n} players in queue", "mm.stuck": "Connecting to the server is taking too long. Tap Cancel and try again.", "mm.cancel": "Cancel", "mm.start": "Enter game", diff --git a/src/lib/online/signalr-service.ts b/src/lib/online/signalr-service.ts index c61ab0a..663f649 100644 --- a/src/lib/online/signalr-service.ts +++ b/src/lib/online/signalr-service.ts @@ -160,7 +160,7 @@ export class SignalrService implements OnlineService { .build(); conn.on("matchmaking", (s: { phase: string; players: number; queuePosition: number | null }) => - this.emitMM(s.phase, s.queuePosition ?? undefined)); + this.emitMM(s.phase, s.queuePosition ?? undefined, s.players)); conn.on("matchFound", () => { this.emitMM("ready"); // Safety net: if the initial state never lands (dropped/raced), ask the @@ -226,7 +226,7 @@ export class SignalrService implements OnlineService { } } - private emitMM(phase: string, queuePosition?: number) { + private emitMM(phase: string, queuePosition?: number, waiting?: number) { const state: MatchmakingState = { phase: phase as MatchmakingState["phase"], players: [], @@ -234,6 +234,7 @@ export class SignalrService implements OnlineService { ranked: this.mmRanked, stake: this.mmStake, queuePosition, + waiting, }; this.mmCbs.forEach((cb) => cb(state)); } diff --git a/src/lib/online/types.ts b/src/lib/online/types.ts index cb3c813..060dee9 100644 --- a/src/lib/online/types.ts +++ b/src/lib/online/types.ts @@ -390,6 +390,8 @@ export interface MatchmakingState { elapsedMs: number; ranked: boolean; stake: number; + /** live count of real humans waiting in the matchmaking queue (incl. you). */ + waiting?: number; /** position in the queue when phase === "queued" */ queuePosition?: number; }