Show explicit blackjack settlements across the stack
- Replace round payout multipliers with per-player settlement amounts - Update blackjack panel to display wager, payout, and net results
This commit is contained in:
@@ -88,32 +88,30 @@ export class GameServer {
|
||||
this.publishRoomListUpdate();
|
||||
});
|
||||
|
||||
this.roomManager.emitter.on("round:settled", async ({ roomId, roundPayouts }) => {
|
||||
this.roomManager.emitter.on("round:settled", async ({ roomId, roundSettlements }) => {
|
||||
const room = this.roomManager.getRoom(roomId);
|
||||
if (!room || room.betAmount <= 0) return;
|
||||
const betAmount = room.betAmount;
|
||||
const gameName = gameRegistry.get(room.gameSlug)?.name ?? "Game";
|
||||
const payoutDetails: Record<string, { net: number }> = {};
|
||||
const settlementDetails: typeof roundSettlements = {};
|
||||
|
||||
for (const [playerId, multiplier] of Object.entries(roundPayouts)) {
|
||||
// roundPayout contains multipliers (e.g., win=2, blackjack=2.5, push=1)
|
||||
const grossAmount = Math.floor(betAmount * multiplier);
|
||||
const netProfit = grossAmount - betAmount;
|
||||
for (const [playerId, settlement] of Object.entries(roundSettlements)) {
|
||||
try {
|
||||
await economyService.modifyUserBalance(
|
||||
playerId,
|
||||
BigInt(grossAmount),
|
||||
TransactionType.GAME_WIN,
|
||||
`${gameName} round payout (room ${roomId.slice(0, 8)})`,
|
||||
);
|
||||
payoutDetails[playerId] = { net: netProfit };
|
||||
if (settlement.payout > 0) {
|
||||
await economyService.modifyUserBalance(
|
||||
playerId,
|
||||
BigInt(settlement.payout),
|
||||
TransactionType.GAME_WIN,
|
||||
`${gameName} round payout (room ${roomId.slice(0, 8)})`,
|
||||
);
|
||||
}
|
||||
settlementDetails[playerId] = settlement;
|
||||
} catch (err) {
|
||||
logger.error("web", `Round payout failed for ${playerId} in room ${roomId}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(payoutDetails).length > 0) {
|
||||
this.publish(`room:${roomId}`, { type: "ROUND_SETTLED", roomId, payouts: payoutDetails });
|
||||
if (Object.keys(settlementDetails).length > 0) {
|
||||
this.publish(`room:${roomId}`, { type: "ROUND_SETTLED", roomId, settlements: settlementDetails });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import mitt from "mitt";
|
||||
import { gameRegistry } from "@shared/games/registry";
|
||||
import type { Room, RoomSummary } from "./types";
|
||||
import type { RoundSettlement } from "@shared/games/types";
|
||||
|
||||
const ROOM_CONFIG = {
|
||||
WAITING_CLEANUP_MS: 60_000,
|
||||
@@ -9,7 +10,7 @@ const ROOM_CONFIG = {
|
||||
} as const;
|
||||
|
||||
type ActionResult =
|
||||
| { ok: true; state: unknown; gameOver: { winner: string | null; reason: string } | null; roundPayouts?: Record<string, number> }
|
||||
| { ok: true; state: unknown; gameOver: { winner: string | null; reason: string } | null; roundSettlements?: Record<string, RoundSettlement> }
|
||||
| { ok: false; error: string };
|
||||
|
||||
type CreateResult = { ok: true; roomId: string } | { ok: false; error: string };
|
||||
@@ -24,7 +25,7 @@ type RoomEvents = {
|
||||
"game:started": { roomId: string; spectatorView: unknown; playerViews: Map<string, unknown> };
|
||||
"game:updated": { roomId: string; spectatorView: unknown; playerViews: Map<string, unknown> };
|
||||
"game:ended": { roomId: string; winner: string | null; reason: string; payouts?: Record<string, number> };
|
||||
"round:settled": { roomId: string; roundPayouts: Record<string, number> };
|
||||
"round:settled": { roomId: string; roundSettlements: Record<string, RoundSettlement> };
|
||||
"player:left": { roomId: string; playerId: string };
|
||||
"room:deleted": { roomId: string };
|
||||
"room:list:changed": void;
|
||||
@@ -138,8 +139,8 @@ export class RoomManager {
|
||||
this.emitter.emit("game:updated", { roomId, spectatorView, playerViews });
|
||||
|
||||
// Emit round payouts for mid-game settlement (continuous-play games)
|
||||
if (result.roundPayouts && !gameOver) {
|
||||
this.emitter.emit("round:settled", { roomId, roundPayouts: result.roundPayouts });
|
||||
if (result.roundSettlements && !gameOver) {
|
||||
this.emitter.emit("round:settled", { roomId, roundSettlements: result.roundSettlements });
|
||||
}
|
||||
|
||||
if (gameOver) {
|
||||
@@ -147,7 +148,7 @@ export class RoomManager {
|
||||
this.emitter.emit("room:list:changed");
|
||||
}
|
||||
|
||||
return { ok: true, state: room.state, gameOver, roundPayouts: result.roundPayouts };
|
||||
return { ok: true, state: room.state, gameOver, roundSettlements: result.roundSettlements };
|
||||
}
|
||||
|
||||
leaveRoom(roomId: string, playerId: string): void {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { RoundSettlement } from "@shared/games/types";
|
||||
import { z } from "zod";
|
||||
|
||||
export interface Room {
|
||||
@@ -59,6 +60,6 @@ export type GameWsServerMessage =
|
||||
| { type: "GAME_ENDED"; roomId: string; winner: string | null; reason: string; payout?: { amount: number; refunded?: boolean } }
|
||||
| { type: "ROOM_CREATED"; roomId: string; gameSlug: string }
|
||||
| { type: "JOIN_RESULT"; roomId: string; joinedAs: "player" | "spectator"; roomStatus: "waiting" | "playing" | "finished"; players: PlayerInfo[]; spectators: PlayerInfo[]; state?: unknown; roomOptions?: { betAmount?: number; timeControl?: string } }
|
||||
| { type: "ROUND_SETTLED"; roomId: string; payouts: Record<string, { net: number }> }
|
||||
| { type: "ROUND_SETTLED"; roomId: string; settlements: Record<string, RoundSettlement> }
|
||||
| { type: "SESSION_REPLACED"; roomId: string }
|
||||
| { type: "ERROR"; message: string };
|
||||
|
||||
Reference in New Issue
Block a user