forked from sass/tipibot
feat(economy): add /consumables shop of repeatable, expiring boosts
The economy had many coin faucets but almost no sinks: the /shop is
one-time ownership, and gambling/rob fines route to the house (which
players drain back via jackpots and heists), so they recirculate rather
than destroy coins. Result: steady inflation.
Add a consumables shop as a true recurring sink - buying destroys the
coins and grants a temporary boost, so there's always something to spend
on after gear is maxed:
- Energiajook XL (500) - 1h of 2x earnings on /work, /beg, /crime
- XP jook (500) - 1h of 2x EXP
- Kohv (300) - instantly clears all cooldowns
Timed buffs live in a new active_buffs field ({kind: expiry_iso}), pruned
on read; rebuying extends the timer. Effects hook where they belong:
earn_mult in income.do_work/do_beg/do_crime, exp_buff_mult in
levels.award_exp; kohv is self-contained. New /consumables command browses
the menu (with active buffs) or buys a boost. Covered by 9 tests.
Note: active_buffs is a new PocketBase field - run
scripts/sync_pb_schema.py before deploying or buffs won't persist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,81 @@ def register_economy_support_commands(
|
||||
await interaction.response.send_message(embed=embed, view=view)
|
||||
view.message = await interaction.original_response()
|
||||
|
||||
# -- /consumables -------------------------------------------------------
|
||||
def _consumables_embed(user_data: dict) -> discord.Embed:
|
||||
embed = discord.Embed(
|
||||
title=S.CONSUMABLES_UI["title"],
|
||||
description=S.CONSUMABLES_UI["desc"].format(bal=coin(user_data["balance"])),
|
||||
color=0xF4C430,
|
||||
)
|
||||
active = economy.active_buffs(user_data)
|
||||
if active:
|
||||
lines = [
|
||||
S.CONSUMABLES_UI["buff_line"].format(
|
||||
name=S.CONSUMABLES_UI[f"kind_{kind}"],
|
||||
time=economy.format_td(economy.buff_remaining(user_data, kind)),
|
||||
)
|
||||
for kind in active
|
||||
]
|
||||
buff_value = "\n".join(lines)
|
||||
else:
|
||||
buff_value = S.CONSUMABLES_UI["active_none"]
|
||||
embed.add_field(name=S.CONSUMABLES_UI["active_header"], value=buff_value, inline=False)
|
||||
for cons in economy.CONSUMABLES.values():
|
||||
embed.add_field(
|
||||
name=f"{cons['emoji']} {cons['name']} · {cons['cost']} {economy.COIN}",
|
||||
value=cons["description"],
|
||||
inline=False,
|
||||
)
|
||||
return embed
|
||||
|
||||
@tree.command(name="consumables", description=S.CMD["consumables"])
|
||||
@app_commands.describe(ese=S.OPT["consumable_ese"])
|
||||
@app_commands.choices(
|
||||
ese=[
|
||||
app_commands.Choice(name=f"{c['name']} ({c['cost']} TipiCOINi)", value=cid)
|
||||
for cid, c in economy.CONSUMABLES.items()
|
||||
]
|
||||
)
|
||||
async def cmd_consumables(
|
||||
interaction: discord.Interaction,
|
||||
ese: app_commands.Choice[str] | None = None,
|
||||
):
|
||||
if ese is None:
|
||||
data = await economy.get_user(interaction.user.id)
|
||||
await interaction.response.send_message(
|
||||
embed=_consumables_embed(data), ephemeral=True
|
||||
)
|
||||
return
|
||||
|
||||
res = await economy.do_buy_consumable(interaction.user.id, ese.value)
|
||||
if not res["ok"]:
|
||||
if res["reason"] == "banned":
|
||||
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
||||
elif res["reason"] == "insufficient":
|
||||
await interaction.response.send_message(
|
||||
S.ERR["broke_need"].format(need=coin(res["need"])), ephemeral=True
|
||||
)
|
||||
else:
|
||||
await interaction.response.send_message(S.ERR["item_not_found"], ephemeral=True)
|
||||
return
|
||||
|
||||
cons = res["consumable"]
|
||||
if res["instant"]:
|
||||
desc = S.CONSUMABLES_UI["bought_instant"].format(balance=coin(res["balance"]))
|
||||
else:
|
||||
key = "bought_extended" if res["extended"] else "bought_buff"
|
||||
desc = S.CONSUMABLES_UI[key].format(
|
||||
time=economy.format_td(res["remaining"]),
|
||||
balance=coin(res["balance"]),
|
||||
)
|
||||
embed = discord.Embed(
|
||||
title=S.CONSUMABLES_UI["bought_title"].format(emoji=cons["emoji"], name=cons["name"]),
|
||||
description=desc,
|
||||
color=0x57F287,
|
||||
)
|
||||
await interaction.response.send_message(embed=embed)
|
||||
|
||||
class RemindersSelect(discord.ui.Select):
|
||||
def __init__(self, user_id: int, current: list[str]):
|
||||
self.user_id = user_id
|
||||
|
||||
Reference in New Issue
Block a user