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:
Rene Arumetsa
2026-09-04 02:09:25 +03:00
parent a2e1601d0e
commit 037628d24f
21 changed files with 393 additions and 31 deletions

View File

@@ -58,6 +58,7 @@ from .economy import (
ITEM_DESCRIPTIONS,
CONSUMABLES_UI,
CONSUMABLE_DESCRIPTIONS,
VANITY_UI,
JAILED_UI,
SHOP_BTN,
DAILY_UI,
@@ -150,6 +151,7 @@ __all__ = [
'ITEM_DESCRIPTIONS',
'CONSUMABLES_UI',
'CONSUMABLE_DESCRIPTIONS',
'VANITY_UI',
'JAILED_UI',
'SHOP_BTN',
'DAILY_UI',

View File

@@ -74,6 +74,7 @@ CMD: dict[str, str] = {
"patchnotes": "Vaata TipiBOTi viimaseid muudatusi ja uuendusi",
"quests": "Vaata oma päeva- ja nädalaülesandeid ning nõua auhinnad",
"consumables": "Sirvi ja osta turgutusi (korduvostetavad boostid)",
"vanity": "Staatusepood - osta ja kanna kosmeetilisi tiitleid",
}
# ---------------------------------------------------------------------------
@@ -92,6 +93,7 @@ OPT: dict[str, str] = {
"give_summa": "Kui palju annad? ('all' = kogu saldo)",
"buy_ese": "Eseme nimi (vaata /shop)",
"consumable_ese": "Turgutus, mida osta (tühjaks jättes näeb menüüd)",
"vanity_ese": "Tiitel, mida osta või kanda (tühjaks jättes näeb poodi)",
"rps_panus": "Valikuline TipiCOINide panus ('all' = kogu saldo)",
"rps_vastane": "Väljakutse teisele mängijale (PvP)",
"slots_panus": "Panus TipiCOINides ('all' = kogu saldo)",

View File

@@ -175,6 +175,7 @@ ERR: dict[str, str] = {
"guild_only": "Seda käsku saab kasutada ainult serveris.",
"sheet_error": "❌ Tabeli laadimine ebaõnnestus: ```{error}```",
"gamble_cooldown": "🎰 Oled just mänginud! Saad uuesti mängida {ts}.",
"payout_failed": "⚠️ Tehniline viga võidu väljamaksmisel - see on logitud ja admin taastab su TipiCOINid. Vabandame!",
}
# ---------------------------------------------------------------------------

View File

@@ -17,6 +17,7 @@ __all__ = [
'ITEM_DESCRIPTIONS',
'CONSUMABLES_UI',
'CONSUMABLE_DESCRIPTIONS',
'VANITY_UI',
'JAILED_UI',
'SHOP_BTN',
'DAILY_UI',
@@ -240,6 +241,22 @@ CONSUMABLES_UI: dict[str, str] = {
"bought_instant": "☕ Kõik ooteajad nullitud!\nUus saldo: {balance}",
}
# ---------------------------------------------------------------------------
# Vanity shop (cosmetic badges/titles - a pure status sink, no gameplay effect)
# ---------------------------------------------------------------------------
VANITY_UI: dict[str, str] = {
"title": "👑 Staatusepood",
"desc": "Puhtalt uhkuse pärast - märgid ja tiitlid ilma igasuguse mänguefektita. Kantud märk paistab su `/profile`-l.\nSaldo: {bal}",
"line_owned": "✅ Olemas",
"line_active": "⭐ Kantud",
"entry_title": "{title}",
"footer": "Osta või kanna: /vanity <märk> · „Eemalda märk“ võtab tiitli maha",
"none_choice": "❌ Eemalda märk",
"bought": "{emoji} Ostsid tiitli **{title}** ja panid selle kohe kandma!\nUus saldo: {balance}",
"equipped": "{emoji} Kannad nüüd tiitlit **{title}**.",
"unequipped": "Märk eemaldatud - su profiil on jälle tavaline.",
}
JAILED_UI: dict[str, str] = {
"title": "🔒 Praegu vanglas",
"empty": "Kõik on vabad! Vanglas pole kedagi.",