1
0
forked from sass/tipibot

More bug fixes

This commit is contained in:
Rene Arumetsa
2026-05-03 15:11:32 +03:00
parent d65173fbe9
commit 07e7f5e0b2
9 changed files with 295 additions and 90 deletions

View File

@@ -1306,12 +1306,32 @@ async def do_rob(robber_id: int, target_id: int) -> dict:
pct = random.uniform(0.10, 0.25)
stolen = max(10, min(int(target["balance"] * pct), target["balance"]))
target["balance"] -= stolen
prev_lifetime_earned = robber.get("lifetime_earned", 0)
prev_biggest_win = robber.get("biggest_win", 0)
prev_peak_balance = robber.get("peak_balance", 0)
robber["balance"] += stolen
robber["lifetime_earned"] = robber.get("lifetime_earned", 0) + stolen
robber["biggest_win"] = max(robber.get("biggest_win", 0), stolen)
robber["peak_balance"] = max(robber.get("peak_balance", 0), robber["balance"])
await _commit(robber_id, robber)
await _commit(target_id, target)
robber["lifetime_earned"] = prev_lifetime_earned + stolen
robber["biggest_win"] = max(prev_biggest_win, stolen)
robber["peak_balance"] = max(prev_peak_balance, robber["balance"])
try:
await _commit(robber_id, robber)
except DatabaseError:
return {"ok": False, "reason": "db_error"}
try:
await _commit(target_id, target)
except DatabaseError:
robber["balance"] -= stolen
robber["lifetime_earned"] = prev_lifetime_earned
robber["biggest_win"] = prev_biggest_win
robber["peak_balance"] = prev_peak_balance
try:
await _commit(robber_id, robber)
except DatabaseError as exc2:
_log.critical(
"do_rob rollback failed for robber %s after target commit failed: %s",
robber_id, exc2,
)
return {"ok": False, "reason": "db_error"}
_txn("ROB_WIN", robber=robber_id, victim=target_id, stolen=f"+{stolen}", jackpot=jackpot, robber_bal=robber["balance"], victim_bal=target["balance"])
return {"ok": True, "success": True, "stolen": stolen, "balance": robber["balance"], "jackpot": jackpot}
else:
@@ -1400,6 +1420,66 @@ async def do_game_bet(user_id: int, bet: int, outcome: str) -> dict:
return {"ok": True, "balance": user["balance"]}
# ---------------------------------------------------------------------------
# /rps PvP escrow (deposit/payout/refund)
# ---------------------------------------------------------------------------
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"]}
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"]}
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
# ---------------------------------------------------------------------------
@@ -1505,8 +1585,23 @@ async def do_give(giver_id: int, receiver_id: int, amount: int) -> dict:
receiver["balance"] += amount
giver["total_given"] = giver.get("total_given", 0) + amount
receiver["total_received"] = receiver.get("total_received", 0) + amount
await _commit(giver_id, giver)
await _commit(receiver_id, receiver)
try:
await _commit(giver_id, giver)
except DatabaseError:
return {"ok": False, "reason": "db_error"}
try:
await _commit(receiver_id, receiver)
except DatabaseError:
giver["balance"] += amount
giver["total_given"] = max(0, giver.get("total_given", 0) - amount)
try:
await _commit(giver_id, giver)
except DatabaseError as exc2:
_log.critical(
"do_give rollback failed for giver %s after receiver commit failed: %s",
giver_id, exc2,
)
return {"ok": False, "reason": "db_error"}
_txn("GIVE", from_=giver_id, to=receiver_id, amount=amount, from_bal=giver["balance"], to_bal=receiver["balance"])
return {
@@ -1760,23 +1855,41 @@ async def do_heist_check(user_id: int) -> dict:
async def do_heist_resolve(user_ids: list[int], success: bool) -> dict:
"""Apply heist outcome to all participants. On win, steals from house."""
"""Apply heist outcome to all participants. On win, steals from house.
Per-user commit failures attempt to compensate the house so the economy
stays balanced. If compensation also fails, a CRITICAL log is emitted.
"""
now = _now()
payout_each = 0
failed_users: list[int] = []
if success and HOUSE_ID is not None:
house = await get_user(HOUSE_ID)
try:
house = await get_user(HOUSE_ID)
except DatabaseError:
return {"ok": False, "reason": "db_error"}
pct = random.uniform(0.20, 0.55)
total = max(300, int(house["balance"] * pct))
payout_each = total // len(user_ids)
house["balance"] = max(0, house["balance"] - total)
await _commit(HOUSE_ID, house)
try:
await _commit(HOUSE_ID, house)
except DatabaseError:
return {"ok": False, "reason": "db_error"}
_txn("HEIST_HOUSE", change=f"-{total}", house_bal=house["balance"])
for uid in user_ids:
user = await get_user(uid)
try:
user = await get_user(uid)
except DatabaseError:
failed_users.append(uid)
if success and payout_each > 0:
await _refund_house_safe(payout_each, "heist_win_compensate", uid)
continue
user["last_heist"] = now.isoformat()
user["heists_joined"] = user.get("heists_joined", 0) + 1
fine_credited = False
if success:
user["balance"] += payout_each
user["heists_won"] = user.get("heists_won", 0) + 1
@@ -1792,7 +1905,51 @@ async def do_heist_resolve(user_ids: list[int], success: bool) -> dict:
user["lifetime_lost"] = user.get("lifetime_lost", 0) + fine
_txn("HEIST_FAIL", user=uid, fine=f"-{fine}", jailed_until=user["jailed_until"], bal=user["balance"])
if fine > 0:
await _credit_house(fine)
await _commit(uid, user)
try:
await _credit_house(fine)
fine_credited = True
except DatabaseError:
pass # user commit will still be attempted; if both fail, no economy effect
try:
await _commit(uid, user)
except DatabaseError:
failed_users.append(uid)
if success and payout_each > 0:
await _refund_house_safe(payout_each, "heist_win_compensate", uid)
elif not success and fine_credited:
await _refund_user_safe(HOUSE_ID, fine if 'fine' in locals() else 0, "heist_fail_compensate", uid)
return {"ok": True, "payout_each": payout_each, "success": success}
return {"ok": True, "payout_each": payout_each, "success": success, "failed_users": failed_users}
async def _refund_house_safe(amount: int, context: str, related_uid: int) -> None:
"""Best-effort refund of `amount` to the house. Logs critical if it fails."""
if HOUSE_ID is None or amount <= 0:
return
try:
house = await get_user(HOUSE_ID)
house["balance"] = house.get("balance", 0) + amount
await _commit(HOUSE_ID, house)
except DatabaseError as exc:
_log.critical(
"House compensation failed (%s, related uid %s, amount %s): %s",
context, related_uid, amount, exc,
)
async def _refund_user_safe(_unused_house_id, amount: int, context: str, uid: int) -> None:
"""Best-effort debit of `amount` from the house (compensates a failed user fine).
Reads house, subtracts amount, commits. Logs critical if it fails.
"""
if HOUSE_ID is None or amount <= 0:
return
try:
house = await get_user(HOUSE_ID)
house["balance"] = max(0, house.get("balance", 0) - amount)
await _commit(HOUSE_ID, house)
except DatabaseError as exc:
_log.critical(
"House debit compensation failed (%s, related uid %s, amount %s): %s",
context, uid, amount, exc,
)