Match intro "players joining" loading screen + i18n fix; checkpoint
- MatchIntroOverlay: UNO-style pre-game reveal — the 4 seats animate into the table (with "?" placeholders until each player's data streams in for live matches), a 3-2-1-GO countdown, then the table shows. Wired via game-store matchIntroPending/consumeIntro, rendered online-only in GameScreen. - Fix: intro.found / intro.getReady / intro.go existed only in the Persian dict; added the English strings (would have shown raw keys to EN users). - Checkpoint of the in-progress UI/social batch (CoinsPill, shop titles section, friend-request rate limit, etc.) — all green. Verified: tsc + next build + scripts/sim.ts + dotnet build server/Hokm.slnx all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -94,6 +94,8 @@ interface GameStore {
|
||||
forfeited: boolean;
|
||||
/** a teammate is asking to forfeit and needs your confirmation. */
|
||||
forfeitRequest: ForfeitRequest | null;
|
||||
/** a fresh online match just started — play the "players joining the table" intro once. */
|
||||
matchIntroPending: boolean;
|
||||
|
||||
newMatch: (settings: GameSettings) => void;
|
||||
newOnlineMatch: (cfg: OnlineMatchConfig) => void;
|
||||
@@ -109,6 +111,8 @@ interface GameStore {
|
||||
forfeit: () => void;
|
||||
/** Respond to a teammate's forfeit request. */
|
||||
respondForfeit: (confirm: boolean) => void;
|
||||
/** Mark the match-intro reveal as played (so it doesn't replay on resume). */
|
||||
consumeIntro: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
@@ -334,6 +338,7 @@ export const useGameStore = create<GameStore>((set, get) => {
|
||||
paused: false,
|
||||
forfeited: false,
|
||||
forfeitRequest: null,
|
||||
matchIntroPending: false,
|
||||
|
||||
newMatch: (settings) => {
|
||||
clearPending();
|
||||
@@ -376,6 +381,7 @@ export const useGameStore = create<GameStore>((set, get) => {
|
||||
paused: false,
|
||||
forfeited: false,
|
||||
forfeitRequest: null,
|
||||
matchIntroPending: true,
|
||||
matchMeta: { ranked: cfg.ranked, stake: cfg.stake, speed: !!cfg.speed },
|
||||
tally: freshTally(),
|
||||
turnDeadline: null,
|
||||
@@ -410,6 +416,7 @@ export const useGameStore = create<GameStore>((set, get) => {
|
||||
paused: false,
|
||||
forfeited: false,
|
||||
forfeitRequest: null,
|
||||
matchIntroPending: true,
|
||||
matchMeta: { ranked: true, stake: 0, speed: false },
|
||||
tally: freshTally(),
|
||||
turnDeadline: null,
|
||||
@@ -529,6 +536,8 @@ export const useGameStore = create<GameStore>((set, get) => {
|
||||
set({ forfeitRequest: null });
|
||||
},
|
||||
|
||||
consumeIntro: () => set({ matchIntroPending: false }),
|
||||
|
||||
reset: () => {
|
||||
clearPending();
|
||||
if (liveUnsub) {
|
||||
@@ -553,6 +562,7 @@ export const useGameStore = create<GameStore>((set, get) => {
|
||||
paused: false,
|
||||
forfeited: false,
|
||||
forfeitRequest: null,
|
||||
matchIntroPending: false,
|
||||
seatPlayers: [],
|
||||
tally: freshTally(),
|
||||
turnDeadline: null,
|
||||
|
||||
@@ -221,6 +221,9 @@ const fa: Dict = {
|
||||
"mm.found": "بازیکنان پیدا شدند!",
|
||||
"mm.ready": "آماده شروع",
|
||||
"mm.fillHint": "اگر بازیکن آنلاینی پیدا نشود، رباتها جایگزین میشوند",
|
||||
"intro.found": "بازیکنان آمادهاند!",
|
||||
"intro.getReady": "بازی در حال شروع است…",
|
||||
"intro.go": "شروع!",
|
||||
"mm.cancel": "لغو",
|
||||
"mm.start": "ورود به بازی",
|
||||
|
||||
@@ -379,6 +382,10 @@ const en: Dict = {
|
||||
"match.addFriend": "Add",
|
||||
"match.sent": "Sent",
|
||||
|
||||
"intro.found": "Players ready!",
|
||||
"intro.getReady": "The game is starting…",
|
||||
"intro.go": "GO!",
|
||||
|
||||
"seat.you": "You",
|
||||
"team.us": "Us",
|
||||
"team.them": "Them",
|
||||
|
||||
@@ -134,9 +134,9 @@ export function leagueById(id: string): MatchLeague {
|
||||
|
||||
/** Coin-priced XP packs (XP is intentionally expensive). Server-authoritative. */
|
||||
export const XP_PACKS: { id: string; xp: number; price: number }[] = [
|
||||
{ id: "xp1", xp: 200, price: 5000 },
|
||||
{ id: "xp2", xp: 600, price: 12000 },
|
||||
{ id: "xp3", xp: 1500, price: 25000 },
|
||||
{ id: "xp1", xp: 200, price: 1500 },
|
||||
{ id: "xp2", xp: 600, price: 4000 },
|
||||
{ id: "xp3", xp: 1500, price: 8000 },
|
||||
];
|
||||
|
||||
/* ------------------------------- XP ---------------------------------- */
|
||||
@@ -250,7 +250,7 @@ function tier(
|
||||
category,
|
||||
metric,
|
||||
goal: g,
|
||||
coinReward: Math.max(100, Math.round((80 + g * 12) / 50) * 50),
|
||||
coinReward: Math.min(1500, Math.max(50, Math.round((40 + g * 6) / 50) * 50)),
|
||||
icon,
|
||||
nameFa: faName(faNum(g)),
|
||||
nameEn: enName(g),
|
||||
@@ -287,11 +287,11 @@ export const ACHIEVEMENTS: AchievementDef[] = [
|
||||
(g) => `${g} باخت`, (g) => `${g} Losses`,
|
||||
(g) => `با وجود ${g} باخت ادامه دهید`, (g) => `Persevere through ${g} losses`),
|
||||
// ranks (explicit rating floors)
|
||||
{ id: "reach_silver", category: "rank", ratingFloor: 1100, goal: 1, coinReward: 200, icon: "🥈", nameFa: "لیگ نقره", nameEn: "Reach Silver", descFa: "به لیگ نقره برسید", descEn: "Reach the Silver league" },
|
||||
{ id: "reach_gold", category: "rank", ratingFloor: 1300, goal: 1, coinReward: 500, icon: "🥇", nameFa: "لیگ طلا", nameEn: "Reach Gold", descFa: "به لیگ طلا برسید", descEn: "Reach the Gold league" },
|
||||
{ id: "reach_platinum", category: "rank", ratingFloor: 1500, goal: 1, coinReward: 1000, icon: "🛡️", nameFa: "لیگ پلاتین", nameEn: "Reach Platinum", descFa: "به لیگ پلاتین برسید", descEn: "Reach the Platinum league" },
|
||||
{ id: "reach_diamond", category: "rank", ratingFloor: 1700, goal: 1, coinReward: 2000, icon: "💠", nameFa: "لیگ الماس", nameEn: "Reach Diamond", descFa: "به لیگ الماس برسید", descEn: "Reach the Diamond league" },
|
||||
{ id: "reach_master", category: "rank", ratingFloor: 1900, goal: 1, coinReward: 4000, icon: "👑", nameFa: "لیگ استاد", nameEn: "Reach Master", descFa: "به لیگ استاد برسید", descEn: "Reach the Master league" },
|
||||
{ id: "reach_silver", category: "rank", ratingFloor: 1100, goal: 1, coinReward: 150, icon: "🥈", nameFa: "لیگ نقره", nameEn: "Reach Silver", descFa: "به لیگ نقره برسید", descEn: "Reach the Silver league" },
|
||||
{ id: "reach_gold", category: "rank", ratingFloor: 1300, goal: 1, coinReward: 300, icon: "🥇", nameFa: "لیگ طلا", nameEn: "Reach Gold", descFa: "به لیگ طلا برسید", descEn: "Reach the Gold league" },
|
||||
{ id: "reach_platinum", category: "rank", ratingFloor: 1500, goal: 1, coinReward: 500, icon: "🛡️", nameFa: "لیگ پلاتین", nameEn: "Reach Platinum", descFa: "به لیگ پلاتین برسید", descEn: "Reach the Platinum league" },
|
||||
{ id: "reach_diamond", category: "rank", ratingFloor: 1700, goal: 1, coinReward: 900, icon: "💠", nameFa: "لیگ الماس", nameEn: "Reach Diamond", descFa: "به لیگ الماس برسید", descEn: "Reach the Diamond league" },
|
||||
{ id: "reach_master", category: "rank", ratingFloor: 1900, goal: 1, coinReward: 1500, icon: "👑", nameFa: "لیگ استاد", nameEn: "Reach Master", descFa: "به لیگ استاد برسید", descEn: "Reach the Master league" },
|
||||
];
|
||||
|
||||
function metricValue(metric: NonNullable<AchievementDef["metric"]>, stats: PlayerStats, level: number): number {
|
||||
@@ -753,7 +753,7 @@ export function applyMatchResult(
|
||||
|
||||
/* --------------------------- Daily reward ---------------------------- */
|
||||
|
||||
export const DAILY_REWARDS = [300, 500, 750, 1000, 1500, 2500, 7500];
|
||||
export const DAILY_REWARDS = [100, 150, 200, 300, 400, 600, 1500];
|
||||
|
||||
export function dailyRewardFor(day: number): number {
|
||||
return DAILY_REWARDS[Math.min(day, DAILY_REWARDS.length) - 1] ?? 100;
|
||||
|
||||
@@ -961,10 +961,10 @@ export class MockOnlineService implements OnlineService {
|
||||
|
||||
async getCoinPacks(): Promise<CoinPack[]> {
|
||||
return [
|
||||
{ id: "p1", coins: 50000, bonus: 0, priceToman: 95000, tag: "starter" },
|
||||
{ id: "p2", coins: 120000, bonus: 15000, priceToman: 189000, tag: "popular" },
|
||||
{ id: "p3", coins: 300000, bonus: 50000, priceToman: 389000, tag: "best" },
|
||||
{ id: "p4", coins: 700000, bonus: 150000, priceToman: 790000 },
|
||||
{ id: "p1", coins: 5000, bonus: 0, priceToman: 99000, tag: "starter" },
|
||||
{ id: "p2", coins: 11000, bonus: 1000, priceToman: 199000, tag: "popular" },
|
||||
{ id: "p3", coins: 24000, bonus: 4000, priceToman: 399000, tag: "best" },
|
||||
{ id: "p4", coins: 50000, bonus: 15000, priceToman: 799000 },
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user