100+ achievements, forfeit, leagues floor, bot humanize, 95k starter
Achievements: generator-driven, now 100+ across 7 categories (added Rulership) mirrored client + server with identical ids/goals/coins. New tracked stats: hakemRounds (be the hakem — incl. "7× Hakem"), roundsWon, plus losses metric. Custom achievement-only sticker packs (Rulership 👑, Firestorm 🔥) with new inline-SVG art (crown-gold, seven-zip, streak-fire), unlocked by hakem_7 / streak_10. Server GameRoom tallies hakem rounds per seat + rounds won per team; client tallies the same for vs-computer/private games (dealId-deduped). Forfeit (surrender): a player can request forfeit; if the teammate is a bot it auto-confirms, otherwise the human teammate gets a confirm/decline prompt (20s timeout). Result: forfeiting with ≥1 round won = normal loss; 0 rounds = Kot. Wired client↔server over the hub (RequestForfeit/ConfirmForfeit/DeclineForfeit + "forfeit" event); offline/vs-computer ends immediately in the store. Flag button + confirm dialogs in the table. Online count: never shows below 50 — live service floors the real count with a drifting believable number (mock base lowered to ~50–170). Matchmaking: real players get a longer priority window (9s) before bots fill; bots now occasionally react after winning a trick (humanize). Coins: starter pack is 95,000 Toman (50k coins); packs rescaled up (server + mock). Verified: dotnet build + tsc + next build clean; sim unlocks 57 achievements/500 matches; live server: starter=95000, a 7-hakem win unlocks hakem_7 + wins_1 with hakemRounds/roundsWon persisted. Images rebuilt on :1500/:1505. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -118,6 +118,8 @@ function defaultProfile(session: AuthSession): UserProfile {
|
||||
bestWinStreak: 0,
|
||||
currentWinStreak: 0,
|
||||
shutoutWins: 0,
|
||||
hakemRounds: 0,
|
||||
roundsWon: 0,
|
||||
},
|
||||
ownedAvatars: [AVATARS[0].id, AVATARS[1].id],
|
||||
ownedCardFronts: ["classic"],
|
||||
@@ -508,6 +510,12 @@ export class MockOnlineService implements OnlineService {
|
||||
onProfile(): Unsubscribe { return () => {}; }
|
||||
onReward(): Unsubscribe { return () => {}; }
|
||||
|
||||
// Forfeit is handled client-side for offline/mock games (see game-store).
|
||||
requestForfeit(): void {}
|
||||
confirmForfeit(): void {}
|
||||
declineForfeit(): void {}
|
||||
onForfeit(): Unsubscribe { return () => {}; }
|
||||
|
||||
onReaction(cb: (seat: number, reaction: string) => void): Unsubscribe {
|
||||
this.reactionCbs.add(cb);
|
||||
if (this.reactionTimer == null) {
|
||||
@@ -778,10 +786,10 @@ export class MockOnlineService implements OnlineService {
|
||||
|
||||
async getCoinPacks(): Promise<CoinPack[]> {
|
||||
return [
|
||||
{ id: "p1", coins: 1000, bonus: 0, priceToman: 19000 },
|
||||
{ id: "p2", coins: 5000, bonus: 500, priceToman: 89000, tag: "popular" },
|
||||
{ id: "p3", coins: 12000, bonus: 2000, priceToman: 179000, tag: "best" },
|
||||
{ id: "p4", coins: 30000, bonus: 7000, priceToman: 399000 },
|
||||
{ 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 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -796,11 +804,11 @@ export class MockOnlineService implements OnlineService {
|
||||
return { ok: true, profile: this.profile, coins: added };
|
||||
}
|
||||
|
||||
private onlineCount = 600 + Math.floor(Math.random() * 900);
|
||||
private onlineCount = 60 + Math.floor(Math.random() * 110);
|
||||
async getOnlineCount(): Promise<number> {
|
||||
// gentle random walk so the badge feels alive
|
||||
this.onlineCount += Math.round((Math.random() - 0.5) * 40);
|
||||
this.onlineCount = Math.max(120, Math.min(6000, this.onlineCount));
|
||||
// gentle random walk so the badge feels alive; never drops below 50
|
||||
this.onlineCount += Math.round((Math.random() - 0.45) * 12);
|
||||
this.onlineCount = Math.max(50, Math.min(4000, this.onlineCount));
|
||||
return this.onlineCount;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user