diff --git a/locales/strings.en-US.json b/locales/strings.en-US.json index 9cb66bb..9a192c3 100644 --- a/locales/strings.en-US.json +++ b/locales/strings.en-US.json @@ -85,7 +85,6 @@ "coinflip_flipping_animation_3": "\ud83e\ude99 Flipping.", "coinflip_flipping_author": "flipping a Coin", "coinflip_flipping_description": "the coin is in the air...", - "coinflip_invalid_prediction_author": "Invalid Prediction", "coinflip_invalid_prediction_description": "please enter a valid prediction ('heads'/'h' or 'tails'/'t').", "coinflip_result_author": "Coin Flip Result", "coinflip_result_description": "the coin landed on **{0}**.", @@ -163,6 +162,7 @@ "error_actionable_hierarchy_bot": "I don't have permission to perform this action on this user due to role hierarchy.", "error_actionable_hierarchy_user": "you don't have permission to perform this action on this user due to role hierarchy.", "error_actionable_self": "you can't perform this action on yourself.", + "error_already_flipping_coin_description": "you already have a coinflip running.", "error_already_playing_blackjack": "you already have a game of blackjack running.", "error_birthdays_disabled_author": "Birthdays Disabled", "error_birthdays_disabled_description": "birthdays are disabled in this server.", diff --git a/modules/economy/coinflip.py b/modules/economy/coinflip.py index 4dc2eb5..767c2d4 100644 --- a/modules/economy/coinflip.py +++ b/modules/economy/coinflip.py @@ -1,20 +1,25 @@ import asyncio import random +import discord +from discord import app_commands from discord.ext import commands import lib.format from lib.client import Luminara from lib.const import CONST +from lib.exceptions import LumiException from ui.embeds import Builder +ACTIVE_COINFLIPS: dict[int, bool] = {} + class Coinflip(commands.Cog): def __init__(self, bot: Luminara) -> None: self.bot: Luminara = bot self.coinflip.usage = lib.format.generate_usage(self.coinflip) - @commands.hybrid_command( + @commands.command( name="coinflip", aliases=["cf"], ) @@ -22,6 +27,7 @@ class Coinflip(commands.Cog): async def coinflip( self, ctx: commands.Context[Luminara], + *, prediction: str | None = None, ) -> None: """ @@ -32,73 +38,120 @@ class Coinflip(commands.Cog): ctx : commands.Context[Luminara] The context of the command. prediction : str, optional - The predicted outcome ('heads'/'h' or 'tails'/'t'). + The predicted outcome ('heads', 'h', 'tails', or 't'). """ - result = random.choice(["heads", "tails"]) - if prediction: prediction = prediction.lower() - if prediction not in ["heads", "h", "tails", "t"]: - embed = Builder.create_embed( - Builder.ERROR, - user_name=ctx.author.name, - author_text=CONST.STRINGS["coinflip_invalid_prediction_author"], - description=CONST.STRINGS["coinflip_invalid_prediction_description"], - ) - await ctx.send(embed=embed) - return + if prediction in ["h", "head"]: + prediction = "heads" + elif prediction in ["t", "tail"]: + prediction = "tails" - flip_embed = Builder.create_embed( - Builder.INFO, - user_name=ctx.author.name, - author_text=CONST.STRINGS["coinflip_flipping_author"], - description=CONST.STRINGS["coinflip_flipping_description"], - ) - flip_message = await ctx.send(embed=flip_embed) + await self._coinflip(ctx, prediction) - await asyncio.sleep(1) + @app_commands.command(name="coinflip", description="Flip a coin. Optionally predict the outcome.") + @app_commands.guild_only() + @app_commands.choices( + prediction=[ + app_commands.Choice(name="Heads", value="heads"), + app_commands.Choice(name="Tails", value="tails"), + ], + ) + async def coinflip_slash( + self, + interaction: discord.Interaction, + prediction: app_commands.Choice[str] | None = None, + ) -> None: + """ + Flip a coin. Optionally predict the outcome. + + Parameters + ---------- + interaction : discord.Interaction + The interaction of the command. + prediction : app_commands.Choice[str], optional + The predicted outcome ('heads' or 'tails'). + """ + await self._coinflip(interaction, prediction.value if prediction else None) + + async def _coinflip(self, ctx: commands.Context[Luminara] | discord.Interaction, prediction: str | None) -> None: + if isinstance(ctx, commands.Context): + author = ctx.author + reply = ctx.reply + else: + author = ctx.user + reply = ctx.followup.send + + if author.id in ACTIVE_COINFLIPS: + raise LumiException(CONST.STRINGS["error_already_flipping_coin_description"]) + + ACTIVE_COINFLIPS[author.id] = True + + try: + result = random.choice(["heads", "tails"]) + + if prediction and prediction not in ["heads", "tails"]: + raise LumiException(CONST.STRINGS["coinflip_invalid_prediction_description"]) - for animation in ( - CONST.STRINGS["coinflip_flipping_animation_1"], - CONST.STRINGS["coinflip_flipping_animation_2"], - CONST.STRINGS["coinflip_flipping_animation_3"], - ): flip_embed = Builder.create_embed( Builder.INFO, - user_name=ctx.author.name, + user_name=author.name, author_text=CONST.STRINGS["coinflip_flipping_author"], - description=animation, + description=CONST.STRINGS["coinflip_flipping_description"], ) - await flip_message.edit(embed=flip_embed) - await asyncio.sleep(0.5) - if prediction: - predicted_correctly = (prediction.startswith("h") and result == "heads") or ( - prediction.startswith("t") and result == "tails" - ) - if predicted_correctly: + if isinstance(ctx, commands.Context): + flip_message = await reply(embed=flip_embed) + else: + await ctx.response.send_message(embed=flip_embed) + flip_message = await ctx.original_response() + + await asyncio.sleep(1) + + if flip_message is not None: + flip_embed.description = f"**{author.name}** " + CONST.STRINGS["coinflip_flipping_animation_1"] + await flip_message.edit(embed=flip_embed) + await asyncio.sleep(0.5) + + flip_embed.description = f"**{author.name}** " + CONST.STRINGS["coinflip_flipping_animation_2"] + await flip_message.edit(embed=flip_embed) + await asyncio.sleep(0.5) + + flip_embed.description = f"**{author.name}** " + CONST.STRINGS["coinflip_flipping_animation_3"] + await flip_message.edit(embed=flip_embed) + await asyncio.sleep(0.5) + + if prediction: + predicted_correctly = prediction == result + + embed_type = Builder.SUCCESS if predicted_correctly else Builder.ERROR + author_text = CONST.STRINGS[ + "coinflip_correct_prediction_author" if predicted_correctly else "coinflip_wrong_prediction_author" + ] + description = CONST.STRINGS[ + "coinflip_correct_prediction_description" + if predicted_correctly + else "coinflip_wrong_prediction_description" + ].format(result) + embed = Builder.create_embed( - Builder.SUCCESS, - user_name=ctx.author.name, - author_text=CONST.STRINGS["coinflip_correct_prediction_author"], - description=CONST.STRINGS["coinflip_correct_prediction_description"].format(result), + embed_type, + user_name=author.name, + author_text=author_text, + description=description, ) else: embed = Builder.create_embed( - Builder.ERROR, - user_name=ctx.author.name, - author_text=CONST.STRINGS["coinflip_wrong_prediction_author"], - description=CONST.STRINGS["coinflip_wrong_prediction_description"].format(result), + Builder.INFO, + user_name=author.name, + author_text=CONST.STRINGS["coinflip_result_author"], + description=CONST.STRINGS["coinflip_result_description"].format(result), ) - else: - embed = Builder.create_embed( - Builder.INFO, - user_name=ctx.author.name, - author_text=CONST.STRINGS["coinflip_result_author"], - description=CONST.STRINGS["coinflip_result_description"].format(result), - ) - await flip_message.edit(embed=embed) + if flip_message is not None: + await flip_message.edit(embed=embed) + finally: + del ACTIVE_COINFLIPS[author.id] async def setup(bot: Luminara) -> None: