feat(economy): add vanity shop and harden economy money-safety
Vanity shop (/vanity): cosmetic badges/titles as a pure whale coin sink - purchases burn coins (not credited to the house) and equip a badge shown on /profile. New vanity_owned/vanity_active fields, schema sync, and tests. Money-safety and robustness fixes from a codebase review: - _parse_amount now rejects negative amounts. Every bet/give/request flows through it, so a negative value can no longer mint coins on a loss/transfer path that trusts the caller's sign (all call sites already guarded <= 0; this closes the source). - do_blackjack_payout no longer raises on a DB failure. The stake was already deducted in do_blackjack_bet, so it now logs critical with the owed amount (for admin reconciliation) and returns db_error; all payout call sites render a clear "payout failed" notice instead of crashing the interaction. - Instant "kohv" consumable now cancels the pending reminder DMs for the cooldowns it wipes (via new INSTANT_RESET_COMMANDS), so no stale/duplicate reminders fire. - Renamed the misleadingly-named _refund_user_safe -> _debit_house_safe (it debits the house) and dropped its ignored first arg. - Added __all__ to vanity.py and consumables.py so `import *` no longer leaks incidental imports into the economy namespace. - Documented Kõrvaklapid's +25 coin daily bonus in README and DEV_NOTES. Tests: blackjack payout DB-failure safety and INSTANT_RESET_COMMANDS lockstep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VbAVsrZuYesea99mPMmPT
This commit is contained in:
@@ -6,7 +6,7 @@ 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 .store import _commit, _is_jailed, _locked_by, _log, _txn, get_user
|
||||
from .house import _credit_house
|
||||
|
||||
|
||||
@@ -254,8 +254,15 @@ async def do_blackjack_bet(user_id: int, bet: int) -> dict:
|
||||
|
||||
@_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)
|
||||
"""Credit the net payout. House receives the difference when payout < total_invested.
|
||||
|
||||
The stake was already deducted in do_blackjack_bet, so a DB failure here must
|
||||
not raise through the interaction handler and swallow the player's winnings:
|
||||
report db_error like every other mutation so the caller can surface it."""
|
||||
try:
|
||||
user = await get_user(user_id)
|
||||
except DatabaseError:
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
user["balance"] += payout
|
||||
user["balance"] = max(0, user["balance"])
|
||||
user["total_wagered"] = user.get("total_wagered", 0) + total_invested
|
||||
@@ -267,7 +274,17 @@ async def do_blackjack_payout(user_id: int, payout: int, total_invested: int = 0
|
||||
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)
|
||||
try:
|
||||
await _commit(user_id, user)
|
||||
except DatabaseError:
|
||||
# The stake is already gone (deducted in do_blackjack_bet) and this credit
|
||||
# did not persist. Nothing to roll back locally - log the owed amount so an
|
||||
# admin can reconcile with /admincoins.
|
||||
_log.critical(
|
||||
"blackjack payout commit failed for %s: owed payout=%s (invested=%s)",
|
||||
user_id, payout, total_invested,
|
||||
)
|
||||
return {"ok": False, "reason": "db_error"}
|
||||
house_gain = total_invested - payout
|
||||
if house_gain > 0:
|
||||
await _credit_house(house_gain)
|
||||
|
||||
Reference in New Issue
Block a user