forked from sass/tipibot
109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import datetime
|
|
from collections.abc import Awaitable, Callable
|
|
|
|
import discord
|
|
from discord import app_commands
|
|
|
|
from core import economy
|
|
import strings as S
|
|
|
|
|
|
_BAR_SLOTS = 8
|
|
|
|
|
|
def _bar(progress: int, goal: int) -> str:
|
|
"""Return an 8-slot ▰/▱ progress bar for a quest."""
|
|
filled = 0 if goal <= 0 else min(_BAR_SLOTS, round(_BAR_SLOTS * progress / goal))
|
|
return "▰" * filled + "▱" * (_BAR_SLOTS - filled)
|
|
|
|
|
|
def _has_claimable(view_data: dict) -> bool:
|
|
return any(
|
|
q["done"] and not q["claimed"]
|
|
for q in view_data["daily"] + view_data["weekly"]
|
|
)
|
|
|
|
|
|
def register_economy_quests_commands(
|
|
tree: app_commands.CommandTree,
|
|
bot: discord.Client,
|
|
coin: Callable[[int], str],
|
|
award_exp: Callable[[discord.Interaction, int], Awaitable[None]],
|
|
) -> None:
|
|
def _render(view_data: dict) -> discord.Embed:
|
|
embed = discord.Embed(title=S.TITLE["quests"], color=0xF4C430)
|
|
sections = (
|
|
(S.QUEST_UI["daily_header"], view_data["daily"]),
|
|
(S.QUEST_UI["weekly_header"], view_data["weekly"]),
|
|
)
|
|
for header, quests in sections:
|
|
if quests:
|
|
blocks = []
|
|
for q in quests:
|
|
desc = S.QUEST_DESCRIPTIONS.get(q["id"], q["id"])
|
|
if q["claimed"]:
|
|
status = S.QUEST_UI["completed"]
|
|
elif q["done"]:
|
|
status = S.QUEST_UI["ready"]
|
|
else:
|
|
status = S.QUEST_UI["progress"].format(progress=q["progress"], max=q["goal"])
|
|
reward = S.QUEST_UI["reward"].format(coins=f"{q['coins']:,}", exp=q["exp"])
|
|
blocks.append(
|
|
f"{_bar(q['progress'], q['goal'])} **{desc}**\n{status} · {reward}"
|
|
)
|
|
value = "\n\n".join(blocks)
|
|
else:
|
|
value = S.QUEST_UI["empty"]
|
|
embed.add_field(name=header, value=value, inline=False)
|
|
return embed
|
|
|
|
class QuestView(discord.ui.View):
|
|
def __init__(self, invoker_id: int, view_data: dict):
|
|
super().__init__(timeout=180)
|
|
self.invoker_id = invoker_id
|
|
btn = discord.ui.Button(
|
|
label=S.QUEST_UI["claim_btn"],
|
|
style=discord.ButtonStyle.success,
|
|
disabled=not _has_claimable(view_data),
|
|
)
|
|
btn.callback = self._claim
|
|
self.add_item(btn)
|
|
|
|
async def _claim(self, interaction: discord.Interaction):
|
|
if interaction.user.id != self.invoker_id:
|
|
await interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
|
return
|
|
try:
|
|
res = await economy.claim_quests(self.invoker_id)
|
|
except economy.DatabaseError:
|
|
await interaction.response.send_message(S.QUEST_UI["error"], ephemeral=True)
|
|
return
|
|
if not res["ok"]:
|
|
await interaction.response.send_message(S.QUEST_UI["nothing"], ephemeral=True)
|
|
return
|
|
new_data = await economy.get_quests(self.invoker_id)
|
|
await interaction.response.edit_message(
|
|
embed=_render(new_data), view=QuestView(self.invoker_id, new_data)
|
|
)
|
|
await interaction.followup.send(
|
|
S.QUEST_UI["claimed_msg"].format(
|
|
count=res["claimed"], coins=coin(res["coins"]), exp=res["exp"]
|
|
),
|
|
ephemeral=True,
|
|
)
|
|
if res["exp"]:
|
|
asyncio.create_task(award_exp(interaction, res["exp"]))
|
|
|
|
@tree.command(name="quests", description=S.CMD["quests"])
|
|
async def cmd_quests(interaction: discord.Interaction):
|
|
await interaction.response.defer()
|
|
try:
|
|
data = await economy.get_quests(interaction.user.id)
|
|
except economy.DatabaseError:
|
|
await interaction.followup.send(S.QUEST_UI["error"], ephemeral=True)
|
|
return
|
|
await interaction.followup.send(embed=_render(data), view=QuestView(interaction.user.id, data))
|