Refator eco code. Split into modules
This commit is contained in:
275
core/economy/gambling.py
Normal file
275
core/economy/gambling.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""Casino games: roulette, slots, RPS bets and escrow, blackjack."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
|
||||
from ..pb_client import DatabaseError
|
||||
from ..emoji import EMOJI as E
|
||||
from .store import _commit, _is_jailed, _locked_by, _txn, get_user
|
||||
from .house import _credit_house
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /roulette
|
||||
# ---------------------------------------------------------------------------
|
||||
@_locked_by(0)
|
||||
async def do_roulette(user_id: int, bet: int, colour: str) -> dict:
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
if user.get("eco_banned"):
|
||||
return {"ok": False, "reason": "banned"}
|
||||
if jail := _is_jailed(user):
|
||||
return {"ok": False, "reason": "jailed", "remaining": jail}
|
||||
|
||||
if user["balance"] < bet:
|
||||
return {"ok": False, "reason": "insufficient"}
|
||||
|
||||
# Wheel: 18 red, 18 black, 1 green (37 slots - real roulette proportions)
|
||||
result = random.choices(["punane", "must", "roheline"], weights=[18, 18, 1])[0]
|
||||
won = result == colour
|
||||
mult = 14 if colour == "roheline" else 1
|
||||
change = bet * mult if won else -bet
|
||||
user["balance"] = max(0, user["balance"] + change)
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
||||
if won:
|
||||
user["lifetime_earned"] = user.get("lifetime_earned", 0) + abs(change)
|
||||
user["biggest_win"] = max(user.get("biggest_win", 0), abs(change))
|
||||
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
||||
else:
|
||||
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
||||
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
||||
await _commit(user_id, user)
|
||||
if not won:
|
||||
await _credit_house(bet)
|
||||
_txn("ROULETTE_" + ("WIN" if won else "LOSE"), user=user_id, bet=bet, colour=colour, result=result, mult=mult, bal=user["balance"])
|
||||
|
||||
return {
|
||||
"ok": True, "won": won,
|
||||
"result": result, "change": abs(change), "mult": mult,
|
||||
"balance": user["balance"],
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /rps (bet resolution)
|
||||
# ---------------------------------------------------------------------------
|
||||
@_locked_by(0)
|
||||
async def do_game_bet(user_id: int, bet: int, outcome: str) -> dict:
|
||||
"""Settle a simple win/tie/lose bet. outcome: 'win' | 'tie' | 'lose'."""
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
if user.get("eco_banned"):
|
||||
return {"ok": False, "reason": "banned"}
|
||||
if jail := _is_jailed(user):
|
||||
return {"ok": False, "reason": "jailed", "remaining": jail}
|
||||
if user["balance"] < bet:
|
||||
return {"ok": False, "reason": "insufficient"}
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
||||
if outcome == "win":
|
||||
user["balance"] += bet
|
||||
user["lifetime_earned"] = user.get("lifetime_earned", 0) + bet
|
||||
user["biggest_win"] = max(user.get("biggest_win", 0), bet)
|
||||
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
||||
elif outcome == "lose":
|
||||
user["balance"] = max(0, user["balance"] - bet)
|
||||
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
||||
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
||||
# tie: no change
|
||||
await _commit(user_id, user)
|
||||
if outcome == "lose" and bet > 0:
|
||||
await _credit_house(bet)
|
||||
_txn("RPS_" + outcome.upper(), user=user_id, bet=bet, bal=user["balance"])
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /rps PvP escrow (deposit/payout/refund)
|
||||
# ---------------------------------------------------------------------------
|
||||
@_locked_by(0)
|
||||
async def do_rps_pvp_deposit(user_id: int, bet: int) -> dict:
|
||||
"""Hold `bet` coins from a player as escrow for a PvP RPS duel."""
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
if user.get("eco_banned"):
|
||||
return {"ok": False, "reason": "banned"}
|
||||
if jail := _is_jailed(user):
|
||||
return {"ok": False, "reason": "jailed", "remaining": jail}
|
||||
if user["balance"] < bet:
|
||||
return {"ok": False, "reason": "insufficient"}
|
||||
user["balance"] -= bet
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
||||
try:
|
||||
await _commit(user_id, user)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
_txn("RPS_PVP_DEPOSIT", user=user_id, bet=bet, bal=user["balance"])
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
|
||||
|
||||
@_locked_by(0)
|
||||
async def do_rps_pvp_payout(winner_id: int, bet: int) -> dict:
|
||||
"""Credit the duel winner with 2*bet (their stake back + opponent's)."""
|
||||
try:
|
||||
user = await get_user(winner_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
payout = bet * 2
|
||||
user["balance"] = user.get("balance", 0) + payout
|
||||
user["lifetime_earned"] = user.get("lifetime_earned", 0) + bet
|
||||
user["biggest_win"] = max(user.get("biggest_win", 0), bet)
|
||||
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
||||
try:
|
||||
await _commit(winner_id, user)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
_txn("RPS_PVP_PAYOUT", user=winner_id, payout=f"+{payout}", bal=user["balance"])
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
|
||||
|
||||
@_locked_by(0)
|
||||
async def do_rps_pvp_refund(user_id: int, bet: int) -> dict:
|
||||
"""Refund a previously escrowed bet (tie / timeout / cancel)."""
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
user["balance"] = user.get("balance", 0) + bet
|
||||
user["total_wagered"] = max(0, user.get("total_wagered", 0) - bet)
|
||||
try:
|
||||
await _commit(user_id, user)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
_txn("RPS_PVP_REFUND", user=user_id, bet=bet, bal=user["balance"])
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /slots
|
||||
# ---------------------------------------------------------------------------
|
||||
_SLOTS_SYMBOLS: list[tuple[str, int]] = [
|
||||
(E["TipiHEART"], 27),
|
||||
(E["TipiFIRE"], 22),
|
||||
(E["TipiTROLL"], 18),
|
||||
(E["TipICRY"], 15),
|
||||
(E["TipiSKULL"], 10),
|
||||
(E["TipiKARIKAS"], 8),
|
||||
]
|
||||
_SLOTS_JACKPOT = E["TipiKARIKAS"]
|
||||
_SLOTS_TRIPLE_MULT: dict[str, int] = {
|
||||
E["TipiHEART"]: 4,
|
||||
E["TipiFIRE"]: 5,
|
||||
E["TipiTROLL"]: 7,
|
||||
E["TipICRY"]: 10,
|
||||
E["TipiSKULL"]: 15,
|
||||
E["TipiKARIKAS"]: 25, # jackpot
|
||||
}
|
||||
|
||||
|
||||
def _spin() -> str:
|
||||
symbols, weights = zip(*_SLOTS_SYMBOLS)
|
||||
return random.choices(list(symbols), weights=list(weights), k=1)[0]
|
||||
|
||||
|
||||
@_locked_by(0)
|
||||
async def do_slots(user_id: int, bet: int) -> dict:
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
if user.get("eco_banned"):
|
||||
return {"ok": False, "reason": "banned"}
|
||||
if jail := _is_jailed(user):
|
||||
return {"ok": False, "reason": "jailed", "remaining": jail}
|
||||
if user["balance"] < bet:
|
||||
return {"ok": False, "reason": "insufficient"}
|
||||
|
||||
reels = [_spin(), _spin(), _spin()]
|
||||
a, b, c = reels
|
||||
|
||||
has_360 = "monitor_360" in user["items"]
|
||||
if a == b == c:
|
||||
tier = "jackpot" if a == _SLOTS_JACKPOT else "triple"
|
||||
base_mult = _SLOTS_TRIPLE_MULT.get(a, 4)
|
||||
mult = int(base_mult * 1.5) if has_360 else base_mult
|
||||
change = bet * (mult - 1)
|
||||
elif a == b or b == c or a == c:
|
||||
tier = "pair"
|
||||
change = bet // 2
|
||||
else:
|
||||
tier = "miss"
|
||||
change = -bet
|
||||
|
||||
user["balance"] = max(0, user["balance"] + change)
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + bet
|
||||
if tier in ("jackpot", "triple", "pair"):
|
||||
user["lifetime_earned"] = user.get("lifetime_earned", 0) + change
|
||||
user["biggest_win"] = max(user.get("biggest_win", 0), change)
|
||||
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
||||
if tier == "jackpot":
|
||||
user["slots_jackpots"] = user.get("slots_jackpots", 0) + 1
|
||||
else:
|
||||
user["lifetime_lost"] = user.get("lifetime_lost", 0) + bet
|
||||
user["biggest_loss"] = max(user.get("biggest_loss", 0), bet)
|
||||
await _commit(user_id, user)
|
||||
if tier == "miss":
|
||||
await _credit_house(bet)
|
||||
_txn("SLOTS_" + tier.upper(), user=user_id, bet=bet, change=change, bal=user["balance"])
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"reels": reels,
|
||||
"tier": tier,
|
||||
"change": change,
|
||||
"balance": user["balance"],
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# /blackjack
|
||||
# ---------------------------------------------------------------------------
|
||||
@_locked_by(0)
|
||||
async def do_blackjack_bet(user_id: int, bet: int) -> dict:
|
||||
"""Deduct the initial blackjack bet. Returns ok/fail."""
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
if user.get("eco_banned"):
|
||||
return {"ok": False, "reason": "banned"}
|
||||
if jail := _is_jailed(user):
|
||||
return {"ok": False, "reason": "jailed", "remaining": jail}
|
||||
if user["balance"] < bet:
|
||||
return {"ok": False, "reason": "insufficient", "balance": user["balance"]}
|
||||
user["balance"] -= bet
|
||||
await _commit(user_id, user)
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
|
||||
|
||||
@_locked_by(0)
|
||||
async def do_blackjack_payout(user_id: int, payout: int, total_invested: int = 0) -> dict:
|
||||
"""Credit the net payout. House receives the difference when payout < total_invested."""
|
||||
user = await get_user(user_id)
|
||||
user["balance"] += payout
|
||||
user["balance"] = max(0, user["balance"])
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + total_invested
|
||||
net = payout - total_invested
|
||||
if net > 0:
|
||||
user["lifetime_earned"] = user.get("lifetime_earned", 0) + net
|
||||
user["biggest_win"] = max(user.get("biggest_win", 0), net)
|
||||
user["peak_balance"] = max(user.get("peak_balance", 0), user["balance"])
|
||||
elif net < 0:
|
||||
user["lifetime_lost"] = user.get("lifetime_lost", 0) + abs(net)
|
||||
user["biggest_loss"] = max(user.get("biggest_loss", 0), abs(net))
|
||||
await _commit(user_id, user)
|
||||
house_gain = total_invested - payout
|
||||
if house_gain > 0:
|
||||
await _credit_house(house_gain)
|
||||
_txn("BLACKJACK", user=user_id, payout=f"{payout:+}", net=f"{net:+}", bal=user["balance"])
|
||||
return {"ok": True, "balance": user["balance"]}
|
||||
Reference in New Issue
Block a user