forked from sass/tipibot
1149 lines
50 KiB
Python
1149 lines
50 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import datetime
|
|
import random
|
|
from collections.abc import Awaitable, Callable, MutableSet
|
|
|
|
import discord
|
|
from discord import app_commands
|
|
|
|
from core import economy
|
|
import strings as S
|
|
|
|
|
|
def register_economy_games_commands(
|
|
tree: app_commands.CommandTree,
|
|
coin: Callable[[int], str],
|
|
cd_ts: Callable[[datetime.timedelta], str],
|
|
parse_amount: Callable[[str, int], tuple[int | None, str | None]],
|
|
gamble_cd: Callable[[int, bool], datetime.timedelta | None],
|
|
award_exp: Callable[[discord.Interaction, int], Awaitable[None]],
|
|
active_games: MutableSet[int],
|
|
) -> None:
|
|
# -----------------------------------------------------------------------
|
|
# /roulette animation
|
|
# -----------------------------------------------------------------------
|
|
_ROULETTE_R = "\U0001f534" # 🔴
|
|
_ROULETTE_B = "\u26ab" # ⚫
|
|
_ROULETTE_G = "\U0001f7e2" # 🟢
|
|
# delays between frames, fast → slow (12 transitions = 13 total viewport positions)
|
|
_ROULETTE_WHEEL_DELAYS = [0.15, 0.15, 0.18, 0.20, 0.22, 0.25, 0.28, 0.35, 0.45, 0.60, 0.80, 1.00]
|
|
|
|
def _build_roulette_strip(result_emoji: str) -> list[str]:
|
|
"""Build a 17-symbol wheel strip obeying strict transition rules:
|
|
R -> B or G (R can go to either)
|
|
B -> R (B must go to R)
|
|
G -> B (G must go to B)
|
|
Result is at strip[14] = center of the final viewport.
|
|
Prefix (0-13) is generated backward from the result;
|
|
suffix (15-16) is generated forward from the result.
|
|
Greens appear randomly in the prefix as near-miss elements (up to 2).
|
|
"""
|
|
r, b, g = _ROULETTE_R, _ROULETTE_B, _ROULETTE_G
|
|
strip: list[str] = [None] * 17 # type: ignore[list-item]
|
|
|
|
# Suffix: positions 15-16 (deterministic, no greens after result)
|
|
strip[14] = result_emoji
|
|
strip[15] = r if result_emoji == b else b # B->R, R->B, G->B
|
|
strip[16] = b if strip[15] == r else r # R->B, B->R
|
|
|
|
# Prefix: positions 0-13 built backward from result
|
|
# Inverse transitions: pred(R)=B, pred(B)=R or G, pred(G)=R
|
|
# First pass: collect positions where a green is valid (cur == B, with room for pred).
|
|
# Green is only relevant when result is not green itself.
|
|
green_pos: int | None = None
|
|
if result_emoji != g:
|
|
candidates: list[int] = []
|
|
cur = result_emoji
|
|
for i in range(13, -1, -1):
|
|
if cur == b and 2 <= i <= 11:
|
|
candidates.append(i)
|
|
cur = b if cur == r else (r if cur == g else r)
|
|
if candidates:
|
|
green_pos = random.choice(candidates)
|
|
|
|
# Second pass: generate strip, inserting green at the chosen position.
|
|
cur = result_emoji
|
|
for i in range(13, -1, -1):
|
|
if cur == r:
|
|
strip[i] = b
|
|
elif cur == g:
|
|
strip[i] = r
|
|
else: # cur == B
|
|
strip[i] = g if i == green_pos else r
|
|
cur = strip[i]
|
|
|
|
return strip
|
|
|
|
def _roulette_frame_embed(symbols: list[str], stopped: bool = False) -> discord.Embed:
|
|
title = S.ROULETTE["spin_stop"] if stopped else S.ROULETTE["spin_title"]
|
|
desc = S.ROULETTE["spin_strip"].format(
|
|
s0=symbols[0], s1=symbols[1], s2=symbols[2], s3=symbols[3], s4=symbols[4]
|
|
)
|
|
return discord.Embed(title=title, description=desc, color=0x99AAB5)
|
|
|
|
@tree.command(name="roulette", description=S.CMD["roulette"])
|
|
@app_commands.describe(panus=S.OPT["roulette_panus"], värv=S.OPT["roulette_värv"])
|
|
@app_commands.choices(
|
|
värv=[
|
|
app_commands.Choice(name="🔴 Punane", value="punane"),
|
|
app_commands.Choice(name="⚫ Must", value="must"),
|
|
app_commands.Choice(name="🟢 Roheline", value="roheline"),
|
|
]
|
|
)
|
|
async def cmd_roulette(interaction: discord.Interaction, panus: str, värv: app_commands.Choice[str]):
|
|
data = await economy.get_user(interaction.user.id)
|
|
panus_int, err = parse_amount(panus, data["balance"])
|
|
if err:
|
|
await interaction.response.send_message(err, ephemeral=True)
|
|
return
|
|
if panus_int is None or panus_int <= 0:
|
|
await interaction.response.send_message(S.ERR["positive_bet"], ephemeral=True)
|
|
return
|
|
has_360 = "monitor_360" in data.get("items", [])
|
|
if rem := gamble_cd(interaction.user.id, has_360):
|
|
await interaction.response.send_message(
|
|
S.ERR["gamble_cooldown"].format(ts=cd_ts(rem)), ephemeral=True
|
|
)
|
|
return
|
|
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
active_games.add(interaction.user.id)
|
|
res = await economy.do_roulette(interaction.user.id, panus_int, värv.value)
|
|
if not res["ok"]:
|
|
active_games.discard(interaction.user.id)
|
|
if res["reason"] == "banned":
|
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
|
elif res["reason"] == "jailed":
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
|
)
|
|
else:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
|
)
|
|
return
|
|
|
|
result_emoji = S.ROULETTE["emoji"][res["result"]]
|
|
strip = _build_roulette_strip(result_emoji)
|
|
|
|
try:
|
|
await interaction.response.send_message(embed=_roulette_frame_embed(strip[0:5]))
|
|
spin_msg = await interaction.original_response()
|
|
|
|
for i, delay in enumerate(_ROULETTE_WHEEL_DELAYS, 1):
|
|
await asyncio.sleep(delay)
|
|
stopped = i == len(_ROULETTE_WHEEL_DELAYS)
|
|
await spin_msg.edit(embed=_roulette_frame_embed(strip[i : i + 5], stopped=stopped))
|
|
|
|
await asyncio.sleep(0.55)
|
|
|
|
emoji = S.ROULETTE["emoji"].get(res["result"], "🎰")
|
|
genitive = S.ROULETTE["genitive"].get(res["result"], res["result"])
|
|
if res["won"]:
|
|
mult_str = f" · **{res['mult']}x**" if res["mult"] > 1 else ""
|
|
embed = discord.Embed(
|
|
title=S.ROULETTE["win_title"].format(emoji=emoji),
|
|
description=S.ROULETTE["win_desc"].format(
|
|
genitive=genitive,
|
|
mult=mult_str,
|
|
change=coin(res["change"]),
|
|
balance=coin(res["balance"]),
|
|
),
|
|
color=0x57F287,
|
|
)
|
|
asyncio.create_task(award_exp(interaction, economy.gamble_exp(panus_int)))
|
|
else:
|
|
embed = discord.Embed(
|
|
title=S.ROULETTE["lose_title"].format(emoji=emoji),
|
|
description=S.ROULETTE["lose_desc"].format(
|
|
genitive=genitive,
|
|
change=coin(abs(res["change"])),
|
|
balance=coin(res["balance"]),
|
|
),
|
|
color=0xED4245,
|
|
)
|
|
await spin_msg.edit(embed=embed)
|
|
finally:
|
|
active_games.discard(interaction.user.id)
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Rock Paper Scissors (vs Bot OR PvP)
|
|
# -----------------------------------------------------------------------
|
|
_RPS_CHOICES = S.RPS_CHOICES
|
|
_RPS_BEATS = {"🪨": "✂️", "📄": "🪨", "✂️": "📄"}
|
|
|
|
class RPSView(discord.ui.View):
|
|
def __init__(self, challenger: discord.User, bet: int = 0):
|
|
super().__init__(timeout=60)
|
|
self.challenger = challenger
|
|
self.bet = bet
|
|
|
|
async def _resolve(self, interaction: discord.Interaction, player_pick: str):
|
|
if interaction.user.id != self.challenger.id:
|
|
await interaction.response.send_message(S.ERR["not_your_game"], ephemeral=True)
|
|
return
|
|
self.stop()
|
|
active_games.discard(self.challenger.id)
|
|
bot_pick = random.choice(list(_RPS_CHOICES))
|
|
p_name = _RPS_CHOICES[player_pick]
|
|
b_name = _RPS_CHOICES[bot_pick]
|
|
if player_pick == bot_pick:
|
|
outcome, result, color = "tie", S.RPS_UI["result_tie"], 0x99AAB5
|
|
elif _RPS_BEATS[player_pick] == bot_pick:
|
|
outcome, result, color = "win", S.RPS_UI["result_win"], 0x57F287
|
|
else:
|
|
outcome, result, color = "lose", S.RPS_UI["result_lose"], 0xED4245
|
|
|
|
bet_line = ""
|
|
if self.bet > 0:
|
|
res = await economy.do_game_bet(interaction.user.id, self.bet, outcome)
|
|
if outcome == "win":
|
|
bet_line = S.RPS_UI["bet_win"].format(amount=coin(self.bet), balance=coin(res["balance"]))
|
|
elif outcome == "lose":
|
|
bet_line = S.RPS_UI["bet_lose"].format(amount=coin(self.bet), balance=coin(res["balance"]))
|
|
else:
|
|
bet_line = S.RPS_UI["bet_tie"].format(balance=coin(res["balance"]))
|
|
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps"],
|
|
description=S.RPS_UI["result_desc"].format(
|
|
player_pick=player_pick,
|
|
player_name=p_name,
|
|
bot_pick=bot_pick,
|
|
bot_name=b_name,
|
|
result=result,
|
|
bet_line=bet_line,
|
|
),
|
|
color=color,
|
|
)
|
|
await interaction.response.edit_message(embed=embed, view=None)
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_rock"], style=discord.ButtonStyle.secondary)
|
|
async def pick_rock(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._resolve(interaction, "🪨")
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_paper"], style=discord.ButtonStyle.secondary)
|
|
async def pick_paper(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._resolve(interaction, "📄")
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_scissors"], style=discord.ButtonStyle.secondary)
|
|
async def pick_scissors(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._resolve(interaction, "✂️")
|
|
|
|
async def on_timeout(self):
|
|
active_games.discard(self.challenger.id)
|
|
for item in self.children:
|
|
item.disabled = True
|
|
|
|
class RpsGame:
|
|
"""Shared mutable state for a PvP RPS match."""
|
|
|
|
def __init__(self, player_a: discord.Member, player_b: discord.Member, bet: int):
|
|
self.player_a = player_a
|
|
self.player_b = player_b
|
|
self.bet = bet
|
|
self.choice_a: str | None = None
|
|
self.choice_b: str | None = None
|
|
self.dm_msg_a: discord.Message | None = None
|
|
self.dm_msg_b: discord.Message | None = None
|
|
self.server_message: discord.Message | None = None
|
|
self._resolved = False
|
|
self._lock = asyncio.Lock()
|
|
|
|
async def maybe_resolve(self) -> None:
|
|
async with self._lock:
|
|
if self._resolved or self.choice_a is None or self.choice_b is None:
|
|
return
|
|
self._resolved = True
|
|
|
|
a, b = self.choice_a, self.choice_b
|
|
if a == b:
|
|
winner, color = None, 0x99AAB5
|
|
result_a = result_b = S.RPS_UI["result_tie"]
|
|
elif _RPS_BEATS[a] == b:
|
|
winner, color = "a", 0x57F287
|
|
result_a = S.RPS_UI["result_win"]
|
|
result_b = S.RPS_UI["duel_verdict_win"].format(name=self.player_a.display_name)
|
|
else:
|
|
winner, color = "b", 0xED4245
|
|
result_a = S.RPS_UI["duel_verdict_win"].format(name=self.player_b.display_name)
|
|
result_b = S.RPS_UI["result_win"]
|
|
|
|
bet_line_a = bet_line_b = ""
|
|
if self.bet > 0:
|
|
if winner == "a":
|
|
await economy.do_rps_pvp_payout(self.player_a.id, self.bet)
|
|
bet_line_a = f"\n+{coin(self.bet)}"
|
|
bet_line_b = f"\n-{coin(self.bet)}"
|
|
elif winner == "b":
|
|
await economy.do_rps_pvp_payout(self.player_b.id, self.bet)
|
|
bet_line_a = f"\n-{coin(self.bet)}"
|
|
bet_line_b = f"\n+{coin(self.bet)}"
|
|
else:
|
|
await economy.do_rps_pvp_refund(self.player_a.id, self.bet)
|
|
await economy.do_rps_pvp_refund(self.player_b.id, self.bet)
|
|
|
|
data_a = await economy.get_user(self.player_a.id)
|
|
data_b = await economy.get_user(self.player_b.id)
|
|
bal_a, bal_b = data_a["balance"], data_b["balance"]
|
|
|
|
if self.dm_msg_a:
|
|
await self.dm_msg_a.edit(
|
|
content=S.RPS_UI["duel_result_a"].format(
|
|
opponent=self.player_b.display_name,
|
|
pick_a=a,
|
|
name_a=_RPS_CHOICES[a],
|
|
pick_b=b,
|
|
name_b=_RPS_CHOICES[b],
|
|
result=result_a,
|
|
bet_line=bet_line_a,
|
|
balance=coin(bal_a),
|
|
),
|
|
view=None,
|
|
)
|
|
if self.dm_msg_b:
|
|
await self.dm_msg_b.edit(
|
|
content=S.RPS_UI["duel_result_a"].format(
|
|
opponent=self.player_a.display_name,
|
|
pick_a=b,
|
|
name_a=_RPS_CHOICES[b],
|
|
pick_b=a,
|
|
name_b=_RPS_CHOICES[a],
|
|
result=result_b,
|
|
bet_line=bet_line_b,
|
|
balance=coin(bal_b),
|
|
),
|
|
view=None,
|
|
)
|
|
|
|
if self.server_message:
|
|
if winner == "a":
|
|
verdict = S.RPS_UI["duel_verdict_win"].format(name=self.player_a.display_name)
|
|
elif winner == "b":
|
|
verdict = S.RPS_UI["duel_verdict_win"].format(name=self.player_b.display_name)
|
|
else:
|
|
verdict = S.RPS_UI["duel_verdict_tie"]
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_done"],
|
|
description=S.RPS_UI["duel_done_desc"].format(
|
|
a=self.player_a.mention,
|
|
pick_a=a,
|
|
pick_b=b,
|
|
b=self.player_b.mention,
|
|
verdict=verdict,
|
|
name_a=self.player_a.display_name,
|
|
bal_a=coin(bal_a),
|
|
name_b=self.player_b.display_name,
|
|
bal_b=coin(bal_b),
|
|
),
|
|
color=color,
|
|
)
|
|
await self.server_message.edit(embed=embed, view=None)
|
|
active_games.discard(self.player_a.id)
|
|
active_games.discard(self.player_b.id)
|
|
|
|
class RpsDmView(discord.ui.View):
|
|
"""DM view for each player to make their pick in a PvP match."""
|
|
|
|
def __init__(self, game: RpsGame, side: str):
|
|
super().__init__(timeout=120)
|
|
self.game = game
|
|
self.side = side
|
|
|
|
async def _pick(self, interaction: discord.Interaction, choice: str) -> None:
|
|
if self.side == "a":
|
|
self.game.choice_a = choice
|
|
else:
|
|
self.game.choice_b = choice
|
|
for item in self.children:
|
|
item.disabled = True
|
|
self.stop()
|
|
await interaction.response.edit_message(
|
|
content=S.RPS_UI["duel_waiting"].format(choice=choice, name=_RPS_CHOICES[choice]),
|
|
view=self,
|
|
)
|
|
await self.game.maybe_resolve()
|
|
|
|
async def on_timeout(self) -> None:
|
|
async with self.game._lock:
|
|
if self.game._resolved:
|
|
return
|
|
self.game._resolved = True
|
|
if self.game.bet > 0:
|
|
await economy.do_rps_pvp_refund(self.game.player_a.id, self.game.bet)
|
|
await economy.do_rps_pvp_refund(self.game.player_b.id, self.game.bet)
|
|
active_games.discard(self.game.player_a.id)
|
|
active_games.discard(self.game.player_b.id)
|
|
for item in self.children:
|
|
item.disabled = True
|
|
for player in (self.game.player_a, self.game.player_b):
|
|
try:
|
|
await player.send(S.RPS_UI["duel_expire_dm"])
|
|
except discord.Forbidden:
|
|
pass
|
|
if self.game.server_message:
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_expire"],
|
|
description=S.RPS_UI["duel_expire_desc"].format(
|
|
a=self.game.player_a.mention,
|
|
b=self.game.player_b.mention,
|
|
),
|
|
color=0x99AAB5,
|
|
)
|
|
await self.game.server_message.edit(embed=embed, view=None)
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_rock"], style=discord.ButtonStyle.secondary)
|
|
async def pick_rock(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._pick(interaction, "🪨")
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_paper"], style=discord.ButtonStyle.secondary)
|
|
async def pick_paper(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._pick(interaction, "📄")
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_scissors"], style=discord.ButtonStyle.secondary)
|
|
async def pick_scissors(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
await self._pick(interaction, "✂️")
|
|
|
|
class RpsChallengeView(discord.ui.View):
|
|
"""Server-side accept/decline view for PvP RPS challenge."""
|
|
|
|
def __init__(self, game: RpsGame):
|
|
super().__init__(timeout=60)
|
|
self.game = game
|
|
|
|
def _disable_all(self) -> None:
|
|
for item in self.children:
|
|
item.disabled = True
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_accept"], style=discord.ButtonStyle.success)
|
|
async def accept(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
if interaction.user.id != self.game.player_b.id:
|
|
await interaction.response.send_message(S.ERR["not_your_challenge"], ephemeral=True)
|
|
return
|
|
if self.game.player_b.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
self.stop()
|
|
self._disable_all()
|
|
active_games.add(self.game.player_b.id)
|
|
|
|
if self.game.bet > 0:
|
|
deposit_a = await economy.do_rps_pvp_deposit(self.game.player_a.id, self.game.bet)
|
|
if not deposit_a.get("ok"):
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_cancel"],
|
|
description=S.RPS_UI["duel_insufficient"].format(mention=self.game.player_a.mention),
|
|
color=0xED4245,
|
|
)
|
|
await interaction.response.edit_message(embed=embed, view=None)
|
|
async with self.game._lock:
|
|
self.game._resolved = True
|
|
active_games.discard(self.game.player_a.id)
|
|
active_games.discard(self.game.player_b.id)
|
|
return
|
|
deposit_b = await economy.do_rps_pvp_deposit(self.game.player_b.id, self.game.bet)
|
|
if not deposit_b.get("ok"):
|
|
await economy.do_rps_pvp_refund(self.game.player_a.id, self.game.bet)
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_cancel"],
|
|
description=S.RPS_UI["duel_insufficient"].format(mention=self.game.player_b.mention),
|
|
color=0xED4245,
|
|
)
|
|
await interaction.response.edit_message(embed=embed, view=None)
|
|
async with self.game._lock:
|
|
self.game._resolved = True
|
|
active_games.discard(self.game.player_a.id)
|
|
active_games.discard(self.game.player_b.id)
|
|
return
|
|
|
|
bet_str = S.RPS_UI["duel_active_bet"].format(bet=coin(self.game.bet)) if self.game.bet > 0 else ""
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_active"],
|
|
description=S.RPS_UI["duel_active_desc"].format(
|
|
a=self.game.player_a.mention,
|
|
b=self.game.player_b.mention,
|
|
bet=bet_str,
|
|
),
|
|
color=0x5865F2,
|
|
)
|
|
await interaction.response.edit_message(embed=embed, view=self)
|
|
|
|
bet_dm = S.RPS_UI["duel_dm_bet"].format(bet=coin(self.game.bet)) if self.game.bet > 0 else ""
|
|
dm_failed: list[str] = []
|
|
for player, side in ((self.game.player_a, "a"), (self.game.player_b, "b")):
|
|
view = RpsDmView(self.game, side)
|
|
opponent = self.game.player_b if side == "a" else self.game.player_a
|
|
try:
|
|
msg = await player.send(
|
|
S.RPS_UI["duel_dm"].format(opponent=opponent.display_name, bet=bet_dm),
|
|
view=view,
|
|
)
|
|
if side == "a":
|
|
self.game.dm_msg_a = msg
|
|
else:
|
|
self.game.dm_msg_b = msg
|
|
except discord.Forbidden:
|
|
dm_failed.append(player.display_name)
|
|
|
|
if dm_failed:
|
|
async with self.game._lock:
|
|
self.game._resolved = True
|
|
if self.game.bet > 0:
|
|
await economy.do_rps_pvp_refund(self.game.player_a.id, self.game.bet)
|
|
await economy.do_rps_pvp_refund(self.game.player_b.id, self.game.bet)
|
|
active_games.discard(self.game.player_a.id)
|
|
active_games.discard(self.game.player_b.id)
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_cancel"],
|
|
description=S.RPS_UI["duel_dm_fail"].format(names=", ".join(dm_failed)),
|
|
color=0xED4245,
|
|
)
|
|
await self.game.server_message.edit(embed=embed, view=None)
|
|
|
|
@discord.ui.button(label=S.RPS_UI["btn_decline"], style=discord.ButtonStyle.danger)
|
|
async def decline(self, interaction: discord.Interaction, _: discord.ui.Button):
|
|
if interaction.user.id != self.game.player_b.id:
|
|
await interaction.response.send_message(S.ERR["not_your_challenge"], ephemeral=True)
|
|
return
|
|
self.stop()
|
|
self._disable_all()
|
|
active_games.discard(self.game.player_a.id)
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_decline"],
|
|
description=S.RPS_UI["duel_decline"].format(name=self.game.player_b.display_name),
|
|
color=0xED4245,
|
|
)
|
|
await interaction.response.edit_message(embed=embed, view=self)
|
|
|
|
async def on_timeout(self) -> None:
|
|
active_games.discard(self.game.player_a.id)
|
|
self._disable_all()
|
|
if self.game.server_message:
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel_expire"],
|
|
description=S.RPS_UI["duel_no_answer"].format(name=self.game.player_b.display_name),
|
|
color=0x99AAB5,
|
|
)
|
|
await self.game.server_message.edit(embed=embed, view=self)
|
|
|
|
@tree.command(name="rps", description=S.CMD["rps"])
|
|
@app_commands.describe(panus=S.OPT["rps_panus"], vastane=S.OPT["rps_vastane"])
|
|
async def cmd_rps(interaction: discord.Interaction, panus: str = "0", vastane: discord.Member | None = None):
|
|
data = await economy.get_user(interaction.user.id)
|
|
panus_int, err = parse_amount(panus, data["balance"])
|
|
if err:
|
|
await interaction.response.send_message(err, ephemeral=True)
|
|
return
|
|
if panus_int is None or panus_int < 0:
|
|
await interaction.response.send_message(S.ERR["positive_bet"], ephemeral=True)
|
|
return
|
|
if panus_int > 0:
|
|
if rem := economy.jailed_remaining(data):
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["jailed"].format(ts=cd_ts(rem)), ephemeral=True
|
|
)
|
|
return
|
|
|
|
# PvP mode
|
|
if vastane is not None:
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
if vastane.id == interaction.user.id:
|
|
await interaction.response.send_message(S.ERR["rps_self"], ephemeral=True)
|
|
return
|
|
if vastane.bot:
|
|
await interaction.response.send_message(S.ERR["rps_bot"], ephemeral=True)
|
|
return
|
|
if panus_int > 0 and data["balance"] < panus_int:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
|
)
|
|
return
|
|
|
|
game = RpsGame(interaction.user, vastane, panus_int)
|
|
bet_challenge = S.RPS_UI["challenge_bet"].format(bet=coin(panus_int)) if panus_int > 0 else ""
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps_duel"],
|
|
description=S.RPS_UI["challenge_desc"].format(
|
|
challenger=interaction.user.mention,
|
|
opponent=vastane.mention,
|
|
bet=bet_challenge,
|
|
),
|
|
color=0x5865F2,
|
|
)
|
|
embed.set_footer(text=S.RPS_UI["challenge_footer"])
|
|
challenge_view = RpsChallengeView(game)
|
|
await interaction.response.send_message(embed=embed, view=challenge_view)
|
|
active_games.add(interaction.user.id)
|
|
game.server_message = await interaction.original_response()
|
|
return
|
|
|
|
# vs Bot mode
|
|
if panus_int > 0 and data["balance"] < panus_int:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
|
)
|
|
return
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
if panus_int > 0:
|
|
has_360 = "monitor_360" in data.get("items", [])
|
|
if rem := gamble_cd(interaction.user.id, has_360):
|
|
await interaction.response.send_message(
|
|
S.ERR["gamble_cooldown"].format(ts=cd_ts(rem)), ephemeral=True
|
|
)
|
|
return
|
|
active_games.add(interaction.user.id)
|
|
bet_str = S.RPS_UI["vs_bot_bet"].format(bet=coin(panus_int)) if panus_int > 0 else ""
|
|
embed = discord.Embed(
|
|
title=S.TITLE["rps"],
|
|
description=S.RPS_UI["vs_bot_desc"] + bet_str,
|
|
color=0x5865F2,
|
|
)
|
|
await interaction.response.send_message(embed=embed, view=RPSView(interaction.user, panus_int))
|
|
|
|
# -----------------------------------------------------------------------
|
|
# /slots
|
|
# -----------------------------------------------------------------------
|
|
_SLOTS_SPIN = "<a:TipiSLOTS:1483444233863037101>"
|
|
_SLOTS_DELAY = 0.7
|
|
|
|
def _slots_embed(
|
|
r1: str,
|
|
r2: str,
|
|
r3: str,
|
|
title: str = "", # set dynamically
|
|
color: int = 0x5865F2,
|
|
footer: str = "",
|
|
) -> discord.Embed:
|
|
desc = f"{r1} | {r2} | {r3}"
|
|
if footer:
|
|
desc += f"\n\n{footer}"
|
|
return discord.Embed(title=title, description=desc, color=color)
|
|
|
|
@tree.command(name="slots", description=S.CMD["slots"])
|
|
@app_commands.describe(panus=S.OPT["slots_panus"])
|
|
async def cmd_slots(interaction: discord.Interaction, panus: str):
|
|
data = await economy.get_user(interaction.user.id)
|
|
panus_int, err = parse_amount(panus, data["balance"])
|
|
if err:
|
|
await interaction.response.send_message(err, ephemeral=True)
|
|
return
|
|
if panus_int is None or panus_int <= 0:
|
|
await interaction.response.send_message(S.ERR["positive_bet"], ephemeral=True)
|
|
return
|
|
has_360 = "monitor_360" in data.get("items", [])
|
|
if rem := gamble_cd(interaction.user.id, has_360):
|
|
await interaction.response.send_message(
|
|
S.ERR["gamble_cooldown"].format(ts=cd_ts(rem)), ephemeral=True
|
|
)
|
|
return
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
active_games.add(interaction.user.id)
|
|
res = await economy.do_slots(interaction.user.id, panus_int)
|
|
if not res["ok"]:
|
|
active_games.discard(interaction.user.id)
|
|
if res["reason"] == "banned":
|
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
|
return
|
|
if res["reason"] == "jailed":
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
|
)
|
|
return
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
|
)
|
|
return
|
|
|
|
reels = res["reels"]
|
|
tier = res["tier"]
|
|
change = res["change"]
|
|
sp = _SLOTS_SPIN
|
|
|
|
try:
|
|
await interaction.response.send_message(embed=_slots_embed(sp, sp, sp, title=S.SLOTS_UI["playing"]))
|
|
msg = await interaction.original_response()
|
|
|
|
await asyncio.sleep(_SLOTS_DELAY)
|
|
await msg.edit(embed=_slots_embed(reels[0], sp, sp, title=S.SLOTS_UI["playing"]))
|
|
await asyncio.sleep(_SLOTS_DELAY)
|
|
await msg.edit(embed=_slots_embed(reels[0], reels[1], sp, title=S.SLOTS_UI["playing"]))
|
|
await asyncio.sleep(_SLOTS_DELAY)
|
|
await msg.edit(embed=_slots_embed(reels[0], reels[1], reels[2], title=S.SLOTS_UI["playing"]))
|
|
await asyncio.sleep(_SLOTS_DELAY * 0.6)
|
|
|
|
tier_key = tier if tier in S.SLOTS_TIERS else "miss"
|
|
title, color = S.SLOTS_TIERS[tier_key]
|
|
if tier == "jackpot":
|
|
footer = S.SLOTS_UI["jackpot_footer"].format(change=coin(change))
|
|
elif tier == "triple":
|
|
footer = S.SLOTS_UI["triple_footer"].format(change=coin(change))
|
|
elif tier == "pair":
|
|
footer = S.SLOTS_UI["pair_footer"].format(change=coin(change))
|
|
else:
|
|
footer = S.SLOTS_UI["miss_footer"].format(amount=coin(panus_int))
|
|
footer += S.SLOTS_UI["balance_line"].format(balance=coin(res["balance"]))
|
|
|
|
await msg.edit(
|
|
embed=_slots_embed(
|
|
reels[0],
|
|
reels[1],
|
|
reels[2],
|
|
title=title,
|
|
color=color,
|
|
footer=footer,
|
|
)
|
|
)
|
|
if tier in ("jackpot", "triple", "pair"):
|
|
asyncio.create_task(award_exp(interaction, economy.gamble_exp(panus_int)))
|
|
finally:
|
|
active_games.discard(interaction.user.id)
|
|
|
|
# -----------------------------------------------------------------------
|
|
# /blackjack
|
|
# -----------------------------------------------------------------------
|
|
_BJ_RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
|
|
_BJ_SUITS = ["♠", "♥", "♦", "♣"]
|
|
_BJ_DEAL_DELAY = 0.65
|
|
|
|
def _bj_deck() -> list[tuple[str, str]]:
|
|
deck = [(r, s) for r in _BJ_RANKS for s in _BJ_SUITS]
|
|
random.shuffle(deck)
|
|
return deck
|
|
|
|
def _bj_value(hand: list[tuple[str, str]]) -> int:
|
|
total, aces = 0, 0
|
|
for rank, _ in hand:
|
|
if rank == "A":
|
|
total += 11
|
|
aces += 1
|
|
elif rank in ("J", "Q", "K", "10"):
|
|
total += 10
|
|
else:
|
|
total += int(rank)
|
|
while total > 21 and aces:
|
|
total -= 10
|
|
aces -= 1
|
|
return total
|
|
|
|
def _bj_hand_str(hand: list[tuple[str, str]], hide_second: bool = False) -> str:
|
|
if hide_second and len(hand) >= 2:
|
|
return f"`{hand[0][0]}{hand[0][1]}` `🂠`"
|
|
return " ".join(f"`{r}{s}`" for r, s in hand)
|
|
|
|
def _bj_is_blackjack(hand: list[tuple[str, str]]) -> bool:
|
|
return len(hand) == 2 and _bj_value(hand) == 21
|
|
|
|
def _bj_embed(
|
|
player_hand: list,
|
|
dealer_hand: list,
|
|
title: str,
|
|
color: int,
|
|
*,
|
|
hide_dealer: bool = True,
|
|
doubled_total: int = 0,
|
|
result_field: tuple | None = None,
|
|
) -> discord.Embed:
|
|
p_str = _bj_hand_str(player_hand) if player_hand else "-"
|
|
p_val = f" `{_bj_value(player_hand)}`" if player_hand else ""
|
|
if not dealer_hand:
|
|
d_str, d_val = "-", ""
|
|
elif hide_dealer:
|
|
d_str = _bj_hand_str(dealer_hand, hide_second=True)
|
|
d_val = f" `{_bj_value([dealer_hand[0]])}`"
|
|
else:
|
|
d_str = _bj_hand_str(dealer_hand)
|
|
d_val = f" `{_bj_value(dealer_hand)}`"
|
|
desc = f"**{S.BJ_UI['dealer']}:** {d_str}{d_val}\n**{S.BJ_UI['player']}:** {p_str}{p_val}"
|
|
if doubled_total:
|
|
desc += "\n" + S.BJ["doubled_label"].format(total=coin(doubled_total))
|
|
embed = discord.Embed(title=title, description=desc, color=color)
|
|
if result_field:
|
|
embed.add_field(name=result_field[0], value=result_field[1], inline=False)
|
|
return embed
|
|
|
|
class BlackjackView(discord.ui.View):
|
|
def __init__(
|
|
self,
|
|
user_id: int,
|
|
bet: int,
|
|
player_hand: list,
|
|
dealer_hand: list,
|
|
deck: list,
|
|
):
|
|
super().__init__(timeout=120)
|
|
self.user_id = user_id
|
|
self.bet = bet
|
|
self.hands: list[list] = [player_hand]
|
|
self.bets: list[int] = [bet]
|
|
self.hand_idx: int = 0
|
|
self.dealer_hand = dealer_hand
|
|
self.deck = deck
|
|
self._doubled_hands: set[int] = set()
|
|
self._split_aces: bool = False
|
|
self.message: discord.Message | None = None
|
|
self._refresh_buttons()
|
|
|
|
@property
|
|
def _cur_hand(self) -> list:
|
|
return self.hands[self.hand_idx]
|
|
|
|
def _can_split(self) -> bool:
|
|
return (
|
|
len(self.hands) == 1
|
|
and len(self._cur_hand) == 2
|
|
and self._cur_hand[0][0] == self._cur_hand[1][0]
|
|
)
|
|
|
|
def _refresh_buttons(self) -> None:
|
|
self.clear_items()
|
|
is_split = len(self.hands) > 1
|
|
can_double = not is_split and 0 not in self._doubled_hands and len(self._cur_hand) == 2
|
|
hit_btn = discord.ui.Button(label=S.BJ["btn_hit"], style=discord.ButtonStyle.primary)
|
|
hit_btn.callback = self._hit
|
|
stand_btn = discord.ui.Button(label=S.BJ["btn_stand"], style=discord.ButtonStyle.secondary)
|
|
stand_btn.callback = self._stand
|
|
double_btn = discord.ui.Button(
|
|
label=S.BJ["btn_double"].format(bet=self.bet),
|
|
style=discord.ButtonStyle.success,
|
|
disabled=not can_double,
|
|
)
|
|
double_btn.callback = self._double
|
|
self.add_item(hit_btn)
|
|
self.add_item(stand_btn)
|
|
self.add_item(double_btn)
|
|
if self._can_split():
|
|
split_btn = discord.ui.Button(
|
|
label=S.BJ["btn_split"].format(bet=self.bet),
|
|
style=discord.ButtonStyle.danger,
|
|
)
|
|
split_btn.callback = self._split_hand
|
|
self.add_item(split_btn)
|
|
|
|
def _cur_embed(self, game_over: bool = False, hand_results: list | None = None) -> discord.Embed:
|
|
if not self.dealer_hand:
|
|
d_str, d_val = "-", ""
|
|
elif not game_over:
|
|
d_str = _bj_hand_str(self.dealer_hand, hide_second=True)
|
|
d_val = f" `{_bj_value([self.dealer_hand[0]])}`"
|
|
else:
|
|
d_str = _bj_hand_str(self.dealer_hand)
|
|
d_val = f" `{_bj_value(self.dealer_hand)}`"
|
|
desc = f"**{S.BJ_UI['dealer']}:** {d_str}{d_val}"
|
|
|
|
if len(self.hands) == 1:
|
|
hand = self.hands[0]
|
|
pv = _bj_value(hand)
|
|
doubled_str = f" 💰 *{coin(self.bets[0])}*" if 0 in self._doubled_hands else ""
|
|
desc += f"\n**{S.BJ_UI['player']}:** {_bj_hand_str(hand)} `{pv}`{doubled_str}"
|
|
else:
|
|
for i, hand in enumerate(self.hands):
|
|
pv = _bj_value(hand)
|
|
if hand_results and i < len(hand_results):
|
|
icon = {"win": "✅", "push": "🤝", "lose": "❌"}[hand_results[i]]
|
|
label = f"{icon} " + S.BJ_UI["hand_n"].format(n=i + 1)
|
|
elif game_over or i < self.hand_idx:
|
|
label = S.BJ_UI["hand_n"].format(n=i + 1)
|
|
elif i == self.hand_idx:
|
|
label = S.BJ_UI["hand_active"].format(n=i + 1)
|
|
else:
|
|
label = S.BJ_UI["hand_pending"].format(n=i + 1)
|
|
bust_str = S.BJ_UI["bust"] if pv > 21 else ""
|
|
desc += f"\n**{label}:** {_bj_hand_str(hand)} `{pv}`{bust_str}"
|
|
|
|
return discord.Embed(title=S.TITLE["blackjack"], description=desc, color=0x5865F2)
|
|
|
|
async def _resolve_all(self, interaction: discord.Interaction) -> None:
|
|
active_games.discard(self.user_id)
|
|
self.clear_items()
|
|
self.stop()
|
|
dv = _bj_value(self.dealer_hand)
|
|
total_payout = 0
|
|
hand_results: list[str] = []
|
|
|
|
for hand, bet in zip(self.hands, self.bets):
|
|
pv = _bj_value(hand)
|
|
if pv > 21:
|
|
hand_results.append("lose")
|
|
elif dv > 21 or pv > dv:
|
|
hand_results.append("win")
|
|
total_payout += bet * 2
|
|
elif pv == dv:
|
|
hand_results.append("push")
|
|
total_payout += bet
|
|
else:
|
|
hand_results.append("lose")
|
|
|
|
total_invested = sum(self.bets)
|
|
res = await economy.do_blackjack_payout(self.user_id, total_payout, total_invested)
|
|
net = total_payout - total_invested
|
|
result_str = (
|
|
f"+{coin(total_payout)}"
|
|
if net > 0
|
|
else (S.BJ["push_result"] if net == 0 else f"-{coin(total_invested)}")
|
|
)
|
|
|
|
if len(self.hands) == 1:
|
|
r = hand_results[0]
|
|
doubled = 0 in self._doubled_hands
|
|
if r == "win":
|
|
title_key, color = ("blackjack_dwin" if doubled else "blackjack_win"), 0x57F287
|
|
elif r == "push":
|
|
title_key, color = "blackjack_push", 0x99AAB5
|
|
else:
|
|
pv = _bj_value(self.hands[0])
|
|
if pv > 21:
|
|
title_key = "blackjack_dbust" if doubled else "blackjack_bust"
|
|
else:
|
|
title_key = "blackjack_lose"
|
|
color = 0xED4245
|
|
else:
|
|
if net > 0:
|
|
title_key, color = "blackjack_win", 0x57F287
|
|
elif net == 0:
|
|
title_key, color = "blackjack_push", 0x99AAB5
|
|
else:
|
|
title_key, color = "blackjack_lose", 0xED4245
|
|
|
|
embed = self._cur_embed(game_over=True, hand_results=hand_results)
|
|
embed.title = S.TITLE[title_key]
|
|
embed.color = color
|
|
embed.add_field(
|
|
name=S.BJ["result_field"],
|
|
value=result_str + S.BJ_UI["balance_line"].format(balance=coin(res["balance"])),
|
|
inline=False,
|
|
)
|
|
await self.message.edit(embed=embed, view=self)
|
|
if total_payout > total_invested:
|
|
asyncio.create_task(award_exp(interaction, economy.gamble_exp(total_invested)))
|
|
|
|
async def _do_dealer_reveal(self, interaction: discord.Interaction) -> None:
|
|
await self.message.edit(embed=self._cur_embed(game_over=True), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
while _bj_value(self.dealer_hand) < 17:
|
|
self.dealer_hand.append(self.deck.pop())
|
|
await self.message.edit(embed=self._cur_embed(game_over=True), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
await self._resolve_all(interaction)
|
|
|
|
async def _advance_or_finish(self, interaction: discord.Interaction) -> None:
|
|
self.hand_idx += 1
|
|
if self.hand_idx < len(self.hands):
|
|
self._refresh_buttons()
|
|
await self.message.edit(embed=self._cur_embed(), view=self)
|
|
else:
|
|
self.hand_idx = len(self.hands) - 1
|
|
await self._do_dealer_reveal(interaction)
|
|
|
|
async def _hit(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
|
|
await interaction.response.defer()
|
|
self._cur_hand.append(self.deck.pop())
|
|
val = _bj_value(self._cur_hand)
|
|
if val > 21:
|
|
await self.message.edit(embed=self._cur_embed(), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
if len(self.hands) > 1:
|
|
await self._advance_or_finish(interaction)
|
|
else:
|
|
await self._resolve_all(interaction)
|
|
elif val == 21:
|
|
await self.message.edit(embed=self._cur_embed(), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY * 0.5)
|
|
if len(self.hands) > 1:
|
|
await self._advance_or_finish(interaction)
|
|
else:
|
|
await self._do_dealer_reveal(interaction)
|
|
else:
|
|
self._refresh_buttons()
|
|
await self.message.edit(embed=self._cur_embed(), view=self)
|
|
|
|
async def _stand(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
|
|
await interaction.response.defer()
|
|
if len(self.hands) > 1:
|
|
await self._advance_or_finish(interaction)
|
|
else:
|
|
await self._do_dealer_reveal(interaction)
|
|
|
|
async def _double(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
|
|
res = await economy.do_blackjack_bet(self.user_id, self.bet)
|
|
if not res["ok"]:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(res.get("balance", 0))), ephemeral=True
|
|
)
|
|
return
|
|
await interaction.response.defer()
|
|
self._doubled_hands.add(0)
|
|
self.bets[0] *= 2
|
|
self._cur_hand.append(self.deck.pop())
|
|
await self.message.edit(embed=self._cur_embed(), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
await self._do_dealer_reveal(interaction)
|
|
|
|
async def _split_hand(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
|
|
res = await economy.do_blackjack_bet(self.user_id, self.bet)
|
|
if not res["ok"]:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(res.get("balance", 0))), ephemeral=True
|
|
)
|
|
return
|
|
await interaction.response.defer()
|
|
card1, card2 = self._cur_hand[0], self._cur_hand[1]
|
|
self._split_aces = card1[0] == "A"
|
|
self.hands = [[card1, self.deck.pop()], [card2, self.deck.pop()]]
|
|
self.bets = [self.bet, self.bet]
|
|
self.hand_idx = 0
|
|
await self.message.edit(embed=self._cur_embed(), view=None)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
if self._split_aces:
|
|
await self._do_dealer_reveal(interaction)
|
|
else:
|
|
self._refresh_buttons()
|
|
await self.message.edit(embed=self._cur_embed(), view=self)
|
|
|
|
async def on_timeout(self) -> None:
|
|
active_games.discard(self.user_id)
|
|
try:
|
|
await economy.do_blackjack_payout(self.user_id, 0, sum(self.bets))
|
|
except Exception:
|
|
pass
|
|
self.clear_items()
|
|
if self.message:
|
|
try:
|
|
await self.message.edit(view=self)
|
|
except discord.HTTPException:
|
|
pass
|
|
|
|
@tree.command(name="blackjack", description=S.CMD["blackjack"])
|
|
@app_commands.describe(panus=S.OPT["blackjack_panus"])
|
|
async def cmd_blackjack(interaction: discord.Interaction, panus: str):
|
|
data = await economy.get_user(interaction.user.id)
|
|
bet, err = parse_amount(panus, data["balance"])
|
|
if err:
|
|
await interaction.response.send_message(err, ephemeral=True)
|
|
return
|
|
if bet is None or bet <= 0:
|
|
await interaction.response.send_message(S.ERR["positive_bet"], ephemeral=True)
|
|
return
|
|
has_360 = "monitor_360" in data.get("items", [])
|
|
if rem := gamble_cd(interaction.user.id, has_360):
|
|
await interaction.response.send_message(
|
|
S.ERR["gamble_cooldown"].format(ts=cd_ts(rem)), ephemeral=True
|
|
)
|
|
return
|
|
if interaction.user.id in active_games:
|
|
await interaction.response.send_message(S.ERR["already_in_game"], ephemeral=True)
|
|
return
|
|
|
|
res = await economy.do_blackjack_bet(interaction.user.id, bet)
|
|
if not res["ok"]:
|
|
if res["reason"] == "banned":
|
|
await interaction.response.send_message(S.MSG_BANNED, ephemeral=True)
|
|
elif res["reason"] == "jailed":
|
|
await interaction.response.send_message(
|
|
S.CD_MSG["jailed"].format(ts=cd_ts(res["remaining"])), ephemeral=True
|
|
)
|
|
else:
|
|
await interaction.response.send_message(
|
|
S.ERR["broke"].format(bal=coin(data["balance"])), ephemeral=True
|
|
)
|
|
return
|
|
active_games.add(interaction.user.id)
|
|
|
|
deck = _bj_deck()
|
|
player_hand: list = []
|
|
dealer_hand: list = []
|
|
|
|
await interaction.response.send_message(
|
|
embed=discord.Embed(title=S.TITLE["blackjack"], description=S.BJ["dealing"], color=0x5865F2)
|
|
)
|
|
msg = await interaction.original_response()
|
|
|
|
for target in ["player", "dealer", "player", "dealer"]:
|
|
if target == "player":
|
|
player_hand.append(deck.pop())
|
|
else:
|
|
dealer_hand.append(deck.pop())
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
await msg.edit(
|
|
embed=_bj_embed(
|
|
player_hand,
|
|
dealer_hand,
|
|
S.TITLE["blackjack"],
|
|
0x5865F2,
|
|
hide_dealer=True,
|
|
)
|
|
)
|
|
|
|
await asyncio.sleep(_BJ_DEAL_DELAY * 0.5)
|
|
|
|
if _bj_is_blackjack(player_hand):
|
|
await msg.edit(
|
|
embed=_bj_embed(
|
|
player_hand,
|
|
dealer_hand,
|
|
S.TITLE["blackjack"],
|
|
0x5865F2,
|
|
hide_dealer=False,
|
|
)
|
|
)
|
|
await asyncio.sleep(_BJ_DEAL_DELAY)
|
|
if _bj_is_blackjack(dealer_hand):
|
|
push_res = await economy.do_blackjack_payout(interaction.user.id, bet, bet)
|
|
embed = _bj_embed(
|
|
player_hand,
|
|
dealer_hand,
|
|
S.TITLE["blackjack_push"],
|
|
0x99AAB5,
|
|
hide_dealer=False,
|
|
result_field=(
|
|
S.BJ["result_field"],
|
|
S.BJ["push_result"] + S.BJ_UI["balance_line"].format(balance=coin(push_res["balance"])),
|
|
),
|
|
)
|
|
else:
|
|
payout = bet + int(bet * 1.5)
|
|
bj_res = await economy.do_blackjack_payout(interaction.user.id, payout, bet)
|
|
embed = _bj_embed(
|
|
player_hand,
|
|
dealer_hand,
|
|
S.TITLE["blackjack_bj"],
|
|
0xF4C430,
|
|
hide_dealer=False,
|
|
result_field=(
|
|
S.BJ["result_field"],
|
|
f"+{coin(payout)}" + S.BJ_UI["balance_line"].format(balance=coin(bj_res["balance"])),
|
|
),
|
|
)
|
|
asyncio.create_task(award_exp(interaction, economy.gamble_exp(bet)))
|
|
active_games.discard(interaction.user.id)
|
|
await msg.edit(embed=embed)
|
|
return
|
|
|
|
view = BlackjackView(interaction.user.id, bet, player_hand, dealer_hand, deck)
|
|
view.message = msg
|
|
await msg.edit(
|
|
embed=_bj_embed(player_hand, dealer_hand, S.TITLE["blackjack"], 0x5865F2, hide_dealer=True),
|
|
view=view,
|
|
)
|