From c547e8e247f909964b2daa151779ba89b5c84c8a Mon Sep 17 00:00:00 2001 From: Nathaniel Landau Date: Thu, 25 Jan 2024 22:42:39 -0500 Subject: [PATCH] fix(gameplay): roll correct traits (#112) fix(gameplay): fix bug rolling traits --- src/valentina/cogs/characters.py | 23 ++++++++++------------- src/valentina/cogs/gameplay.py | 16 ++++++++++------ src/valentina/cogs/macros.py | 17 +++++++---------- src/valentina/utils/__init__.py | 3 ++- src/valentina/utils/autocomplete.py | 9 +++++---- src/valentina/utils/console.py | 5 +++++ src/valentina/utils/converters.py | 12 ++++++++++++ tests/utils/test_autocomplete.py | 4 ++-- 8 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 src/valentina/utils/console.py diff --git a/src/valentina/cogs/characters.py b/src/valentina/cogs/characters.py index d835f746..dfd73d07 100644 --- a/src/valentina/cogs/characters.py +++ b/src/valentina/cogs/characters.py @@ -36,6 +36,7 @@ ValidClan, ValidImageURL, ValidTraitCategory, + ValidTraitFromID, ValidYYYYMMDD, ) from valentina.utils.helpers import ( @@ -529,12 +530,12 @@ async def add_trait( async def update_trait( self, ctx: ValentinaContext, - trait_index: Option( - int, # Index of the trait in character.traits - description="Trait to update", + trait: Option( + ValidTraitFromID, + name="trait_one", + description="First trait to roll", required=True, autocomplete=select_char_trait, - name="trait", ), new_value: Option( int, description="New value for the trait", required=True, min_value=0, max_value=20 @@ -561,9 +562,6 @@ async def update_trait( ) return - # Grab the CharacterTrait object - trait = character.traits[trait_index] - if not 0 <= new_value <= trait.max_value: await present_embed( ctx, @@ -594,11 +592,11 @@ async def update_trait( async def delete_trait( self, ctx: ValentinaContext, - trait_index: Option( - int, - description="Trait to delete", + trait: Option( + ValidTraitFromID, + name="trait_one", + description="First trait to roll", required=True, - name="trait", autocomplete=select_char_trait, ), hidden: Option( @@ -610,7 +608,6 @@ async def delete_trait( """Delete a trait from a character.""" # Fetch the active character and trait character = await ctx.fetch_active_character() - trait = character.traits[trait_index] # Guard statement: check permissions if not await ctx.can_manage_traits(character): @@ -635,7 +632,7 @@ async def delete_trait( if not is_confirmed: return - character.traits.pop(trait_index) + character.traits.remove(trait) await character.save() await trait.delete() diff --git a/src/valentina/cogs/gameplay.py b/src/valentina/cogs/gameplay.py index a0f2ff59..5fafc5ca 100644 --- a/src/valentina/cogs/gameplay.py +++ b/src/valentina/cogs/gameplay.py @@ -6,12 +6,14 @@ import discord from discord.commands import Option from discord.ext import commands +from loguru import logger from valentina.constants import DEFAULT_DIFFICULTY, DiceType from valentina.models import User from valentina.models.bot import Valentina, ValentinaContext from valentina.utils import random_num from valentina.utils.autocomplete import select_char_trait, select_char_trait_two, select_macro +from valentina.utils.converters import ValidTraitFromID from valentina.utils.perform_roll import perform_roll from valentina.views import present_embed @@ -55,15 +57,15 @@ async def throw( async def traits( self, ctx: ValentinaContext, - index1: Option( - int, + trait_one: Option( + ValidTraitFromID, name="trait_one", description="First trait to roll", required=True, autocomplete=select_char_trait, ), - index2: Option( - int, + trait_two: Option( + ValidTraitFromID, name="trait_two", description="Second trait to roll", required=True, @@ -79,11 +81,13 @@ async def traits( ) -> None: """Roll the total number of d10s for two given traits against a difficulty.""" character = await ctx.fetch_active_character() - trait_one = character.traits[index1] - trait_two = character.traits[index2] pool = trait_one.value + trait_two.value + logger.debug( + f"ROLL TRAITS: {trait_one.name} ({trait_one.id}) + {trait_two.name} ({trait_two.id}) = {pool}" + ) + await perform_roll( ctx, pool, diff --git a/src/valentina/cogs/macros.py b/src/valentina/cogs/macros.py index eb4fb36d..d185eb84 100644 --- a/src/valentina/cogs/macros.py +++ b/src/valentina/cogs/macros.py @@ -12,6 +12,7 @@ select_char_trait_two, select_macro, ) +from valentina.utils.converters import ValidTraitFromID from valentina.utils.helpers import truncate_string from valentina.views import MacroCreateModal, confirm_action, present_embed @@ -28,15 +29,15 @@ def __init__(self, bot: Valentina) -> None: async def create( self, ctx: ValentinaContext, - index1: Option( - int, + trait_one: Option( + ValidTraitFromID, name="trait_one", description="First trait to roll", required=True, autocomplete=select_char_trait, ), - index2: Option( - int, + trait_two: Option( + ValidTraitFromID, name="trait_two", description="Second trait to roll", required=True, @@ -52,15 +53,11 @@ async def create( Args: ctx (ValentinaContext): The context of the application. - index1 (int): The index for the first trait. - index2 (int): The index for the second trait. + trait_one (CharacterTrait): The index for the first trait. + trait_two (CharacterTrait): The index for the second trait. hidden (Option[bool]): Whether to make the result only to you (default true). """ user = await User.get(ctx.author.id, fetch_links=True) - character = await user.active_character(ctx.guild) - - trait_one = character.traits[index1] - trait_two = character.traits[index2] modal = MacroCreateModal( title=truncate_string("Enter the details for your macro", 45), diff --git a/src/valentina/utils/__init__.py b/src/valentina/utils/__init__.py index 605eb9d3..9754004b 100644 --- a/src/valentina/utils/__init__.py +++ b/src/valentina/utils/__init__.py @@ -1,6 +1,7 @@ """Utility functions for Valentina.""" +from .console import console from .helpers import random_num from .logging import InterceptHandler -__all__ = ["Context", "InterceptHandler", "random_num"] +__all__ = ["Context", "InterceptHandler", "console", "random_num"] diff --git a/src/valentina/utils/autocomplete.py b/src/valentina/utils/autocomplete.py index 61a65322..0a83e040 100644 --- a/src/valentina/utils/autocomplete.py +++ b/src/valentina/utils/autocomplete.py @@ -153,6 +153,7 @@ async def select_char_trait(ctx: discord.AutocompleteContext) -> list[OptionChoi """ # Fetch the active character user_object = await User.get(ctx.interaction.user.id, fetch_links=True) + if not ( active_character := await user_object.active_character( ctx.interaction.guild, raise_error=False @@ -165,8 +166,8 @@ async def select_char_trait(ctx: discord.AutocompleteContext) -> list[OptionChoi # Filter and return the character's traits return [ - OptionChoice(t.name, i) - for i, t in enumerate(active_character.traits) + OptionChoice(t.name, str(t.id)) + for t in active_character.traits if t.name.lower().startswith(argument.lower()) ][:MAX_OPTION_LIST_SIZE] @@ -196,8 +197,8 @@ async def select_char_trait_two(ctx: discord.AutocompleteContext) -> list[Option # Filter and return the character's traits return [ - OptionChoice(t.name, i) - for i, t in enumerate(active_character.traits) + OptionChoice(t.name, str(t.id)) + for t in active_character.traits if t.name.lower().startswith(ctx.options["trait_two"].lower()) ][:MAX_OPTION_LIST_SIZE] diff --git a/src/valentina/utils/console.py b/src/valentina/utils/console.py new file mode 100644 index 00000000..f670eefc --- /dev/null +++ b/src/valentina/utils/console.py @@ -0,0 +1,5 @@ +"""Rich console object for Valentina.""" + +from rich.console import Console + +console = Console() diff --git a/src/valentina/utils/converters.py b/src/valentina/utils/converters.py index 813c44e2..c5e654be 100644 --- a/src/valentina/utils/converters.py +++ b/src/valentina/utils/converters.py @@ -208,6 +208,18 @@ async def convert(self, ctx: commands.Context, argument: str) -> TraitCategory: raise BadArgument(msg) from e +class ValidTraitFromID(Converter): + """Convert a TraitCategory name to a TraitCategory enum.""" + + async def convert(self, ctx: commands.Context, argument: str) -> CharacterTrait: # noqa: ARG002 + """Validate and return a CharacterTrait from it's id.""" + try: + return await CharacterTrait.get(argument) + except KeyError as e: + msg = f"`{argument}` is not an existing trait id" + raise BadArgument(msg) from e + + class ValidYYYYMMDD(Converter): """Convert a string in the form of YYYY-MM-DD to a datetime object.""" diff --git a/tests/utils/test_autocomplete.py b/tests/utils/test_autocomplete.py index e73adc54..622d6adb 100644 --- a/tests/utils/test_autocomplete.py +++ b/tests/utils/test_autocomplete.py @@ -256,7 +256,7 @@ async def test_select_char_trait(mock_ctx1, user_factory, character_factory, tra # THEN the trait and its index is returned assert len(result) == 1 assert result[0].name == "Dexterity" - assert result[0].value == 0 + assert result[0].value == str(trait.id) @pytest.mark.drop_db() @@ -295,7 +295,7 @@ async def test_select_char_trait_two(mock_ctx1, user_factory, character_factory, # THEN the trait and its index is returned assert len(result) == 1 assert result[0].name == "Dexterity" - assert result[0].value == 0 + assert result[0].value == str(trait.id) @pytest.mark.drop_db()