diff options
| author | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 17:48:26 -0400 |
| commit | 8fe9e3c3fef0049df82281a62afe35dda516dfe2 (patch) | |
| tree | dd269f2c0710f334e19f00a191260ebc46284a90 /internal/casino | |
| parent | 194164a77eb4f2479b94c19e6f0e1278ab608781 (diff) | |
| download | thehouseoficarus-8fe9e3c3fef0049df82281a62afe35dda516dfe2.tar.gz | |
fix: close combat ghost, admin traversal, login throttle, journal double-pay, and quality/economy gaps
Diffstat (limited to 'internal/casino')
| -rw-r--r-- | internal/casino/baccarat.go | 48 | ||||
| -rw-r--r-- | internal/casino/baccarat_test.go | 32 | ||||
| -rw-r--r-- | internal/casino/manager.go | 14 |
3 files changed, 56 insertions, 38 deletions
diff --git a/internal/casino/baccarat.go b/internal/casino/baccarat.go index c9a50a1..164d0ff 100644 --- a/internal/casino/baccarat.go +++ b/internal/casino/baccarat.go @@ -200,24 +200,28 @@ func (t *baccaratTable) deal() []Event { if !p.HasBet { continue } - result, payout := settleBaccaratBet(p.lastSpot, p.Wager.Total, playerTotal, bankerTotal, t.Rules) - payoutFailed := false - if payout > 0 && p.Wallet != nil { + result, payout, settleErr := settleBaccaratBet(p.lastSpot, p.Wager.Total, playerTotal, bankerTotal, t.Rules) + // A payout-overflow error means the win could not be computed; treat + // it like an unpersisted payout so the win is never announced as paid + // (blackjack handles the same case identically). + payoutFailed := settleErr != nil + if !payoutFailed && payout > 0 && p.Wallet != nil { if err := p.Wallet.PayStakeCover(payout, []string{p.stakeID}); err != nil { // The payout could not be made durable; recovery will // re-apply the owed amount from the open journal record. // Do not announce the win as paid and leave the stakes // open for recovery to settle. payoutFailed = true - publicResult, _ := baccaratResultMessages(p.Name, result, payout) - settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage()) - if result == "win" { - settlement.Color = "casino_big_win" - } - events = append(events, settlement) } } - if !payoutFailed { + if payoutFailed { + publicResult, _ := baccaratResultMessages(p.Name, result, payout) + settlement := t.playerAction(p.Name, publicResult, payoutFailedMessage()) + if result == "win" { + settlement.Color = "casino_big_win" + } + events = append(events, settlement) + } else { publicResult, privateResult := baccaratResultMessages(p.Name, result, payout) settlement := t.playerAction(p.Name, publicResult, privateResult) if result == "win" { @@ -360,15 +364,18 @@ func bankerDraws(bankerTotal, playerThird int) bool { // winnings) for one wager. Player and banker bets push on a tie. Winnings // round down to whole credits, so the banker commission effectively rounds // up: never bet less than one commission unit. -func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules BaccaratConfig) (string, int) { +func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules BaccaratConfig) (string, int, error) { switch { case playerTotal > bankerTotal: if spot == baccaratSpotPlayer { - return "win", bet * 2 + return "win", bet * 2, nil } case bankerTotal > playerTotal: if spot == baccaratSpotBanker { - bankerWin, _ := MulPayout(bet, rules.BankerPayout) + bankerWin, err := MulPayout(bet, rules.BankerPayout) + if err != nil { + return "win", bet + bankerWin, err + } // The banker commission rounds UP, not down: when the wager is // too small for a whole credit of commission, a winning banker // bet returns only the stake. MulPayout's positive-stake floor @@ -377,18 +384,21 @@ func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules // push), which the "never bet less than one commission unit" // rule exists to prevent. if float64(bet)*rules.BankerPayout < 1 { - return "push", bet + return "push", bet, nil } - return "win", bet + bankerWin + return "win", bet + bankerWin, nil } default: if spot == baccaratSpotTie { - tieWin, _ := MulPayout(bet, rules.TiePayout) - return "win", bet + tieWin + tieWin, err := MulPayout(bet, rules.TiePayout) + if err != nil { + return "win", bet + tieWin, err + } + return "win", bet + tieWin, nil } - return "push", bet + return "push", bet, nil } - return "loss", 0 + return "loss", 0, nil } func baccaratOutcomeMessage(playerTotal, bankerTotal int) string { diff --git a/internal/casino/baccarat_test.go b/internal/casino/baccarat_test.go index 39941eb..41f5acf 100644 --- a/internal/casino/baccarat_test.go +++ b/internal/casino/baccarat_test.go @@ -99,29 +99,29 @@ func TestBaccaratTableauBankerThirdCardRules(t *testing.T) { func TestBaccaratSettlement(t *testing.T) { rules := (BaccaratConfig{}).Normalize() - if result, payout := settleBaccaratBet(baccaratSpotPlayer, 100, 6, 4, rules); result != "win" || payout != 200 { - t.Fatalf("player win: result=%s payout=%d, want win/200", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotPlayer, 100, 6, 4, rules); err != nil || result != "win" || payout != 200 { + t.Fatalf("player win: result=%s payout=%d err=%v, want win/200", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 6, 4, rules); result != "loss" || payout != 0 { - t.Fatalf("banker bet on player win: result=%s payout=%d, want loss/0", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 6, 4, rules); err != nil || result != "loss" || payout != 0 { + t.Fatalf("banker bet on player win: result=%s payout=%d err=%v, want loss/0", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 4, 6, rules); result != "win" || payout != 195 { - t.Fatalf("banker win: result=%s payout=%d, want win/195 (5%% commission)", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 4, 6, rules); err != nil || result != "win" || payout != 195 { + t.Fatalf("banker win: result=%s payout=%d err=%v, want win/195 (5%% commission)", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotBanker, 1, 4, 6, rules); result != "push" || payout != 1 { - t.Fatalf("banker win on bet 1: result=%s payout=%d, want push/1 (a sub-credit commission rounds up, returning only the stake)", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 1, 4, 6, rules); err != nil || result != "push" || payout != 1 { + t.Fatalf("banker win on bet 1: result=%s payout=%d err=%v, want push/1 (a sub-credit commission rounds up, returning only the stake)", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotTie, 100, 7, 7, rules); result != "win" || payout != 900 { - t.Fatalf("tie win: result=%s payout=%d, want win/900 (8:1)", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotTie, 100, 7, 7, rules); err != nil || result != "win" || payout != 900 { + t.Fatalf("tie win: result=%s payout=%d err=%v, want win/900 (8:1)", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotPlayer, 100, 7, 7, rules); result != "push" || payout != 100 { - t.Fatalf("player bet on tie: result=%s payout=%d, want push/100", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotPlayer, 100, 7, 7, rules); err != nil || result != "push" || payout != 100 { + t.Fatalf("player bet on tie: result=%s payout=%d err=%v, want push/100", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotBanker, 100, 7, 7, rules); result != "push" || payout != 100 { - t.Fatalf("banker bet on tie: result=%s payout=%d, want push/100", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotBanker, 100, 7, 7, rules); err != nil || result != "push" || payout != 100 { + t.Fatalf("banker bet on tie: result=%s payout=%d err=%v, want push/100", result, payout, err) } - if result, payout := settleBaccaratBet(baccaratSpotTie, 100, 6, 4, rules); result != "loss" || payout != 0 { - t.Fatalf("tie bet on player win: result=%s payout=%d, want loss/0", result, payout) + if result, payout, err := settleBaccaratBet(baccaratSpotTie, 100, 6, 4, rules); err != nil || result != "loss" || payout != 0 { + t.Fatalf("tie bet on player win: result=%s payout=%d err=%v, want loss/0", result, payout, err) } } diff --git a/internal/casino/manager.go b/internal/casino/manager.go index 8b87147..b1281b2 100644 --- a/internal/casino/manager.go +++ b/internal/casino/manager.go @@ -409,20 +409,28 @@ func (m *Manager) RekeyRooms(idMap map[int]int) { // refunded through each game's refundAll before the entries drop. func (m *Manager) EvictRoom(roomID int) []Event { prefix := fmt.Sprintf("%d:", roomID) - var events []Event + // Snapshot the games under the manager lock, then refund OUTSIDE it: + // refundAll appends to the money journal and saves character files, and + // holding m.mu across that I/O blocks every casino command and Tick. + type evictable interface{ refundAll() []Event } + var evicted []evictable m.mu.Lock() for key, table := range m.tables { if strings.HasPrefix(key, prefix) { - events = append(events, table.refundAll()...) + evicted = append(evicted, table) delete(m.tables, key) } } for key, machine := range m.machines { if strings.HasPrefix(key, prefix) { - events = append(events, machine.refundAll()...) + evicted = append(evicted, machine) delete(m.machines, key) } } m.mu.Unlock() + var events []Event + for _, game := range evicted { + events = append(events, game.refundAll()...) + } return events } |
