Ruby-Cogs/pokemonduel/buttons.py
2025-04-02 22:57:51 -04:00

377 lines
16 KiB
Python

import discord
import asyncio
from .move import Move
# pylint: disable=C0116,W0613,W0221
BUTTON_TIMEOUT = 60
class DuelAcceptView(discord.ui.View):
"""View to accept a duel."""
def __init__(self, ctx: "commands.Context", opponent: discord.Member):
super().__init__(timeout=BUTTON_TIMEOUT)
self.ctx = ctx
self.confirm = False
self.event = asyncio.Event()
self.opponent = opponent
@discord.ui.button(label="Accept", style=discord.ButtonStyle.green)
async def accept(self, interaction, button):
self.confirm = True
await interaction.response.edit_message(view=None)
self.stop()
@discord.ui.button(label="Reject", style=discord.ButtonStyle.red)
async def reject(self, interaction, button):
await interaction.response.edit_message(view=None)
self.stop()
async def interaction_check(self, interaction):
if interaction.user.id != self.opponent.id:
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
return False
return True
async def on_timeout(self):
await self.message.edit(view=None)
self.stop()
async def on_error(self, interaction, error, item):
await self.ctx.cog.log.error("Exception in a button.", exc_info=error)
class PreviewPromptView(discord.ui.View):
"""Prompts a user to select their lead pokemon when previewing both player's parties."""
def __init__(self, battle):
super().__init__(timeout=BUTTON_TIMEOUT)
self.battle = battle
self.child_views = []
@discord.ui.button(label="Select a lead pokemon", style=discord.ButtonStyle.primary)
async def actions(self, interaction, button):
if interaction.user.id == self.battle.trainer1.id:
trainer = self.battle.trainer1
else:
trainer = self.battle.trainer2
view = LeadView(trainer, self.battle)
self.child_views.append(view)
await interaction.response.send_message(content="Pick a pokemon to lead with:", view=view, ephemeral=True)
async def interaction_check(self, interaction):
if interaction.user.id == self.battle.trainer1.id:
if self.battle.trainer1.event.is_set():
await interaction.response.send_message(content="You have already selected a lead.", ephemeral=True)
return False
return True
if interaction.user.id == self.battle.trainer2.id:
if self.battle.trainer2.event.is_set():
await interaction.response.send_message(content="You have already selected a lead.", ephemeral=True)
return False
return True
return False
async def on_timeout(self):
self.battle.trainer1.event.set()
self.battle.trainer2.event.set()
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
def stop(self):
"""Override to stop child views when this view is stopped."""
for view in self.child_views:
view.stop()
super().stop()
class LeadView(discord.ui.View):
"""Shows the user their pokemon, allowing them to click one to make their lead to."""
def __init__(self, trainer, battle):
super().__init__(timeout=BUTTON_TIMEOUT)
self.trainer = trainer
self.battle = battle
for poke in trainer.party:
self.add_item(LeadButton(poke))
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
class LeadButton(discord.ui.Button):
"""A button that makes the pokemon the user's lead when pressed."""
def __init__(self, poke):
super().__init__(style=discord.ButtonStyle.secondary, label=f"{poke._name} | {poke.hp}hp")
self.poke = poke
async def callback(self, interaction):
content = f"You will lead with {self.poke.name}. Waiting for opponent."
self.view.trainer.switch_poke(self.view.trainer.party.index(self.poke))
self.view.trainer.event.set()
await interaction.response.edit_message(content=content, view=None)
class BattlePromptView(discord.ui.View):
"""Prompts users to select an action for their turn."""
def __init__(self, battle):
super().__init__(timeout=BUTTON_TIMEOUT)
self.battle = battle
self.turn = battle.turn
self.child_views = []
@discord.ui.button(label="View your actions", style=discord.ButtonStyle.primary)
async def actions(self, interaction, button):
if interaction.user.id == self.battle.trainer1.id:
trainer = self.battle.trainer1
opponent = self.battle.trainer2
else:
trainer = self.battle.trainer2
opponent = self.battle.trainer1
view = MoveSelectView(self.battle, trainer, opponent)
self.child_views.append(view)
await interaction.response.send_message(content="Pick an action:", view=view, ephemeral=True)
async def interaction_check(self, interaction):
if self.battle.turn != self.turn:
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
return False
if self.battle.trainer1.is_human() and interaction.user.id == self.battle.trainer1.id:
if self.battle.trainer1.selected_action is not None:
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
return False
return True
if self.battle.trainer2.is_human() and interaction.user.id == self.battle.trainer2.id:
if self.battle.trainer2.selected_action is not None:
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
return False
return True
return False
async def on_timeout(self):
self.battle.trainer1.event.set()
self.battle.trainer2.event.set()
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
def stop(self):
"""Override to stop child views when this view is stopped."""
for view in self.child_views:
view.stop()
super().stop()
class MoveSelectView(discord.ui.View):
"""Prompts the user to pick a move, enter the swap pokes view, or cancel the duel."""
def __init__(self, battle, trainer, opponent):
super().__init__(timeout=BUTTON_TIMEOUT)
self.battle = battle
self.turn = battle.turn
self.trainer = trainer
self.opponent = opponent
self.child_views = []
status_code, movedata = trainer.valid_moves(opponent.current_pokemon)
if status_code == "forced":
trainer.selected_action = movedata
trainer.event.set()
self.add_item(discord.ui.Button(style=discord.ButtonStyle.secondary, label="You were forced to play:", disabled=True))
self.add_item(MoveButton(movedata, disabled=True))
return
swapdata = trainer.valid_swaps(opponent.current_pokemon, battle)
if status_code == "struggle":
self.add_item(MoveButton(Move.struggle()))
self.add_item(SwapRequestButton(disabled=not swapdata))
self.add_item(ForfeitButton(row=0))
return
for idx, move in enumerate(trainer.current_pokemon.moves):
self.add_item(MoveButton(move, disabled=idx not in movedata, row=idx // 2))
self.add_item(SwapRequestButton(disabled=not swapdata))
self.add_item(ForfeitButton())
if (
trainer.current_pokemon is not None
and trainer.current_pokemon.mega_type_ids is not None
and not trainer.has_mega_evolved
):
self.add_item(MegaEvolveButton())
async def interaction_check(self, interaction):
if self.battle.turn != self.turn:
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
return False
# Should never be hit, but just in case :P
if interaction.user.id != self.trainer.id:
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
return False
if self.trainer.selected_action is not None:
await interaction.response.send_message(content="You have already selected an action.", ephemeral=True)
return False
return True
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
def stop(self):
"""Override to stop child views when this view is stopped."""
for view in self.child_views:
view.stop()
super().stop()
class MoveButton(discord.ui.Button):
"""A button that represents a selection of a specific move."""
def __init__(self, move, *, disabled=False, row=0):
label = f"{move.pretty_name}"
if move.id != 165:
label += f" | {move.pp}pp"
super().__init__(style=discord.ButtonStyle.secondary, label=label, disabled=disabled, row=row)
self.move = move
async def callback(self, interaction):
self.view.trainer.selected_action = self.move
self.view.trainer.event.set()
await interaction.response.edit_message(content=f"You picked {self.move.pretty_name}. Waiting for opponent.", view=None)
class SwapRequestButton(discord.ui.Button):
"""A button that represents a request to swap pokemon."""
def __init__(self, *, disabled=False):
super().__init__(style=discord.ButtonStyle.primary, label="Swap pokemon", disabled=disabled)
async def callback(self, interaction):
view = SwapView(self.view.trainer, self.view.opponent, self.view.battle, set_move=True)
self.view.child_views.append(view)
await interaction.response.edit_message(content="Pick a pokemon:", view=view)
class DuelForfeitView(discord.ui.View):
"""View to forfeit a duel."""
def __init__(self, trainer):
super().__init__(timeout=BUTTON_TIMEOUT)
self.trainer = trainer
self.confirm = False
@discord.ui.button(label="Forfeit", style=discord.ButtonStyle.red)
async def actuallyforfeit(self, interaction, button):
await interaction.response.edit_message(content="Forfeited.", view=None)
self.confirm = True
self.stop()
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.secondary)
async def cancel(self, interaction, button):
await interaction.response.edit_message(content="Not forfeiting.", view=None)
self.stop()
async def interaction_check(self, interaction):
if interaction.user.id != self.trainer.id:
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
return False
return True
async def on_error(self, interaction, error, item):
await self.trainer.party[0].held_item.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
class ForfeitButton(discord.ui.Button):
"""A button that forfeits the game when pressed."""
def __init__(self, *, row=1):
super().__init__(style=discord.ButtonStyle.danger, label="Forfeit duel", row=row)
async def callback(self, interaction):
view = DuelForfeitView(self.view.trainer)
self.view.child_views.append(view)
await interaction.response.send_message(content="Are you sure you want to forfeit?", view=view, ephemeral=True)
await view.wait()
if view.confirm:
self.view.trainer.event.set()
class SwapPromptView(discord.ui.View):
"""Prompts the trainer to view their pokemon."""
def __init__(self, trainer, opponent, battle, *, mid_turn=False):
super().__init__(timeout=BUTTON_TIMEOUT)
self.trainer = trainer
self.opponent = opponent
self.battle = battle
self.turn = battle.turn
self.mid_turn = mid_turn
self.child_views = []
@discord.ui.button(label="View your pokemon", style=discord.ButtonStyle.primary)
async def swap(self, interaction, button):
view = SwapView(self.trainer, self.opponent, self.battle, mid_turn=self.mid_turn)
self.child_views.append(view)
await interaction.response.send_message(
content="Pick a pokemon to swap to:",
view=view,
ephemeral=True
)
async def interaction_check(self, interaction):
if self.battle.turn != self.turn:
await interaction.response.send_message(content="This button has expired.", ephemeral=True)
return False
if interaction.user.id != self.trainer.id:
await interaction.response.send_message(content="You are not allowed to interact with this button.", ephemeral=True)
return False
return True
async def on_timeout(self):
self.battle.trainer1.event.set()
self.battle.trainer2.event.set()
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
def stop(self):
"""Override to stop child views when this view is stopped."""
for view in self.child_views:
view.stop()
super().stop()
class SwapView(discord.ui.View):
"""Shows the user their pokemon, allowing them to click one to swap to."""
def __init__(self, trainer, opponent, battle, *, set_move=False, mid_turn=False):
super().__init__(timeout=BUTTON_TIMEOUT)
self.trainer = trainer
self.opponent = opponent
self.battle = battle
self.set_move = set_move
self.mid_turn = mid_turn
swapdata = trainer.valid_swaps(opponent.current_pokemon, battle, check_trap=set_move)
for idx, poke in enumerate(trainer.party):
self.add_item(SwapButton(poke, disabled=idx not in swapdata))
async def on_error(self, interaction, error, item):
self.battle.ctx.cog.log.error("Exception in a button.", exc_info=error)
class SwapButton(discord.ui.Button):
"""A button that swaps to that pokemon when pressed."""
def __init__(self, poke, *, disabled=False):
super().__init__(style=discord.ButtonStyle.secondary, label=f"{poke._name} | {poke.hp}hp", disabled=disabled)
self.poke = poke
async def callback(self, interaction):
content = f"You picked {self.poke.name}."
if self.view.set_move:
self.view.trainer.selected_action = self.view.trainer.party.index(self.poke)
content += " Waiting for opponent."
else:
self.view.trainer.switch_poke(self.view.trainer.party.index(self.poke), mid_turn=self.view.mid_turn)
self.view.trainer.event.set()
await interaction.response.edit_message(content=content, view=None)
class MegaEvolveButton(discord.ui.Button):
"""A button that toggles whether the trainer's pokemon should mega evolve this turn."""
def __init__(self):
super().__init__(style=discord.ButtonStyle.gray, label="Mega Evolve", row=0)
def get_color(self):
return [discord.ButtonStyle.gray, discord.ButtonStyle.green][self.view.trainer.current_pokemon.should_mega_evolve]
async def callback(self, interaction):
self.view.trainer.current_pokemon.should_mega_evolve = not self.view.trainer.current_pokemon.should_mega_evolve
self.style = self.get_color()
await interaction.response.edit_message(view=self.view)