From 09eff190dba21a1baeac76ec434375385b0d647b Mon Sep 17 00:00:00 2001 From: Brandon Ly Date: Mon, 12 Apr 2021 05:21:14 -0400 Subject: [PATCH] refactor: V1 Refactor (#18) * feat: setup class modules * feat: create base map class * feat: create base game class * feat: create base match class * feat: string representation of map based on state * feat: update names for classes * create: smash stage class * docs: docstring for game constructor * feat: setup smash game and player classes * feat: override equals method for player * feat: add bot context for veto to match * feat: create stagelist object * feat: move map pool to child classes * feat: veto method for stagelist * refactor: move stagelist methods to game class * refactor: updated smash stages for new model * fix: spacing of game title * refactor: move stage veto back to stagelist * docs: settings page documentation * fix: removed bot stuff from match Match.py should only be backend api, no need to put discord stuff in it. * docs(smash): add docstrings and comments to all * refactor(bot): cleared old files * refactor(coinflip): remove coinflip animation Removed due to avoid clogging up the discord rate limit * feat(smash): generates blank games on match creation * refactor(smash): remove match base class * fix(smash): seed selection through match * refactor(smash): moved embed generator to match class * refactor: removed base classes * feat: rewrote all smash vetos * feat: use specific channel for match creation do this instead of restricting channels, its a lot easier * feat: coinflip supports two player pings * feat(val): valorant map class * feat(val): game and map pool object * feat(val): match object started * fix(smash): players not swapping on p2 win * feat(val): bo3 veto * feat(val): bo5 veto * refactor: split veto command into multiple * refactor: removed old un-needed files * docs: update README.md * docs: fix README.md links --- README.md | 152 ++++++++++- bot.py | 493 +++++++++++++----------------------- settings.py | 104 ++++++-- {veto => smash}/__init__.py | 0 smash/game.py | 73 ++++++ smash/match.py | 193 ++++++++++++++ smash/player.py | 32 +++ smash/stage.py | 51 ++++ smash/stagelist.py | 93 +++++++ utils/checks.py | 51 ++++ utils/embeds.py | 88 ++----- utils/message_generators.py | 77 ------ utils/player_utils.py | 60 ++--- valorant/__init__.py | 0 valorant/game.py | 76 ++++++ valorant/map.py | 46 ++++ valorant/map_pool.py | 34 +++ valorant/match.py | 302 ++++++++++++++++++++++ veto/smash.py | 237 ----------------- veto/valorant.py | 320 ----------------------- 20 files changed, 1408 insertions(+), 1074 deletions(-) rename {veto => smash}/__init__.py (100%) create mode 100644 smash/game.py create mode 100644 smash/match.py create mode 100644 smash/player.py create mode 100644 smash/stage.py create mode 100644 smash/stagelist.py create mode 100644 utils/checks.py delete mode 100644 utils/message_generators.py create mode 100644 valorant/__init__.py create mode 100644 valorant/game.py create mode 100644 valorant/map.py create mode 100644 valorant/map_pool.py create mode 100644 valorant/match.py delete mode 100644 veto/smash.py delete mode 100644 veto/valorant.py diff --git a/README.md b/README.md index 6a495c5..caf809e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,149 @@ -# Vaughan Esports Veto Bot + +
+

+ + Logo + -Currently designed for smash stage veto according to Vaughan Esports' SSBU -rulebook. +

Vaughan Esports Tournament Bot

-REFACTOR In progress, docs for getting started and hosting it yourself will be -updated once the refactor is complete. Check the refactor branch for updates. \ No newline at end of file +

+ Home of Vaughan Esports' very own Discord Bot used to run our events! +
+
+ View Demo + · + Report Bug + · + Request Feature +

+

+ + + + +
+ Table of Contents +
    +
  1. + About The Project + +
  2. +
  3. + Getting Started + +
  4. +
  5. Usage
  6. +
  7. Roadmap
  8. +
  9. License
  10. +
  11. Contact
  12. +
+
+ + + + + +## About The Project + +Tournament rules and rulesets are long can get pretty annoying to read and keep +up with, especially with our regular monthly events. Having a dedicated bot to +handle the processes for veto'ing and selecting maps helps keeps players +engaged and following the rules! + +The bot walks all players through the veto process and has a multitude of +commands to help players and teams setup their matches. + +### Built With + +* [Discord.py](https://github.com/Rapptz/discord.py) +* [Rich](https://github.com/willmcgugan/rich) + + + +## Getting Started + +Follow the steps below if you'd like to host your own instance our the bot. + +### Prerequisites + +- Python 3.8 or higher (May work on 3.6 and 3.7 but untested) +- Discord Developer Account + +### Installation + +1. Get your Discord bot API Key ( + Instructions [here](https://discordpy.readthedocs.io/en/latest/discord.html)) +2. Clone the repo + ```sh + git clone https://github.com/Vaughan-Esports/VE-Tourney-Bot.git + ``` +3. Install Python packages + ```sh + pip install -r requirements.txt + ``` +4. Create a file called `.env` amd put your API key in like so: + ```dotenv + BOT_TOKEN=YOUR_TOKEN + ``` + +### Configuration + +Everything is configurable from `settings.py` + + + + +## Usage + +Its main commands are + +- `ve!coinflip @opponent` + - Flips a coin +- `ve!match @opponent` + - Starts a private chat between you and your opponent +- `ve!smash {best-of} @opponent` + - Starts a smash veto with your opponent (Best of can be 3 or 5) +- `ve!val {best-of} @opponent` + - Starts a VALORANT veto with your opponent (Best of can be 1, 3, or 5) +- `ve!close` + - Closes a private chat between you and your opponent +- `ve!purge` + - Purges all closed channels + +Commands can also be run with pings to both players so that TO's may manually +set up commands for players (e.g `ve!match @player1 @player2`) + + + + + +## Roadmap + +- osu! Veto +- League of Legends ARAM Roll +- Logging +- MongoDB Support (V2) +- Dynamic Veto (Easy config of veto from a single file) (V3) + + + +## License + +Distributed under the MIT License. See `LICENSE` for more information. + + + + + +## Contact + +Your Name - [@brndnly](https://twitter.com/brndnly) - email@example.com + +Project +Link: [https://github.com/Vaughan-Esports/VE-Tourney-Bot](https://github.com/Vaughan-Esports/VE-Tourney-Bot) diff --git a/bot.py b/bot.py index 68065cf..88c4feb 100644 --- a/bot.py +++ b/bot.py @@ -5,298 +5,132 @@ from discord.ext import commands from discord.ext.commands import MissingPermissions +from settings import active_channels_id, inactive_channels_id +from settings import guild_id, TO_role_id, match_creation_channel_id +from settings import prefix, description, tourney_name +from settings import smash_example, valorant_example +from smash.match import Match as SmashMatch +from smash.player import Player from utils import embeds, player_utils -from utils.message_generators import * -from veto import smash, valorant +from valorant.match import Match as ValMatch intents = discord.Intents.default() -allowed_mentions = discord.AllowedMentions(everyone=False, users=True, +allowed_mentions = discord.AllowedMentions(everyone=False, + users=True, roles=True) -bot = discord.ext.commands.Bot(command_prefix=prefix, intents=intents, - description=description, case_insensitive=True, +bot = discord.ext.commands.Bot(command_prefix=prefix, + intents=intents, + description=description, + case_insensitive=True, allowed_mentions=allowed_mentions) + BOT_TOKEN = os.getenv('BOT_TOKEN') -@bot.command() -async def veto(ctx, game=None, series_length=None, opponent=None): +@bot.command(aliases=['ssbu']) +async def smash(ctx, series_length=None, opponent=None): """ - Starts a veto lobby with your opponent + Starts a Smash Ult. veto with your opponent """ - # let user know if they're missing a parameter - if game is None or series_length is None or opponent is None: - text = "Initiate a veto with `ve!veto {game} {series_length} @{" \ - "opponent}` " - await ctx.send(embed=await embeds.missing_param_error(text)) - elif ctx.channel.id in restricted_channels_ids: + # guild and category objects + guild = bot.get_guild(guild_id) + active_category = guild.get_channel(active_channels_id) + + # check if a valid place to start matches + if ctx.channel not in active_category.text_channels or \ + ctx.channel.id == match_creation_channel_id: text = "You can't do that here! Invoke a match chat first with " \ "`ve!match {@opponent}`" await ctx.send(embed=await embeds.missing_permission_error(text)) - # smash best of 3 veto - elif game.lower() == 'smash' and series_length.lower() == 'bo3': - # starting/loading embed - main_msg = await ctx.send(embed=await embeds.starting()) + # let user know if they're missing a parameter + elif series_length is None or opponent is None: + text = "Initiate a veto with `ve!veto {game} " \ + "{best-of (3 or 5)} @{opponent}` " + await ctx.send(embed=await embeds.missing_param_error(text)) - # player + # SMASH VETO + elif series_length == '3' or series_length == '5': + # get players player1, player2 = await player_utils.get_players(ctx) - # send first veto embed - embed = await embeds.smash_veto(player1, player2, 3) - await main_msg.edit(embed=embed) + # initialize game + match = SmashMatch(Player(player1), + Player(player2), + int(series_length)) # run veto with catch statement in case of time out try: - # HIGHER SEED SELECTION - await ctx.send(f"{newline}Player 1 (the higher seed) say `me`") - - # checks which user says me - def playerCheck(message): - return message.content.lower() == 'me' \ - and message.channel == ctx.channel - - msg = await bot.wait_for('message', check=playerCheck, timeout=300) - - # changes player order if player 2 said they were first seed - if msg.author == player2: - player1 = player2 - player2 = ctx.author - - # notifies of veto starts - await ctx.send(f"Starting veto with {player1.mention} as " - f"**Player 1** and {player2.mention} " - f"as **Player 2** in 5 seconds...") - - # delete all messages and begin veto after 5 seconds + # run seed selection + await player_utils.seed_selection(ctx, bot, match) await asyncio.sleep(5) - await ctx.channel.purge(after=main_msg) - - # FIRST GAME VETO PROCESS - # cross out all counterpick stages as they are not valid for - # first veto - embed.set_field_at(2, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # run initial game veto - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.initial(ctx, bot, main_msg, player1, player2, - embed) - - # SECOND GAME VETO PROCESS - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, - 2, 4, 5) - - # THIRD GAME VETO PROCESS - # exits if a DSR stage list is longer than 1 - if len(p1_dsr_stage) > 1 or len(p2_dsr_stage) > 1: - # reset messages - await ctx.channel.purge(after=main_msg) - - # cross out all of game 3 - embed.set_field_at(6, name='~~` Game ' - '3 `~~', - value='~~**Winner:**~~', inline=False) - embed.set_field_at(7, name="Starter Stages", - value=starters_message(starters)) - embed.set_field_at(8, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # final message - await ctx.send("GG!") - return - - # runs non initial veto with player 1's DSR start on removed stages - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, 3, 7, 8) - # final message - await ctx.send("GG!") + + # run veto's + while match.winner is None: + await match.veto(ctx, bot) # if the veto times out except asyncio.TimeoutError: - # purge all messages after original message - await ctx.channel.purge(after=main_msg) - # get error embed and edit original message - error_embed = await embeds.timeout_error() - await main_msg.edit(embed=error_embed) - - # elif smash bo5 - # FIXME LATER - LMAO I COPY PASTED THIS SHIT FROM BO3, DEF NOT OPTIMIZED - elif game.lower() == 'smash' and series_length.lower() == 'bo5': - # starting/loading embed - main_msg = await ctx.send(embed=await embeds.starting()) - - # player objects - player1, player2 = await player_utils.get_players(ctx) - - # send first veto embed - embed = await embeds.smash_veto(player1, player2, 5) - await main_msg.edit(embed=embed) - - # run veto with catch statement in case of time out - try: - # HIGHER SEED SELECTION - await ctx.send(f"{newline}Player 1 (the higher seed) say `me`") + await ctx.send(embed=await embeds.timeout_error()) - # checks which user says me - def playerCheck(message): - return message.content.lower() == 'me' \ - and message.channel == ctx.channel - - msg = await bot.wait_for('message', check=playerCheck, timeout=300) - - # changes player order if player 2 said they were first seed - if msg.author == player2: - player1 = player2 - player2 = ctx.author - - # notifies of veto starts - await ctx.send(f"Starting veto with {player1.mention} as " - f"**Player 1** and {player2.mention} " - f"as **Player 2** in 5 seconds...") + else: + text = f"Matches must either be a best of 3 or 5.\n\n" \ + f"Example: {smash_example}" + await ctx.send(embed=await embeds.invalid_param_error(text)) - # delete all messages and begin veto after 5 seconds - await asyncio.sleep(5) - await ctx.channel.purge(after=main_msg) - - # FIRST GAME VETO PROCESS - # cross out all counterpick stages as they are not valid for - # first veto - embed.set_field_at(2, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # run initial game veto - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.initial(ctx, bot, main_msg, player1, player2, - embed) - - # SECOND GAME VETO PROCESS - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, - 2, 4, 5) - - # THIRD GAME VETO PROCESS - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, - 3, 7, 8) - - # FOURTH GAME VETO PROCESS - # exits if a DSR stage list is longer than 2 - if len(p1_dsr_stage) > 2 or len(p2_dsr_stage) > 2: - # reset messages - await ctx.channel.purge(after=main_msg) - - # cross out all of game 4 - embed.set_field_at(9, name='~~` Game ' - '4 `~~', - value='~~**Winner:**~~', inline=False) - embed.set_field_at(10, name="Starter Stages", - value=starters_message(starters)) - embed.set_field_at(11, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # cross out all of game 5 - embed.set_field_at(12, name='~~` ' - 'Game 5 ' - ' `~~', - value='~~**Winner:**~~', inline=False) - embed.set_field_at(13, name="Starter Stages", - value=starters_message(starters)) - embed.set_field_at(14, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # send final message and return - await ctx.send('GG!') - return - - # run veto for game 4 - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, - 4, 10, 11) - - # FIFTH GAME VETO PROCESS - # exits if a DSR stage list is longer than 2 - if len(p1_dsr_stage) > 2 or len(p2_dsr_stage) > 2: - # reset messages - await ctx.channel.purge(after=main_msg) - - # cross out all of game 5 - embed.set_field_at(12, name='~~` ' - 'Game 5 ' - ' `~~', - value='~~**Winner:**~~', inline=False) - embed.set_field_at(13, name="Starter Stages", - value=starters_message(starters)) - embed.set_field_at(14, name="Counterpick Stages", - value=counters_message(counters)) - await main_msg.edit(embed=embed) - - # send final message and return - await ctx.send('GG!') - return - - # run veto for game 5 - main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await smash.nonInitial(ctx, bot, main_msg, player1, player2, - p1_dsr_stage, p2_dsr_stage, embed, - 5, 13, 14) - - # final message - await ctx.send('GG!') - # if the veto times out - except asyncio.TimeoutError: - # purge all messages after original message - await ctx.channel.purge(after=main_msg) +@bot.command(aliases=['valorant']) +async def val(ctx, series_length=None, opponent=None): + """ + Runs a VALORANT veto with your opponent + """ + # guild and category objects + guild = bot.get_guild(guild_id) + active_category = guild.get_channel(active_channels_id) - # get error embed and edit original message - error_embed = await embeds.timeout_error() - await main_msg.edit(embed=error_embed) + # check if a valid place to start matches + if ctx.channel not in active_category.text_channels or \ + ctx.channel.id == match_creation_channel_id: + text = "You can't do that here! Invoke a match chat first with " \ + "`ve!match {@opponent}`" + await ctx.send(embed=await embeds.missing_permission_error(text)) - elif 'val' in game.lower(): - main_msg = await ctx.send(embed=await embeds.starting()) + # let user know if they're missing a parameter + elif series_length is None or opponent is None: + text = "Initiate a veto with `ve!veto {game} " \ + "{best-of (3 or 5)} @{opponent}` " + await ctx.send(embed=await embeds.missing_param_error(text)) - # player objects + elif series_length == '1' or series_length == '3' or series_length == '5': + # get players player1, player2 = await player_utils.get_players(ctx) - games = int(series_length[2]) + # coinflip to determine seeding + player1, player2 = await player_utils.coinflip(ctx, player1, player2) + # initialize game + match = ValMatch(player1, player2, int(series_length)) - # send first veto embed - embed = await embeds.valorant_veto(player1, player2, games) - await main_msg.edit(embed=embed) + # start delay + await ctx.send(f"Starting veto with {player1.mention} as " + f"**Captain 1** and {player2.mention} " + f"as **Captain 2** in 5 seconds...") + await asyncio.sleep(5) # run veto try: - # best of 1 veto - if games == 1: - embed, main_msg = await valorant.bo1(ctx, bot, main_msg, - player1, player2, embed) - await ctx.send('GG!') - # best of 3 veto - elif games == 3: - embed, main_msg = await valorant.bo3(ctx, bot, main_msg, - player1, player2, embed) - await ctx.send('GG!') + await match.veto(ctx, bot) # if the veto times out except asyncio.TimeoutError: - # purge all messages after original message - await ctx.channel.purge(after=main_msg) - # get error embed and edit original message - error_embed = await embeds.timeout_error() - await main_msg.edit(embed=error_embed) + await ctx.send(embed=await embeds.timeout_error()) + + else: + text = f"Matches must either be a best of 1, 3, or 5.\n\n" \ + f"Example: {valorant_example}" + await ctx.send(embed=await embeds.invalid_param_error(text)) @bot.command() @@ -304,68 +138,83 @@ async def match(ctx, opponent=None): """ Creates a private text channel between you and your opponent(s) """ + # checks that this it he correct channel to use + if ctx.channel.id != match_creation_channel_id: + # get match creation channel object + match_channel = bot.get_channel(match_creation_channel_id) + + # tell user they have to do it over in match creation channel + text = f"You can't do that here! Invoke a match chat first over at " \ + f"{match_channel.mention}" + await ctx.send(embed=await embeds.missing_permission_error(text)) + # let user know if they didn't enter an opponent - if opponent is None: + elif opponent is None: text = "Initiate a match chat with `ve!match @{opponent}`" await ctx.send(embed=await embeds.missing_param_error(text)) return - # run command if they have proper arguments - # send initial starting message - main_msg = await ctx.send(embed=await embeds.starting()) + else: + # run command if they have proper arguments + # send initial starting message + main_msg = await ctx.send(embed=await embeds.starting()) - # player objects - player1, player2 = await player_utils.get_players(ctx) + # player objects + player1, player2 = await player_utils.get_players(ctx) - # guild and category objects - guild = bot.get_guild(guild_id) - active_category = guild.get_channel(active_channels_id) + # guild and category objects + guild = bot.get_guild(guild_id) + active_category = guild.get_channel(active_channels_id) - # game coordinator role - game_coordinator = guild.get_role(TO_role_id) - - # overwrites for the match channel - overwrites = { - player1: discord.PermissionOverwrite(add_reactions=True, - read_messages=True, - send_messages=True, - external_emojis=True, - attach_files=True, - embed_links=True), - - player2: discord.PermissionOverwrite(add_reactions=True, - read_messages=True, - send_messages=True, - external_emojis=True, - attach_files=True, - embed_links=True), - - game_coordinator: discord.PermissionOverwrite(add_reactions=True, - read_messages=True, - send_messages=True, - external_emojis=True, - attach_files=True, - embed_links=True), - guild.default_role: discord.PermissionOverwrite(send_messages=False) - } - - # create channel - name = f"{player1.name} vs {player2.name}" - topic = f"{tourney_name}: {player1.name} vs {player2.name}" - reason = "User invoked tourney match channel" - match_channel = await guild.create_text_channel(name, - category=active_category, - topic=topic, - reason=reason, - overwrites=overwrites) - - # send message linking to channel - await main_msg.edit(embed=await embeds.match_started(match_channel)) - - # send instructions into the channel - await match_channel.send("Once both sides are ready, invoke the veto " - "process with `ve!veto {game} " - "{series_length} {@opponent}`") + # game coordinator role + game_coordinator = guild.get_role(TO_role_id) + + # overwrites for the match channel + overwrites = { + player1: discord.PermissionOverwrite(add_reactions=True, + read_messages=True, + send_messages=True, + external_emojis=True, + attach_files=True, + embed_links=True), + + player2: discord.PermissionOverwrite(add_reactions=True, + read_messages=True, + send_messages=True, + external_emojis=True, + attach_files=True, + embed_links=True), + + game_coordinator: discord.PermissionOverwrite(add_reactions=True, + read_messages=True, + send_messages=True, + external_emojis=True, + attach_files=True, + embed_links=True), + guild.default_role: discord.PermissionOverwrite( + send_messages=False, + read_messages=True) + } + + # create channel + name = f"{player1.name} vs {player2.name}" + topic = f"{tourney_name}: {player1.name} vs {player2.name}" + reason = "User invoked tourney match channel" + match_channel = await \ + guild.create_text_channel(name, + category=active_category, + topic=topic, + reason=reason, + overwrites=overwrites) + + # send message linking to channel + await main_msg.edit(embed=await embeds.match_started(match_channel)) + + # send instructions into the channel + await match_channel.send("Once both sides are ready, invoke the veto " + "process with `ve!veto {game}" + "{best-of (3 or 5)} {@opponent}`. " + "For example: `ve!veto smash 3 @Harry`") @bot.command() @@ -373,38 +222,56 @@ async def close(ctx): """ Moves the channel to the inactive category """ - # message placeholder - main_msg = await ctx.send(embed=await embeds.starting()) + # guild and category objects + guild = bot.get_guild(guild_id) + active_category = guild.get_channel(active_channels_id) - if ctx.channel.id in restricted_channels_ids: + # let user know the channel isn't an active match channel + if ctx.channel not in active_category.text_channels: text = "You can't do that here! You can only close channels in the " \ "active matches category." - await main_msg.edit(embed=await embeds.missing_permission_error(text)) + await ctx.send(embed=await embeds.missing_permission_error(text)) + + # let user know they can't close this channel + elif ctx.channel.id == match_creation_channel_id: + text = "You can't close this channel! It's not a match channel :smile:" + await ctx.send(embed=await embeds.missing_permission_error(text)) else: + # notifies users of archived channel + await ctx.send(embed=await embeds.match_archived()) + # guild and category objects guild = bot.get_guild(guild_id) inactive_category = guild.get_channel(inactive_channels_id) + # game coordinator role + game_coordinator = guild.get_role(TO_role_id) # overwrites for the match channel overwrites = { guild.default_role: discord.PermissionOverwrite( - send_messages=False) + send_messages=False, + read_messages=True), + game_coordinator: discord.PermissionOverwrite(add_reactions=True, + read_messages=True, + send_messages=True, + external_emojis=True, + attach_files=True, + embed_links=True) } await ctx.channel.edit(category=inactive_category, overwrites=overwrites) - # notifies users of archived channel - await main_msg.edit(embed=await embeds.match_archived()) -@bot.command() +@bot.command(aliases=['flip']) async def coinflip(ctx, opponent=None): if opponent is None: text = "You need to specify an opponent!" await ctx.send(embed=await embeds.missing_param_error(text)) else: - await player_utils.coinflip(ctx, ctx.author, ctx.message.mentions[0]) + player1, player2 = await player_utils.get_players(ctx) + await player_utils.coinflip(ctx, player1, player2) @bot.command() diff --git a/settings.py b/settings.py index 589aeb1..af9976b 100644 --- a/settings.py +++ b/settings.py @@ -1,32 +1,98 @@ # embed settings tourney_name = "December 2020 Smash Ult. Monthly" -footer_note = "Ping Brandon for help." +footer_note = "© Brandon Ly 2021" footer_icon = "https://vaughanesports.org/assets/Vaughan%20Esports%20Logo.png" -rulebook_url = "https://vaughanesports.org/rules" -newline = "_ _\n" +rulebook_url = "https://vaughanesports.org/rules" # url to rulebook +newline = "_ _\n" # dont touch me + +# one of these should be on at least +cross_map_on_veto = True # crosses out the stage when veto +hide_map_on_veto = True # covers stage in spoiler tag when veto # bot settings prefix = "ve!" description = "Tournament Bot for Vaughan Esports" # smash stages -starters = ["Battlefield", "Small Battlefield", "Pokemon Stadium 2", - "Town And City", "Final Destination"] -counters = ["Kalos Pokemon League", "Lylat Cruise", "Yoshi's Story", - "Smashville"] +# abbreviations: https://www.ssbwiki.com/List_of_abbreviations#Stages +stages = [ + {'name': 'Battlefield', + 'starter': True, + 'aliases': [ + 'bf' + ]}, + {'name': 'Small Battlefield', + 'starter': True, + 'aliases': [ + 'sbf', + 'small bf' + ]}, + {'name': 'Pokemon Stadium 2', + 'starter': True, + 'aliases': [ + 'ps2' + ]}, + {'name': 'Town And City', + 'starter': True, + 'aliases': [ + 'tan', + 'town', + 't&c', + 'city', + 'tac', + 'tnc', + 'tc' + ]}, + {'name': 'Final Destination', + 'starter': True, + 'aliases': [ + 'fd', + 'final d' + ]}, + {'name': 'Kalos Pokemon League', + 'starter': False, + 'aliases': [ + 'kalos', + 'kpl' + ]}, + {'name': 'Lylat Cruise', + 'starter': False, + 'aliases': [ + 'lylat', + 'lc' + ]}, + {'name': 'Yoshi\'s Story', + 'starter': False, + 'aliases': [ + 'ys', + 'yoshis', + 'yoshi\'s', + 'yoshi' + ]}, + {'name': 'Smashville', + 'starter': False, + 'aliases': [ + 'sv', + 'smashv', + 'ville' + ]} +] # valorant maps -maps = ["Bind", "Split", "Haven", "Ascent", "Icebox"] +val_maps = ["Bind", "Split", "Haven", "Ascent", "Icebox"] # tourney categories -active_channels_id = 777443551478153216 -inactive_channels_id = 777443021654196264 -guild_id = 688141732507942918 -TO_role_id = 709560702225874976 - -# last two are test channel -restricted_channels_ids = [688534418939445315, 712960670735400991, - 778693693569368084, 703347224985337897, - 771131053519011860, 697935838096654347, - 688943624553365568, 688141732507942926, - 773550892263014400, 775542351640002600] +active_channels_id = 777421991031734292 +inactive_channels_id = 777422048943013908 +guild_id = 762532363695292455 +TO_role_id = 822710142985830410 + +# channel where matches can be started +match_creation_channel_id = 828867315256000513 + +# timeout settings (in seconds) +veto_timeout = 1800 + +# example commands (discord formatting) +smash_example = '`ve!smash 3 @Brandon`' +valorant_example = '`ve!val 1 @Brandon`' diff --git a/veto/__init__.py b/smash/__init__.py similarity index 100% rename from veto/__init__.py rename to smash/__init__.py diff --git a/smash/game.py b/smash/game.py new file mode 100644 index 0000000..e4a6a3d --- /dev/null +++ b/smash/game.py @@ -0,0 +1,73 @@ +from smash.stagelist import StageList + + +class Game: + """ + Represents a Smash game + """ + + def __init__(self, game_num: int): + self.name = f"` " \ + f"Game {game_num + 1}" \ + f" `" + + self.selected_stage = None + self.winner = None + + self.stagelist: StageList = StageList() + + # auto veto counters if first game + if game_num == 0: + for x in range(len(self.stagelist.counters)): + self.stagelist.counters[x].veto = True + + def starters_embed(self) -> str: + """ + Generate embed string for stagelist starters + :return: + """ + message = "" + # loop through start stage names + for x in range(len(self.stagelist.starters)): + # append each name to a new line in the message + message = f'{message}{self.stagelist.starters[x]}\n' + # return message string + return message + + def counters_embed(self) -> str: + """ + Generate embed string for stagelist counters + :return: + """ + message = "" + # loop through counter stage names + for x in range(len(self.stagelist.counters)): + # append each name to a new line in the message + message = f'{message}{self.stagelist.counters[x]}\n' + # return message string + return message + + def winner_embed(self) -> str: + """ + Generate embed string for winner line + :return: + """ + message = "**Winner:**" + if self.winner is None: + return f"{message} TBD" + else: + return f"{message} {self.winner.mention}" + + def choose_stage(self, stage: str): + """ + Tries to choose a stage + :param stage: name/alias of stage to chose + """ + self.selected_stage = self.stagelist.choose(stage) + + def veto_stage(self, stage: str): + """ + Tries to veto a stage + :param stage: name/alias of stage to veto + """ + self.stagelist.veto(stage) diff --git a/smash/match.py b/smash/match.py new file mode 100644 index 0000000..64892b7 --- /dev/null +++ b/smash/match.py @@ -0,0 +1,193 @@ +import asyncio +from typing import List + +import discord +from discord.ext import commands + +from settings import rulebook_url, tourney_name, footer_icon, footer_note, \ + veto_timeout, newline +from smash.game import Game +from smash.player import Player +from utils.checks import playerCheck, stageCheck + + +class Match: + """ + Represents a Smash match + """ + + def __init__(self, player1: Player, player2: Player, num_of_games: int): + # players + self.player1 = player1 + self.player2 = player2 + self.winner = None + + # match data + self.games: List[Game] = [] + self.name: str = f"{tourney_name}: {player1.name} vs {player2.name}" + self.description: str = f"{self.player1.mention} vs " \ + f"{self.player2.mention} " \ + f"\nThe rulebook can be found " \ + f"[here]({rulebook_url})" + + # match state + self.num_of_games: int = num_of_games + self.current_game: int = 0 + + # generate first game + self.games.append(Game(0)) + + @property + def embed(self): + title = f"Smash Ultimate Best-of-{self.num_of_games} Veto" + embed = discord.Embed(title=title, + description=self.description, + color=discord.Colour.gold()) + # loop through max games times and generate embed fields + for x in range(len(self.games)): + embed.add_field(name=self.games[x].name, + value=self.games[x].winner_embed(), + inline=False) + # x - 1 because its using index num + embed.add_field(name="__Starter Stages__", + value=self.games[x].starters_embed(), + inline=True) + embed.add_field(name="__Counterpick Stages__", + value=self.games[x].counters_embed(), + inline=True) + # set footer + embed.set_footer(icon_url=footer_icon, + text=f"{tourney_name} | {footer_note}") + + return embed + + async def veto(self, ctx: discord.ext.commands.Context, + bot: discord.ext.commands.Bot): + # FIGURING OUT GAME EMBED INDEXES (ONLY IF U WANT TO REWRITE): + # GAME NUM * 3 = embed title index, just + 1 and + 2 for stage lists + # IF FINISH EARLY LOOP FROM CURRENT_GAME TO NUM_OF_GAMES - 1 + # AND CROSS OUT LINE + + # add DSR stages to the game if on game 3 or higher + if self.current_game >= 2: + for x in range(len(self.player2.dsr)): + self.games[self.current_game].veto_stage( + self.player2.dsr[x].name) + + # send first embed + await ctx.send(embed=self.embed) + + # initial veto + if self.current_game == 0: + # loop through 4 stage veto's/selection + for x in range(4): + # check which message to send + if x == 0: + await ctx.send(f"{self.player1.mention}, veto a starter.") + if x == 1: + await ctx.send(f"{self.player2.mention}, veto a starter.") + if x == 2: + await ctx.send( + f"{self.player2.mention}, veto another starter.") + if x == 3: + await ctx.send( + f"{self.player1.mention}, select the stage from " + f"the remaining starters.") + + # wait for players stage choice + msg = await bot.wait_for('message', + check=stageCheck(ctx, self), + timeout=veto_timeout) + + # if on stage selection + if x == 3: + # choose the stage + self.games[self.current_game].choose_stage(msg.content) + # if on veto still + else: + # veto the stage + self.games[self.current_game].veto_stage(msg.content) + + # regenerate and send embed + await ctx.send(embed=self.embed) + + # subsequent veto + else: + # loop through 3 stage veto's/selection process + for x in range(3): + if x == 0: + await ctx.send( + f"{self.player1.mention}, veto a stage.") + elif x == 1: + await ctx.send( + f"{self.player1.mention}, veto another stage.") + elif x == 2: + await ctx.send( + f"{self.player2.mention}, select the stage.") + + # wait for players stage choice + msg = await bot.wait_for('message', + check=stageCheck(ctx, self), + timeout=veto_timeout) + + # if on stage selection + if x == 2: + # choose the stage + self.games[self.current_game].choose_stage(msg.content) + # if on veto still + else: + # veto the stage + self.games[self.current_game].veto_stage(msg.content) + + # regenerate and send embed + await ctx.send(embed=self.embed) + + # get winner + await ctx.send(f"{newline}GLHF! Once finished, " + f"the winner should say `me`.") + msg = await bot.wait_for('message', + check=playerCheck(ctx), + timeout=veto_timeout) + + # swap player 1 if player 2 was winner + if msg.author.id == self.player2.id: + self.swap_players() + + # add stage to winner dsr list + self.player1.dsr.append( + self.games[self.current_game].selected_stage) + + # set the game winner + self.games[self.current_game].winner = self.player1 + self.player1.wins += 1 + + # check if the match has a winner + if self.player1.wins >= (self.num_of_games // 2) + 1: + self.winner = self.player1 + await ctx.send(embed=self.embed) + await ctx.send('GG!') + + # else prep for the next game veto + else: + # move to next game + self.current_game += 1 + + # add games + self.games.append(Game(self.current_game)) + + # setup next game + await ctx.send(embed=self.embed) + await ctx.send('Starting next game veto...') + await asyncio.sleep(3) + + def swap_players(self): + """ + Swap player1 and player2 + """ + # swap player objects + p1 = self.player2 + p2 = self.player1 + + # set new players + self.player1 = p1 + self.player2 = p2 diff --git a/smash/player.py b/smash/player.py new file mode 100644 index 0000000..49ab254 --- /dev/null +++ b/smash/player.py @@ -0,0 +1,32 @@ +from typing import List + +import discord + +from smash.stage import Stage + + +class Player: + """ + Represents a player in a Smash match + """ + + def __init__(self, user: discord.Member): + """ + Constructor method for a Smash Player + :param user: Discord user to grab info from + """ + self.name = user.display_name + self.mention = user.mention + self.id = user.id + self.wins = 0 + + # list of DSR stages + self.dsr: List[Stage] = [] + + def __eq__(self, other) -> bool: + """ + Compares self to discord member + :param other: Discord member to check + :return: boolean for whether they are the same + """ + return self.id == other.id diff --git a/smash/stage.py b/smash/stage.py new file mode 100644 index 0000000..11b0fee --- /dev/null +++ b/smash/stage.py @@ -0,0 +1,51 @@ +from string import capwords +from typing import List + +from settings import hide_map_on_veto, cross_map_on_veto + + +class Stage: + """ + Represents a Smash stage + """ + + def __init__(self, name: str, starter: bool, aliases: List[str] = None): + """ + Constructor Method + :param name: name of the stage + :param starter: if stage is a starter + :param aliases: alias names for the stage + """ + self.name = name + self.veto = False + self.chosen = False + self.starter = starter + self.aliases = aliases + + def __str__(self) -> str: + """ + String representation of a map + :return: + """ + if self.veto: + name = self.name + # surround in cross out if crossing + if cross_map_on_veto: + name = f'~~{name}~~' + # surround in spoiler tags if hiding + if hide_map_on_veto: + name = f'||{name}||' + # return the name of the stage + return name + elif self.chosen: + return f'⮕**{self.name}**' + else: + return self.name + + def __eq__(self, stage: str) -> bool: + """ + Compares a string of the stage name to its name and aliases + :param stage: string name of the stage + :return: boolean if matching + """ + return capwords(stage) == self.name or stage.lower() in self.aliases diff --git a/smash/stagelist.py b/smash/stagelist.py new file mode 100644 index 0000000..e14f4e4 --- /dev/null +++ b/smash/stagelist.py @@ -0,0 +1,93 @@ +from typing import List + +from settings import stages +from smash.stage import Stage + + +class StageList: + """ + Represents a list of smash stages + """ + + def __init__(self): + # stage lists + self.starters: List[Stage] = [] + self.counters: List[Stage] = [] + + # loops through the stages from settings + for x in range(len(stages)): + # if starter bool = true + if stages[x]['starter']: + # create new stage from info and add to starters + self.starters.append(Stage(stages[x]['name'], + stages[x]['starter'], + stages[x]['aliases'])) + # if starter bool = false + else: + # create new stage from info and add to counters + self.counters.append(Stage(stages[x]['name'], + stages[x]['starter'], + stages[x]['aliases'])) + + def veto(self, stage: str) -> Stage: + """ + Tries to veto a stage + :param stage: name/alias of stage to veto + :return: the stage object that was veto'd + """ + # search for stage in starters + for x in range(len(self.starters)): + if self.starters[x] == stage: + # if matching, change the state of the stage + self.starters[x].veto = True + return self.starters[x] + + # search through counters + for x in range(len(self.counters)): + if self.counters[x] == stage: + # if matching, change the state of the stage + self.counters[x].veto = True + return self.counters[x] + + def choose(self, stage: str) -> Stage: + """ + Chooses a stage + :param stage: name/alias of stage to veto + :return: the stage object that was chosen + """ + # search for stage in starters + for x in range(len(self.starters)): + if self.starters[x] == stage: + # if matching, change the state of the stage + self.starters[x].chosen = True + return self.starters[x] + + # search through counters + for x in range(len(self.counters)): + if self.counters[x] == stage: + # if matching, change the state of the stage + self.counters[x].chosen = True + return self.counters[x] + + def __contains__(self, stage: str): + """ + Check's if a string is the name of a stage + :param stage: + :return: + """ + # search for stage in starters + for x in range(len(self.starters)): + if self.starters[x] == stage and \ + not self.starters[x].veto and \ + not self.starters[x].chosen: + return True + + # search through counters + for x in range(len(self.counters)): + if self.counters[x] == stage and \ + not self.counters[x].veto and \ + not self.counters[x].chosen: + return True + + # returns false if it wasn't successful + return False diff --git a/utils/checks.py b/utils/checks.py new file mode 100644 index 0000000..f0b8dcc --- /dev/null +++ b/utils/checks.py @@ -0,0 +1,51 @@ +def playerCheck(ctx): + def func(message): + """Checks if a player said 'me' """ + return message.content.lower() == 'me' \ + and message.channel == ctx.channel + + return func + + +def stageCheck(ctx, match): + """ + Check if the stage name is valid + :param ctx: discord context to check for channel + :param match: match to check stagelist and game from + :return: + """ + + def func(message): + return message.content in match.games[match.current_game].stagelist \ + and message.channel == ctx.channel + + return func + + +def mapCheck(ctx, match): + """ + Check if the stage name is valid + :param ctx: discord context to check for channel + :param match: match to check stagelist and game from + :return: + """ + + def func(message): + return message.content in match.games[match.current_game].map_pool \ + and message.channel == ctx.channel + + return func + + +def sideCheck(ctx): + """ + Check if valid valorant side + :param ctx: discord context to check for channel + :return: + """ + + def func(message): + return ('att' in message.content or 'def' in message.content) and \ + message.channel == ctx.channel + + return func diff --git a/utils/embeds.py b/utils/embeds.py index b61a485..1224782 100644 --- a/utils/embeds.py +++ b/utils/embeds.py @@ -1,4 +1,6 @@ -from utils.message_generators import * +import discord + +from settings import tourney_name, footer_note, footer_icon async def starting(): @@ -38,6 +40,19 @@ async def missing_param_error(error_message: str): return embed +async def invalid_param_error(error_message: str): + # generate embed with red colour + embed = discord.Embed(color=discord.Colour.red()) + # set title field + embed.add_field(name=f"Invalid Parameter Error", value=error_message) + # set footer + embed.set_footer(icon_url=footer_icon, + text=f"{tourney_name} | {footer_note}") + + # return finished embed + return embed + + async def missing_permission_error(error_message: str): # generate embed with red colour embed = discord.Embed(color=discord.Colour.red()) @@ -93,79 +108,12 @@ async def purged(): return embed -async def flipping_coin(dot): - # generate embed with blue colour - embed = discord.Embed(colour=discord.Colour.blue()) - # set starting field - embed.set_author(name=f"Flipping coin{'.' * dot}") - - # return finished embed - return embed - - async def coinflip_winner(winner: discord.User): # generate embed with green colour embed = discord.Embed(colour=discord.Colour.green()) # set starting field - embed.set_author(name=f"{winner.name} won the coinflip!") - - # return finished embed - return embed - - -async def smash_veto(player1: discord.User, player2: discord.User, - max_games: int): - # generate embed - desc = f"{player1.mention} vs {player2.mention} " \ - f"\nThe rulebook can be found [here]({rulebook_url})" - embed = discord.Embed(title=f"Smash Ultimate Best-of-{max_games} Veto", - description=desc, - color=discord.Colour.gold()) - - # loop through max games times and generate embed fields - for x in range(1, max_games + 1): - embed.add_field( - name=f"` Game {x} " - f" `", - value="**Winner:** TBD", inline=False) - embed.add_field(name="Starter Stages", value=starters_message(), - inline=True) - embed.add_field(name="Counterpick Stages", - value=counters_message(), inline=True) - - # set footer - embed.set_footer(icon_url=footer_icon, - text=f"{tourney_name} | {footer_note}") - - # return finished embed - return embed - - -async def valorant_veto(player1: discord.User, player2: discord.User, - max_games: int): - # generate embed - desc = f"**Captains:** {player1.mention}, {player2.mention} " \ - f"\nThe rulebook can be found [here]({rulebook_url})" - embed = discord.Embed(title=f"VALORANT Best-of-{max_games} Veto", - description=desc, - color=discord.Colour.gold()) - - # loop through max games times and generate embed fields - for x in range(1, max_games + 1): - embed.add_field( - name=f"` Game {x} " - f" `", - value="**Winner:** TBD", inline=False) - embed.add_field(name="Maps", value=valorant_maps_message(), - inline=True) - - embed.add_field(name="Starting Sides", value=f"**Attack:** TBD " - f"\n**Defense:** TBD", - inline=True) - - # set footer - embed.set_footer(icon_url=footer_icon, - text=f"{tourney_name} | {footer_note}") + embed.add_field(name="Coinflip", + value=f"{winner.mention} won the coinflip!") # return finished embed return embed diff --git a/utils/message_generators.py b/utils/message_generators.py deleted file mode 100644 index 3ab00cc..0000000 --- a/utils/message_generators.py +++ /dev/null @@ -1,77 +0,0 @@ -import discord - -from settings import * - - -def map_list_message(map_list: list, removed_maps: list, selected_map: str): - """ - Base map list message generator - :param map_list: string list of map names - :param removed_maps: string list of removed maps - :param selected_map: string name of the selected map - :return: output message for embed - """ - # blank string - message = "" - # loop through starter stages - for x in range(len(map_list)): - # if removed maps is none then don't worry about striking out - if removed_maps is not None: - # bolds the map if its selected - if map_list[x] == selected_map: - message = f"{message}⮕**{map_list[x]}**\n" - # crosses map out if its in the removed_maps list - elif map_list[x] in removed_maps: - message = f"{message}~~{map_list[x]}~~\n" - # else concatenate the new map name regularly - else: - message = f"{message}{map_list[x]}\n" - # concatenate the new map name regularly - else: - message = f"{message}{map_list[x]}\n" - return message - - -def starters_message(removed_stages=None, selected_stage=None): - """ - Generates the smash starter stages list - :param selected_stage: string name of the stage that is selected - :param removed_stages: list of veto'd stages (exact spellings) - :return: string for embed value - """ - return map_list_message(starters, removed_stages, selected_stage) - - -def counters_message(removed_stages=None, selected_stage=None): - """ - Generates the smash counterpick stages list - :param selected_stage: string name of the stage that is selected - :param removed_stages: list of veto'd stages (exact spellings) - :return: string for embed value - """ - return map_list_message(counters, removed_stages, selected_stage) - - -def valorant_maps_message(removed_maps=None, selected_map=None): - """ - Generates the VALORANT map list - :param removed_maps: - :param selected_map: - :return: string embed value - """ - return map_list_message(maps, removed_maps, selected_map) - - -def valorant_sides_message(p1: discord.User, p2: discord.User, - attack: bool): - """ - Generates starting side message for VALORANT Veto - :param p1 player 1 user - :param p2 player 2 user - :param attack boolean for if player1 chose attack - :return: string embed value - """ - if attack: - return f"**Attack:** {p1.mention} \n**Defense:** {p2.mention}" - else: - return f"**Attack:** {p2.mention} \n**Defense:** {p1.mention}" diff --git a/utils/player_utils.py b/utils/player_utils.py index 00bbb1b..2a6a1ad 100644 --- a/utils/player_utils.py +++ b/utils/player_utils.py @@ -1,32 +1,12 @@ -import asyncio import random import discord from discord.ext import commands +from settings import newline +from smash.player import Player from utils import embeds - - -async def swap_players(player1: discord.User, player2: discord.User, - player1_dsr: list, player2_dsr: list): - """ - Swap players for smash veto with tracking for DSR'd stages - :param player1: player 1 - :param player2: player 2 - :param player1_dsr: player 1's DSR stage(s) - :param player2_dsr: player 2's DSR stage(s) - :return: new player1 and player2 - """ - # swap player objects - p1 = player2 - p2 = player1 - - # swap player DSR's - p1_dsr = player2_dsr - p2_dsr = player1_dsr - - # return new player 1 and player 2 - return p1, p2, p1_dsr, p2_dsr +from utils.checks import playerCheck async def get_players(ctx: discord.ext.commands.Context): @@ -65,15 +45,6 @@ async def coinflip(ctx: discord.ext.commands.Context, p1: discord.User, player1 = None player2 = None - flip = await embeds.flipping_coin(5) - coin_msg = await ctx.send(embed=flip) - - # coinflip animation - for n in range(1, 5): - await asyncio.sleep(0.7) - flip = await embeds.flipping_coin(n) - await coin_msg.edit(embed=flip) - # pick random num = random.randrange(0, 101) if num % 2 == 0: @@ -82,6 +53,29 @@ async def coinflip(ctx: discord.ext.commands.Context, p1: discord.User, elif num % 2 == 1: player1 = p2 player2 = p1 - await coin_msg.edit(embed=await embeds.coinflip_winner(player1)) + await ctx.send(embed=await embeds.coinflip_winner(player1)) return player1, player2 + + +async def seed_selection(ctx: discord.ext.commands.Context, + bot: discord.ext.commands.Bot, + match): + # HIGHER SEED SELECTION + await ctx.send(f"{newline}Player 1 (the higher seed) say `me`") + + msg = await bot.wait_for('message', check=playerCheck(ctx), timeout=300) + + # placeholders + p1 = match.player1 + p2 = match.player2 + + # changes player order if player 2 said they were first seed + if Player(msg.author) == match.player2: + match.player1 = p2 + match.player2 = p1 + + # notifies of veto starts + await ctx.send(f"Starting veto with {match.player1.mention} as " + f"**Player 1** and {match.player2.mention} " + f"as **Player 2** in 5 seconds...") diff --git a/valorant/__init__.py b/valorant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/valorant/game.py b/valorant/game.py new file mode 100644 index 0000000..e97664b --- /dev/null +++ b/valorant/game.py @@ -0,0 +1,76 @@ +from valorant.map_pool import MapPool + + +class Game: + """ + Represents a VALORANT game + """ + + def __init__(self, game_num: int): + self.name = f"` " \ + f"Game {game_num + 1}" \ + f" `" + + self.selected_stage = None + self.winner = None + self.map_pool = MapPool() + + self.att_start = None + self.def_start = None + + def map_embed(self) -> str: + """ + Generate embed string for discord embeds + :return: string + """ + message = "" + # loop through map names + for val_map in self.map_pool.maps: + # append each map name to a newline + message = f'{message}{val_map}\n' + # return message string + return message + + def sides_embed(self) -> str: + """ + Generate embed string for discord embeds + :return: string + """ + if self.att_start is not None and self.def_start is not None: + return f"**Attack:** {self.att_start.mention} \n" \ + f"**Defense:** {self.def_start.mention}" + else: + return f"**Attack:** TBD \n" \ + f"**Defense:** TBD" + + def veto_map(self, name: str): + """ + Tries to veto a map + :param name: + :return: map that was veto'd + """ + for x in range(len(self.map_pool.maps)): + if self.map_pool.maps[x] == name: + # if matching change th state of the map and return it + self.map_pool.maps[x].veto = True + return self.map_pool.maps[x] + + def choose(self, name: str): + """ + Tries to choose a map, veto's all maps that aren't chosen + :param name: + """ + for x in range(len(self.map_pool.maps)): + if self.map_pool.maps[x] == name: + # if matching change th state of the map and return it + self.map_pool.maps[x].chosen = True + # veto map if not the chosen one + self.map_pool.maps[x].veto = True + + def choose_last(self): + """ + Chooses the last map that isn't veto'd + """ + for val_map in self.map_pool.maps: + if not val_map.veto: + val_map.chosen = True diff --git a/valorant/map.py b/valorant/map.py new file mode 100644 index 0000000..1ac140a --- /dev/null +++ b/valorant/map.py @@ -0,0 +1,46 @@ +from string import capwords + +from settings import hide_map_on_veto, cross_map_on_veto + + +class Map: + """ + Represents a VALORANT Map + """ + + def __init__(self, name: str): + """ + Constructor method + :param name: name of the map + """ + self.name = name + self.veto = False + self.chosen = False + + def __str__(self) -> str: + """ + String representation of the map + :return: string of map + """ + if self.chosen: + return f'⮕**{self.name}**' + elif self.veto: + name = self.name + # surround in cross out if crossing + if cross_map_on_veto: + name = f'~~{name}~~' + # surround in spoiler tags if hiding + if hide_map_on_veto: + name = f'||{name}||' + # return the name of the stage + return name + else: + return self.name + + def __eq__(self, other): + """ + Compares a string of the map name to its name + :param other: string name of other map + :return: boolean if matching + """ + return capwords(other) == self.name diff --git a/valorant/map_pool.py b/valorant/map_pool.py new file mode 100644 index 0000000..45deca3 --- /dev/null +++ b/valorant/map_pool.py @@ -0,0 +1,34 @@ +from typing import List + +from settings import val_maps +from valorant.map import Map + + +class MapPool: + """ + Represents a VALORANT Map Pool + """ + + def __init__(self): + # map list + self.maps: List[Map] = [] + + # loops through the map from settings + for val_map in val_maps: + self.maps.append(Map(val_map)) + + def __contains__(self, item): + """ + Check if a string is the name of a stage + :param item: string of map name + :return: + """ + # search through map for match + for val_map in self.maps: + if val_map == item and \ + not val_map.veto and \ + not val_map.chosen: + return True + + # else return false if it wasn't found + return False diff --git a/valorant/match.py b/valorant/match.py new file mode 100644 index 0000000..1ffcfa3 --- /dev/null +++ b/valorant/match.py @@ -0,0 +1,302 @@ +from typing import List + +import discord +from discord.ext import commands + +from settings import tourney_name, rulebook_url, footer_icon, footer_note, \ + veto_timeout +from utils.checks import mapCheck, sideCheck +from valorant.game import Game + + +class Match: + """ + Represents a VALORANT Match + """ + + def __init__(self, + captain1: discord.Member, + captain2: discord.Member, + num_of_games: int): + # captains + self.captain1 = captain1 + self.captain2 = captain2 + self.winner = None + # match data + self.games: List[Game] = [] + self.name: str = f"{tourney_name}: {captain1.name} vs {captain2.name}" + self.description: str = f"{self.captain1.mention} vs " \ + f"{self.captain2.mention} " \ + f"\nThe rulebook can be found " \ + f"[here]({rulebook_url})" + + # match state + self.num_of_games: int = num_of_games + self.current_game: int = 0 + + # generate games + for x in range(num_of_games): + self.games.append(Game(x)) + + @property + def embed(self): + title = f"VALORANT Best-of-{self.num_of_games} Veto" + embed = discord.Embed(title=title, + description=self.description, + color=discord.Colour.gold()) + # loop through max games times and generate embed fields + for x in range(len(self.games)): + embed.add_field(name=self.games[x].name, + value='_ _', + inline=False) + # x - 1 because its using index num + embed.add_field(name="__Maps__", + value=self.games[x].map_embed(), + inline=True) + embed.add_field(name="__Sides__", + value=self.games[x].sides_embed(), + inline=True) + # set footer + embed.set_footer(icon_url=footer_icon, + text=f"{tourney_name} | {footer_note}") + + return embed + + async def veto(self, ctx: discord.ext.commands.Context, + bot: discord.ext.commands.Bot): + # send first embed + await ctx.send(embed=self.embed) + + # Bo1 VETO + if self.num_of_games == 1: + for x in range(5): + if x == 0: + await ctx.send( + f"{self.captain1.mention} veto a map.") + elif x == 1: + await ctx.send( + f"{self.captain2.mention} veto a map.") + elif x == 2: + await ctx.send( + f"{self.captain1.mention} veto another map.") + elif x == 3: + await ctx.send( + f"{self.captain2.mention} veto another map.") + elif x == 4: + # select last map + self.games[self.current_game].choose_last() + + # resend embed + await ctx.send(embed=self.embed) + + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side?") + + # if veto'ing + if x <= 3: + msg = await bot.wait_for('message', + check=mapCheck(ctx, self), + timeout=veto_timeout) + # veto the map + self.games[self.current_game].veto_map(msg.content) + + # otherwise select side + else: + # run side selection + await self.sideSelection(ctx, bot, 1) + + # resend embed + await ctx.send(embed=self.embed) + + # Bo3 VETO + elif self.num_of_games == 3: + for x in range(6): + if x == 0: + await ctx.send( + f"{self.captain1.mention} select a map for game 1.") + elif x == 1: + await ctx.send( + f"{self.captain2.mention} what is your " + f"preferred starting side for game 1?") + await self.sideSelection(ctx, bot, 2) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + elif x == 2: + await ctx.send( + f"{self.captain2.mention} select a map for game 2.") + elif x == 3: + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side for game 2?") + await self.sideSelection(ctx, bot, 1) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + elif x == 4: + await ctx.send( + f"{self.captain1.mention} please veto a map.") + elif x == 5: + await ctx.send( + f"{self.captain2.mention} please veto a map.") + + msg = await bot.wait_for('message', + check=mapCheck(ctx, self), + timeout=veto_timeout) + + if x == 0 or x == 2: + # choose map for these games + self.games[self.current_game].choose(msg.content) + # loop through and veto from rest (chosen takes over veto) + for game in self.games: + game.veto_map(msg.content) + + # FIXME: idk some way to do this better but it s 4am now xd + elif x == 4 or x == 5: + # veto the map from all games + for game in self.games: + game.veto_map(msg.content) + + if x == 5: + # choose last map + self.games[self.current_game].choose_last() + + # refresh embed + await ctx.send(embed=self.embed) + + # side selection for last game + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side for game 3?") + await self.sideSelection(ctx, bot, 1) + + # refresh embed + await ctx.send(embed=self.embed) + + # BO5 VETO + # FIXME: most definitely can be better, but its 4am xd + elif self.num_of_games == 5: + for x in range(9): + # GAME 1 + if x == 0: + await ctx.send( + f"{self.captain1.mention} select a map for game 1.") + elif x == 1: + await ctx.send( + f"{self.captain2.mention} what is your " + f"preferred starting side for game 1?") + await self.sideSelection(ctx, bot, 2) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + # GAME 2 + elif x == 2: + await ctx.send( + f"{self.captain2.mention} select a map for game 2.") + elif x == 3: + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side for game 2?") + await self.sideSelection(ctx, bot, 1) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + # GAME 3 + elif x == 4: + await ctx.send( + f"{self.captain1.mention} select a map for game 3.") + elif x == 5: + await ctx.send( + f"{self.captain2.mention} what is your " + f"preferred starting side for game 3?") + await self.sideSelection(ctx, bot, 2) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + # GAME 4 + elif x == 6: + await ctx.send( + f"{self.captain2.mention} select a map for game 4.") + elif x == 7: + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side for game 4?") + await self.sideSelection(ctx, bot, 1) + # update current game + self.current_game += 1 + # refresh embed + await ctx.send(embed=self.embed) + # skip to next step + continue + + if x % 2 == 0 and x != 8: + msg = await bot.wait_for('message', + check=mapCheck(ctx, self), + timeout=veto_timeout) + # choose map for these games + self.games[self.current_game].choose(msg.content) + # loop through and veto from rest (chosen takes over veto) + for game in self.games: + game.veto_map(msg.content) + + # FIXME: idk some way to do this better but it s 4am now xd + elif x == 8: + # choose last map + self.games[self.current_game].choose_last() + + # refresh embed + await ctx.send(embed=self.embed) + + # side selection for last game + await ctx.send( + f"{self.captain1.mention} what is your " + f"preferred starting side for game 5?") + await self.sideSelection(ctx, bot, 1) + + # refresh embed + await ctx.send(embed=self.embed) + + await ctx.send("GLHF! Don't forget to report your scores afterwards.") + + async def sideSelection(self, ctx, bot, chooser): + # get side message + msg = await bot.wait_for('message', + check=sideCheck(ctx), + timeout=veto_timeout) + + # set proper captain pings + if 'att' in msg.content: + if chooser == 1: + self.games[self.current_game].att_start = self.captain1 + self.games[self.current_game].def_start = self.captain2 + else: + self.games[self.current_game].att_start = self.captain2 + self.games[self.current_game].def_start = self.captain1 + elif 'def' in msg.content: + if chooser == 2: + self.games[self.current_game].att_start = self.captain1 + self.games[self.current_game].def_start = self.captain2 + else: + self.games[self.current_game].att_start = self.captain2 + self.games[self.current_game].def_start = self.captain1 diff --git a/veto/smash.py b/veto/smash.py deleted file mode 100644 index f92cf80..0000000 --- a/veto/smash.py +++ /dev/null @@ -1,237 +0,0 @@ -import asyncio -from string import capwords - -import discord -from discord.ext import commands - -from utils import player_utils -from utils.message_generators import * - -# combine lists for all stages list -all_stages = starters + counters - - -async def initial(ctx: discord.ext.commands.Context, - bot: discord.ext.commands.Bot, main_message: discord.Message, - p1: discord.User, p2: discord.User, emb: discord.Embed): - """ - Runs an initial smash veto - :param ctx: context for the message - :param bot: bot user to use - :param main_message: original message to edit - :param p1: player1 user - :param p2: player2 user - :param emb: embed to edit - :return: - """ - - # player check function - def playerCheck(message): - return message.content.lower() == 'me' and message.channel == ctx.channel - - # stage check function - def stageCheck(message): - return capwords(message.content) in all_stages \ - and capwords(message.content) not in removed_stages \ - and message.channel == ctx.channel - - # setup stages - removed_stages = [] - removed_stages = removed_stages + counters - p1_dsr_stage = [] - p2_dsr_stage = [] - - # setup players - player1 = p1 - player2 = p2 - - # more setup - main_msg = main_message - embed = emb - - # loop through 4 stage veto's/selection - for x in range(4): - # check which message to send - if x == 0: - await ctx.send(f"{player1.mention} please veto a starter.") - if x == 1: - await ctx.send(f"{player2.mention} please veto a starter.") - if x == 2: - await ctx.send(f"{player2.mention} please veto another starter.") - if x == 3: - await ctx.send(f"{player1.mention} please select the stage from " - f"the remaining starters.") - - # wait for players stage choice - msg = await bot.wait_for('message', check=stageCheck, timeout=300) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # if stage selection - if x == 3: - # highlight selected stage - value = starters_message(removed_stages, capwords(msg.content)) - embed.set_field_at(1, name="Starter Stages", - value=value) - - # sets DSR stage to selected stage (will wipe losers DSR later) - p1_dsr_stage.append(capwords(msg.content)) - p2_dsr_stage.append(capwords(msg.content)) - # otherwise edit game 1 embed to remove the stage - else: - # add stage to removed list - removed_stages.append(capwords(msg.content)) - # wipe out stage from embed - value = starters_message(removed_stages) - embed.set_field_at(1, name="Starter Stages", value=value) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # get winner of the match - await ctx.send(f"{newline}GLHF! Once finished, " - f"the winner should say `me`.") - msg = await bot.wait_for('message', check=playerCheck, timeout=1800) - - # switch player 1 to winner and player 2 to loser - if msg.author == player2: - player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await player_utils.swap_players(player1, player2, - p1_dsr_stage, p2_dsr_stage) - - # remove stage from losers DSR list - p2_dsr_stage.pop() - - # edit game 3 embed message - embed.set_field_at(0, name="` Game 1 " - " `", - value=f"**Winner:** {player1.mention}", inline=False) - await main_msg.edit(embed=embed) - - return main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage - - -async def nonInitial(ctx: discord.ext.commands.Context, - bot: discord.ext.commands.Bot, - main_message: discord.Message, - p1: discord.User, p2: discord.User, - p1_dsr: list, p2_dsr: list, - emb: discord.Embed, - game_num: int, starter_index: int, counter_index: int): - """ - Runs a nonInitial smash veto process - :param ctx: context for the message - :param bot: bot user to use - :param main_message: original message to edit - :param p1: player1 user - :param p2: player2 user - :param p1_dsr: player1's DSR'd stages - :param p2_dsr: player2's DSR'd stages - :param emb: embed to edit - :param game_num: game number in the set - :param starter_index: index of start stages field in the embed - :param counter_index: index of counterpick stages field in the embed - """ - - # setup stuff - main_msg = main_message - embed = emb - - # player check function - def playerCheck(message): - return message.content.lower() == 'me' \ - and message.channel == ctx.channel - - # stage check function - def stageCheck(message): - return capwords(message.content) in all_stages \ - and capwords(message.content) not in removed_stages \ - and message.channel == ctx.channel - - # notify of veto and reset channel after 3 seconds - await ctx.send(f"Starting Game {game_num} veto...") - await asyncio.sleep(3) - await ctx.channel.purge(after=main_msg) - - # setup stages - removed_stages = [] - p1_dsr_stage = p1_dsr - p2_dsr_stage = p2_dsr - removed_stages = removed_stages + p2_dsr_stage - - # setup players - player1 = p1 - player2 = p2 - - # update embed for DSR'd stage - value1 = starters_message(removed_stages) - value2 = counters_message(removed_stages) - embed.set_field_at(starter_index, name="Starter Stages", value=value1) - embed.set_field_at(counter_index, name="Counterpick Stages", value=value2) - await main_msg.edit(embed=embed) - - # loop through 3 stage veto's/selection - for x in range(3): - if x == 0: - await ctx.send(f"{player1.mention} please veto a stage.") - elif x == 1: - await ctx.send(f"{player1.mention} please veto another stage.") - elif x == 2: - await ctx.send(f"{player2.mention} please select the stage.") - - # wait for players stage choice - msg = await bot.wait_for('message', check=stageCheck, timeout=300) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # if on stage selection - if x == 2: - # highlight selected stage - value = starters_message(removed_stages, capwords(msg.content)) - embed.set_field_at(starter_index, name="Starter Stages", - value=value) - # highlight selected stage - value = counters_message(removed_stages, capwords(msg.content)) - embed.set_field_at(counter_index, name="Counterpick Stages", - value=value) - - # append to both DSR lists (to be removed when winner is decided - p1_dsr_stage.append(capwords(msg.content)) - p2_dsr_stage.append(capwords(msg.content)) - else: - # add stage to removed list - removed_stages.append(capwords(msg.content)) - # redraw starter stages list - value = starters_message(removed_stages) - embed.set_field_at(starter_index, name="Starter Stages", - value=value) - # redraw counterpick stages list - embed.set_field_at(counter_index, name="Counterpick Stages", - value=counters_message(removed_stages)) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # get winner of the match - await ctx.send(f"{newline}GLHF! Once finished, the winner should say `me`.") - msg = await bot.wait_for('message', check=playerCheck, timeout=1800) - - # switch player 1 to winner and player 2 to loser - if msg.author == player2: - player1, player2, p1_dsr_stage, p2_dsr_stage = \ - await player_utils.swap_players(player1, player2, - p1_dsr_stage, p2_dsr_stage) - - # remove stage from losers DSR list - p2_dsr_stage.pop() - - # edit game embed message - embed.set_field_at(starter_index - 1, - name=f"` Game {game_num} " - f" `", - value=f"**Winner:** {player1.mention}", inline=False) - await main_msg.edit(embed=embed) - - return main_msg, embed, player1, player2, p1_dsr_stage, p2_dsr_stage diff --git a/veto/valorant.py b/veto/valorant.py deleted file mode 100644 index 1d6fa45..0000000 --- a/veto/valorant.py +++ /dev/null @@ -1,320 +0,0 @@ -import asyncio -from string import capwords - -from discord.ext import commands - -from utils import player_utils -from utils.message_generators import * - - -async def bo1(ctx: discord.ext.commands.Context, bot: discord.ext.commands.Bot, - main_message: discord.Message, - p1: discord.User, p2: discord.User, emb: discord.Embed): - # setup stuff - main_msg = main_message - embed = emb - - # player check function - def playerCheck(message): - return message.content.lower() == 'me' \ - and message.channel == ctx.channel - - # stage check function - def mapCheck(message): - return capwords(message.content) in maps \ - and capwords(message.content) not in removed_maps \ - and message.channel == ctx.channel - - # setup players - player1, player2 = await player_utils.coinflip(ctx, p1, p2) - await asyncio.sleep(2) - - # maps - removed_maps = [] - - # notifies of veto starts - await ctx.send(f"Starting veto with {player1.mention} as **Team A** and " - f"{player2.mention} as **Team B** in 5 seconds...") - - # delete all messages and begin veto after 5 seconds - await asyncio.sleep(5) - await ctx.channel.purge(after=main_msg) - - for x in range(4): - if x == 0: - await ctx.send(f"{player1.mention} please veto a map.") - elif x == 1: - await ctx.send(f"{player2.mention} please veto a map.") - elif x == 2: - await ctx.send(f"{player1.mention} please veto another map.") - elif x == 3: - await ctx.send(f"{player2.mention} please veto another map.") - elif x == 4: - await ctx.send(f"{player1.mention} what is your " - f"preferred starting side?") - - # wait for players stage choice - msg = await bot.wait_for('message', check=mapCheck, timeout=300) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # if not last map then strike out - if x != 3: - # strike out map - removed_maps.append(capwords(msg.content)) - embed.set_field_at(1, name="Maps", - value=valorant_maps_message(removed_maps)) - else: - removed_maps.append(capwords(msg.content)) - # highlight last map - selected_map = "" - for val_map in maps: - if val_map not in removed_maps: - selected_map = val_map - embed.set_field_at(1, name="Maps", - value=valorant_maps_message(removed_maps, - selected_map)) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # side selection - await sideSelection(ctx, bot, player1, player2, embed, 2, True) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # get winner of the match - await ctx.send( - f"{newline}GLHF! Once finished, the winner should say `me`.") - msg = await bot.wait_for('message', check=playerCheck, timeout=1800) - - # update winner embed - embed.set_field_at(0, - name=f"` Game 1 " - f" `", - value=f"**Winner:** {msg.author.mention}", inline=False) - await main_msg.edit(embed=embed) - - return main_msg, embed - - -async def bo3(ctx: discord.ext.commands.Context, bot: discord.ext.commands.Bot, - main_message: discord.Message, - p1: discord.User, p2: discord.User, emb: discord.Embed): - # setup stuff - main_msg = main_message - embed = emb - - # player check function - def playerCheck(message): - return message.content.lower() == 'me' \ - and message.channel == ctx.channel - - # stage check function - def mapCheck(message): - return capwords(message.content) in maps \ - and capwords(message.content) not in removed_maps \ - and message.channel == ctx.channel - - # setup players - player1, player2 = await player_utils.coinflip(ctx, p1, p2) - await asyncio.sleep(1) - - # maps - removed_maps = [] - - # notifies of veto starts - await ctx.send( - f"Starting veto with {player1.mention} as **Team A** " - f"and {player2.mention} as **Team B** in 5 seconds...") - - # delete all messages and begin veto after 5 seconds - await asyncio.sleep(5) - await ctx.channel.purge(after=main_msg) - - for x in range(6): - if x == 0: - await ctx.send( - f"{player1.mention} please select a map for game 1.") - elif x == 1: - await sideSelection(ctx, bot, player1, player2, embed, 2, False) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - continue - elif x == 2: - await ctx.send( - f"{player2.mention} please select a map for game 2.") - elif x == 3: - await sideSelection(ctx, bot, player1, player2, embed, 5, True) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - continue - elif x == 4: - await ctx.send(f"{player1.mention} please veto a map.") - elif x == 5: - await ctx.send(f"{player2.mention} please veto a map.") - - # wait for players stage choice - msg = await bot.wait_for('message', check=mapCheck, timeout=300) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - # if not last map then strike out - # FIXME LATER - definitely could do this more efficiently but its - # 3 am and im tired bruh - if x == 0: - selected_map = capwords(msg.content) - removed_maps.append(selected_map) - # select map and strike out rest - embed.set_field_at(1, name="Maps", - value=valorant_maps_message(maps, - selected_map)) - - # strike out for next two games as well - embed.set_field_at(4, name="Maps", - value=valorant_maps_message(removed_maps)) - embed.set_field_at(7, name="Maps", - value=valorant_maps_message(removed_maps)) - - elif x == 2: - selected_map = capwords(msg.content) - removed_maps.append(selected_map) - # select map and strike out rest - embed.set_field_at(4, name="Maps", - value=valorant_maps_message(maps, - selected_map)) - - # strike out for final game as well - embed.set_field_at(7, name="Maps", - value=valorant_maps_message(removed_maps)) - - elif x == 4: - # strike out map - removed_maps.append(capwords(msg.content)) - embed.set_field_at(7, name="Maps", - value=valorant_maps_message(removed_maps)) - elif x == 5: - removed_maps.append(capwords(msg.content)) - # highlight last map - selected_map = "" - for val_map in maps: - if val_map not in removed_maps: - selected_map = val_map - embed.set_field_at(7, name="Maps", - value=valorant_maps_message(removed_maps, - selected_map)) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # side selection - await sideSelection(ctx, bot, player1, player2, embed, 8, True) - - # edit original message with new embed - await main_msg.edit(embed=embed) - - # winner tracker - p1_wins = 0 - p2_wins = 0 - # for loop for each games winners - for x in range(3): - - # get winner of the match - await ctx.send( - f"{newline}You may play now play game {x + 1}. GLHF! Once finished, " - f"the winner should say `me`.") - msg = await bot.wait_for('message', check=playerCheck, timeout=1800) - - if msg.author == player1: - p1_wins += 1 - elif msg.author == player2: - p2_wins += 1 - - # update winner embed - # if winner - if p1_wins == 2 or p2_wins == 2: - embed.set_field_at(x * 3, - name=f"~~` Game {x + 1}" - f" `~~", - value=f"~~**Winner:** TBD~~", - inline=False) - embed.set_field_at(x * 3 + 1, name="Maps", - value=valorant_maps_message(maps)) - embed.set_field_at(x * 3 + 2, name="Starting Sides", - value=f"~~**Attack:** TBD " - f"\n**Defense:** TBD,~~") - await main_msg.edit(embed=embed) - - continue - - # otherwise update normally - embed.set_field_at(x * 3, - name=f"` Game {x + 1} " - f" `", - value=f"**Winner:** {msg.author.mention}", - inline=False) - await main_msg.edit(embed=embed) - - # remove messages sent after the embed - await ctx.channel.purge(after=main_msg) - - await ctx.send('GG!') - - return main_msg, embed - - -async def sideSelection(ctx: discord.ext.commands.Context, - bot: discord.ext.commands.Bot, - player1: discord.User, player2: discord.User, - embed: discord.Embed, embed_index: int, p1_pick: bool): - # side check function - def sideCheck(message): - return 'att' in message.content or 'def' in message.content \ - and message.channel == ctx.channel - - # side selection - if p1_pick: - await ctx.send(f"{player1.mention} what is your " - f"preferred starting side?") - msg = await bot.wait_for('message', check=sideCheck, timeout=300) - if 'att' in msg.content: - embed.set_field_at(embed_index, name="Starting Sides", - value=valorant_sides_message(player1, - player2, - True), - inline=True) - elif 'def' in msg.content: - embed.set_field_at(embed_index, name="Starting Sides", - value=valorant_sides_message(player1, - player2, - False), - inline=True) - else: - await ctx.send(f"{player2.mention} what is your " - f"preferred starting side?") - msg = await bot.wait_for('message', check=sideCheck, timeout=300) - if 'att' in msg.content: - embed.set_field_at(embed_index, name="Starting Sides", - value=valorant_sides_message(player1, - player2, - False), - inline=True) - elif 'def' in msg.content: - embed.set_field_at(embed_index, name="Starting Sides", - value=valorant_sides_message(player1, - player2, - True), - inline=True)