381 lines
15 KiB
Python
381 lines
15 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import random
|
|
from collections.abc import Awaitable, Callable, MutableSet
|
|
|
|
import discord
|
|
from discord import app_commands
|
|
|
|
import economy
|
|
import strings as S
|
|
|
|
|
|
def register_economy_fish_commands(
|
|
tree: app_commands.CommandTree,
|
|
coin: Callable[[int], str],
|
|
cd_ts: Callable,
|
|
check_cmd_rate: Callable[[discord.Interaction], Awaitable[bool]],
|
|
maybe_remind: Callable[[int, str], Awaitable[None]],
|
|
award_exp: Callable[[discord.Interaction, int], Awaitable[None]],
|
|
active_games: MutableSet[int],
|
|
) -> None:
|
|
class FishCatchView(discord.ui.View):
|
|
"""Shown after a successful pull - lets user sell or keep the fish."""
|
|
|
|
def __init__(self, user_id: int, res: dict, fish_id: str, weight: int):
|
|
super().__init__(timeout=60)
|
|
self.user_id = user_id
|
|
self._res = res
|
|
self._fish_id = fish_id
|
|
self._weight = weight
|
|
self._done = False
|
|
|
|
def _catch_embed(self, color: int = 0x57F287) -> discord.Embed:
|
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
|
fish_name = S.FISH_NAMES[self._fish_id]
|
|
desc = S.FISH_UI["catch_desc"].format(
|
|
name=fish_name,
|
|
weight=self._weight,
|
|
exp=self._res["exp"],
|
|
value=coin(self._res["value"]),
|
|
)
|
|
if self._res.get("is_new"):
|
|
desc += S.FISH_UI["new_fish"]
|
|
return discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=color)
|
|
|
|
@discord.ui.button(label="", style=discord.ButtonStyle.success)
|
|
async def sell_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
if interaction.user.id != self.user_id:
|
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
|
return
|
|
if self._done:
|
|
await interaction.response.defer()
|
|
return
|
|
self._done = True
|
|
self.stop()
|
|
for child in self.children:
|
|
child.disabled = True
|
|
sell_res = await economy.do_fish_sell(self.user_id, [-1])
|
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
|
fish_name = S.FISH_NAMES[self._fish_id]
|
|
desc = S.FISH_UI["catch_sold"].format(
|
|
name=fish_name,
|
|
weight=self._weight,
|
|
coins=coin(sell_res["coins"]),
|
|
exp=self._res["exp"],
|
|
balance=coin(sell_res["balance"]),
|
|
)
|
|
if self._res.get("is_new"):
|
|
desc += S.FISH_UI["new_fish"]
|
|
embed = discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=0x57F287)
|
|
await interaction.response.edit_message(embed=embed, view=self)
|
|
|
|
@discord.ui.button(label="", style=discord.ButtonStyle.secondary)
|
|
async def keep_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
|
|
if interaction.user.id != self.user_id:
|
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
|
return
|
|
if self._done:
|
|
await interaction.response.defer()
|
|
return
|
|
self._done = True
|
|
self.stop()
|
|
for child in self.children:
|
|
child.disabled = True
|
|
rarity = economy.FISH_CATALOGUE[self._fish_id]["rarity"]
|
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
|
fish_name = S.FISH_NAMES[self._fish_id]
|
|
desc = S.FISH_UI["catch_kept"].format(
|
|
name=fish_name,
|
|
weight=self._weight,
|
|
exp=self._res["exp"],
|
|
)
|
|
if self._res.get("is_new"):
|
|
desc += S.FISH_UI["new_fish"]
|
|
embed = discord.Embed(title=f"{emoji} {fish_name}!", description=desc, color=0x5865F2)
|
|
await interaction.response.edit_message(embed=embed, view=self)
|
|
|
|
async def on_timeout(self):
|
|
for child in self.children:
|
|
child.disabled = True
|
|
|
|
class FishingView(discord.ui.View):
|
|
BITE_WINDOW = 2.0
|
|
|
|
def __init__(self, user_id: int, fish_id: str, weight: int):
|
|
super().__init__(timeout=40)
|
|
self.user_id = user_id
|
|
self._fish_id = fish_id
|
|
self._weight = weight
|
|
self._clicked = False
|
|
self._bite_active = False
|
|
self._msg: discord.Message | None = None
|
|
|
|
self.pull_btn = discord.ui.Button(
|
|
label=S.FISH_UI["btn_wait"],
|
|
style=discord.ButtonStyle.secondary,
|
|
disabled=True,
|
|
)
|
|
self.pull_btn.callback = self._pull
|
|
self.add_item(self.pull_btn)
|
|
|
|
async def start(self, msg: discord.Message) -> None:
|
|
self._msg = msg
|
|
wait = random.uniform(5, 15)
|
|
await asyncio.sleep(wait)
|
|
if self._clicked or self.is_finished():
|
|
return
|
|
self._bite_active = True
|
|
self.pull_btn.disabled = False
|
|
self.pull_btn.label = S.FISH_UI["btn_bite"]
|
|
self.pull_btn.style = discord.ButtonStyle.success
|
|
try:
|
|
await msg.edit(
|
|
embed=discord.Embed(
|
|
title=S.TITLE["fish_bite"],
|
|
description=S.FISH_UI["bite_desc"],
|
|
color=0xED4245,
|
|
),
|
|
view=self,
|
|
)
|
|
except Exception:
|
|
pass
|
|
await asyncio.sleep(self.BITE_WINDOW)
|
|
if not self._clicked:
|
|
self.stop()
|
|
active_games.discard(self.user_id)
|
|
self.pull_btn.disabled = True
|
|
try:
|
|
await msg.edit(
|
|
embed=discord.Embed(
|
|
title=S.TITLE["fish_escape"],
|
|
description=S.FISH_UI["escape_desc"],
|
|
color=0x99AAB5,
|
|
),
|
|
view=self,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
async def _pull(self, interaction: discord.Interaction) -> None:
|
|
if interaction.user.id != self.user_id:
|
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
|
return
|
|
if not self._bite_active:
|
|
await interaction.response.send_message(S.FISH_UI["too_early"], ephemeral=True)
|
|
return
|
|
self._clicked = True
|
|
self.stop()
|
|
active_games.discard(self.user_id)
|
|
self.pull_btn.disabled = True
|
|
await interaction.response.defer()
|
|
|
|
if self._fish_id == "junk":
|
|
junk_text = random.choice(S.FISH_JUNK_LINES)
|
|
user_data = await economy.get_user(interaction.user.id)
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fish_junk"],
|
|
description=S.FISH_UI["junk_desc"].format(
|
|
text=junk_text,
|
|
balance=coin(user_data.get("balance", 0)),
|
|
),
|
|
color=0x99AAB5,
|
|
)
|
|
await self._msg.edit(embed=embed, view=self)
|
|
return
|
|
|
|
res = await economy.do_fish_resolve(self.user_id, self._fish_id, self._weight)
|
|
if not res["ok"]:
|
|
await self._msg.edit(
|
|
embed=discord.Embed(title=S.ERR["generic_error"], color=0xED4245),
|
|
view=self,
|
|
)
|
|
return
|
|
|
|
catch_view = FishCatchView(self.user_id, res, self._fish_id, self._weight)
|
|
catch_view.sell_btn.label = S.FISH_UI["btn_sell"]
|
|
catch_view.keep_btn.label = S.FISH_UI["btn_keep"]
|
|
await self._msg.edit(embed=catch_view._catch_embed(), view=catch_view)
|
|
if res.get("exp", 0) > 0:
|
|
asyncio.create_task(award_exp(interaction, res["exp"]))
|
|
|
|
async def on_timeout(self):
|
|
for child in self.children:
|
|
child.disabled = True
|
|
active_games.discard(self.user_id)
|
|
|
|
@tree.command(name="fish", description=S.CMD["fish"])
|
|
@app_commands.guild_only()
|
|
async def cmd_fish(interaction: discord.Interaction):
|
|
if await check_cmd_rate(interaction):
|
|
return
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["game_in_progress"], ephemeral=True)
|
|
return
|
|
|
|
res = await economy.do_fish_start(interaction.user.id)
|
|
if not res["ok"]:
|
|
if res["reason"] == "banned":
|
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
|
elif res["reason"] == "cooldown":
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["fish"].format(ts=cd_ts(res["remaining"])),
|
|
ephemeral=True,
|
|
)
|
|
elif res["reason"] == "jailed":
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])),
|
|
ephemeral=True,
|
|
)
|
|
return
|
|
|
|
user_data = await economy.get_user(interaction.user.id)
|
|
rarity_bump = "kalavork" in user_data.get("items", [])
|
|
has_echolood = "echolood" in user_data.get("items", [])
|
|
fish_id, weight = economy.roll_fish(rarity_bump=rarity_bump)
|
|
|
|
active_games.add(interaction.user.id)
|
|
view = FishingView(interaction.user.id, fish_id, weight)
|
|
if has_echolood:
|
|
view.BITE_WINDOW = 3.0
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fish_cast"],
|
|
description=S.FISH_UI["cast_desc"],
|
|
color=0x5865F2,
|
|
)
|
|
await interaction.response.send_message(embed=embed, view=view)
|
|
msg = await interaction.original_response()
|
|
asyncio.create_task(view.start(msg))
|
|
asyncio.create_task(maybe_remind(interaction.user.id, "fish"))
|
|
|
|
@tree.command(name="fishbook", description=S.CMD["fishbook"])
|
|
@app_commands.describe(kasutaja=S.OPT["fishbook_kasutaja"])
|
|
async def cmd_fishbook(interaction: discord.Interaction, kasutaja: discord.Member | None = None):
|
|
target = kasutaja or interaction.user
|
|
res = await economy.do_fishbook(target.id)
|
|
book: dict = res["book"]
|
|
total = res["total_species"]
|
|
caught_count = res["unique_caught"]
|
|
|
|
if not book:
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fishbook"],
|
|
description=S.FISH_UI["book_empty"],
|
|
color=0x5865F2,
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
inv_counts: dict = res.get("inv_counts", {})
|
|
all_fish = list(economy.FISH_CATALOGUE.items())
|
|
lines = [S.FISH_UI["book_caught"].format(caught=caught_count, total=total)]
|
|
for fish_id, fish_data in all_fish:
|
|
rarity = fish_data["rarity"]
|
|
emoji = S.FISH_RARITY_EMOJI[rarity]
|
|
rarity_name = S.FISH_RARITY_NAMES[rarity]
|
|
count = book.get(fish_id, 0)
|
|
if count > 0:
|
|
n_inv = inv_counts.get(fish_id, 0)
|
|
inv_str = S.FISH_UI["book_inv"].format(n=n_inv) if n_inv > 0 else ""
|
|
lines.append(
|
|
S.FISH_UI["book_yes"].format(
|
|
emoji=emoji,
|
|
name=S.FISH_NAMES[fish_id],
|
|
rarity=rarity_name,
|
|
count=count,
|
|
inv=inv_str,
|
|
)
|
|
)
|
|
else:
|
|
lines.append(S.FISH_UI["book_no"].format(rarity=rarity_name))
|
|
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fishbook"].replace("Kalakogu", f"{target.display_name} kalakogu"),
|
|
description="\n".join(lines),
|
|
color=0x5865F2,
|
|
)
|
|
embed.set_footer(
|
|
text=S.FISH_UI["book_footer"].format(
|
|
page=1,
|
|
total_pages=1,
|
|
caught=caught_count,
|
|
total=total,
|
|
)
|
|
)
|
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
|
|
|
@tree.command(name="fishsell", description=S.CMD["fishsell"])
|
|
@app_commands.guild_only()
|
|
async def cmd_fishsell(interaction: discord.Interaction):
|
|
await interaction.response.defer(ephemeral=True)
|
|
user_data = await economy.get_user(interaction.user.id)
|
|
inv: list = user_data.get("fish_inventory") or []
|
|
if not inv:
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fishbook"],
|
|
description=S.FISH_UI["inv_empty"],
|
|
color=0x5865F2,
|
|
)
|
|
await interaction.followup.send(embed=embed, ephemeral=True)
|
|
return
|
|
|
|
total_value = sum(e["value"] for e in inv)
|
|
lines = [S.FISH_UI["inv_header"].format(count=len(inv), total_value=coin(total_value))]
|
|
for entry in inv:
|
|
fid = entry.get("fish_id", "")
|
|
rarity = economy.FISH_CATALOGUE.get(fid, {}).get("rarity", "common")
|
|
emoji = S.FISH_RARITY_EMOJI.get(rarity, "🐟")
|
|
name = S.FISH_NAMES.get(fid, fid)
|
|
lines.append(
|
|
S.FISH_UI["inv_entry"].format(
|
|
emoji=emoji,
|
|
name=name,
|
|
weight=entry["weight"],
|
|
value=coin(entry["value"]),
|
|
)
|
|
)
|
|
|
|
embed = discord.Embed(
|
|
title=S.TITLE["fishbook"],
|
|
description="\n".join(lines),
|
|
color=0x5865F2,
|
|
)
|
|
|
|
sell_all_btn = discord.ui.Button(
|
|
label=S.FISH_UI["btn_sell"] + f" ({coin(total_value)})",
|
|
style=discord.ButtonStyle.success,
|
|
)
|
|
|
|
async def _sell_all(btn_interaction: discord.Interaction):
|
|
if btn_interaction.user.id != interaction.user.id:
|
|
await btn_interaction.response.send_message(S.ERR["not_your_menu"], ephemeral=True)
|
|
return
|
|
sell_view.stop()
|
|
for child in sell_view.children:
|
|
child.disabled = True
|
|
res = await economy.do_fish_sell(interaction.user.id)
|
|
if not res["ok"]:
|
|
await btn_interaction.response.edit_message(
|
|
embed=discord.Embed(description=S.FISH_UI["inv_none"], color=0x99AAB5),
|
|
view=sell_view,
|
|
)
|
|
return
|
|
sold_embed = discord.Embed(
|
|
title=S.TITLE["fishbook"],
|
|
description=S.FISH_UI["inv_sold_all"].format(
|
|
count=res["count"],
|
|
coins=coin(res["coins"]),
|
|
balance=coin(res["balance"]),
|
|
),
|
|
color=0x57F287,
|
|
)
|
|
await btn_interaction.response.edit_message(embed=sold_embed, view=sell_view)
|
|
|
|
sell_all_btn.callback = _sell_all
|
|
sell_view = discord.ui.View(timeout=60)
|
|
sell_view.add_item(sell_all_btn)
|
|
await interaction.followup.send(embed=embed, view=sell_view, ephemeral=True)
|