Skip to content

Commit

Permalink
feat(diceroll): roll traits by name (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
natelandau authored Jun 20, 2023
1 parent 715f0a0 commit ae30c47
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 16 deletions.
3 changes: 1 addition & 2 deletions src/valentina/cogs/characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from valentina.character.traits import add_trait
from valentina.character.view_sheet import show_sheet
from valentina.character.views import BioModal
from valentina.models.constants import CharClass, TraitAreas
from valentina.models.constants import MAX_OPTION_LIST_SIZE, CharClass, TraitAreas
from valentina.utils.errors import (
CharacterClaimedError,
NoClaimError,
Expand All @@ -28,7 +28,6 @@
from valentina.views.embeds import ConfirmCancelView, present_embed

possible_classes = sorted([char_class.value for char_class in CharClass])
MAX_OPTION_LIST_SIZE = 25


class Characters(commands.Cog, name="Character"):
Expand Down
101 changes: 100 additions & 1 deletion src/valentina/cogs/gameplay.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,46 @@
from discord.ext import commands
from loguru import logger

from valentina import Valentina, user_svc
from valentina import Valentina, char_svc, user_svc
from valentina.models.constants import MAX_OPTION_LIST_SIZE
from valentina.models.dicerolls import DiceRoll
from valentina.utils.errors import NoClaimError, TraitNotFoundError
from valentina.views.embeds import present_embed
from valentina.views.roll_display import RollDisplay


async def trait_one_autocomplete(ctx: discord.ApplicationContext) -> list[str]:
"""Populates the autocomplete for the trait option."""
try:
character = char_svc.fetch_claim(ctx.interaction.guild.id, ctx.interaction.user.id)
except NoClaimError:
return ["No character claimed"]

traits = []
for trait in char_svc.fetch_all_character_traits(character, flat_list=True):
if trait.lower().startswith(ctx.options["trait_one"].lower()):
traits.append(trait)
if len(traits) >= MAX_OPTION_LIST_SIZE:
break
return traits


async def trait_two_autocomplete(ctx: discord.ApplicationContext) -> list[str]:
"""Populates the autocomplete for the trait option."""
try:
character = char_svc.fetch_claim(ctx.interaction.guild.id, ctx.interaction.user.id)
except NoClaimError:
return ["No character claimed"]

traits = []
for trait in char_svc.fetch_all_character_traits(character, flat_list=True):
if trait.lower().startswith(ctx.options["trait_two"].lower()):
traits.append(trait)
if len(traits) >= MAX_OPTION_LIST_SIZE:
break
return traits


class Roll(commands.Cog):
"""Commands used during gameplay."""

Expand Down Expand Up @@ -51,6 +86,70 @@ async def throw(
except ValueError as e:
await ctx.respond(f"Error rolling dice: {e}", ephemeral=True)

@roll.command(name="traits", description="Throw a roll based on trait names")
@logger.catch
async def traits(
self,
ctx: discord.ApplicationContext,
trait_one: Option(
str,
description="First trait to roll",
required=True,
autocomplete=trait_one_autocomplete,
),
trait_two: Option(
str,
description="Second trait to roll",
required=False,
autocomplete=trait_two_autocomplete,
default=None,
),
difficulty: Option(
int,
"The difficulty of the roll",
required=False,
default=6,
),
comment: Option(str, "A comment to display with the roll", required=False, default=None),
) -> None:
"""Roll the total number of d10s for two given traits against a difficulty."""
try:
character = char_svc.fetch_claim(ctx.guild.id, ctx.user.id)
trait_one_value = char_svc.fetch_trait_value(character, trait_one)
trait_two_value = char_svc.fetch_trait_value(character, trait_two) if trait_two else 0
pool = trait_one_value + trait_two_value

roll = DiceRoll(pool=pool, difficulty=difficulty, dice_size=10)
logger.debug(f"ROLL: {ctx.author.display_name} rolled {roll.roll}")
await RollDisplay(
ctx,
roll=roll,
comment=comment,
trait_one_name=trait_one,
trait_one_value=trait_one_value,
trait_two_name=trait_two,
trait_two_value=trait_two_value,
).display()

except NoClaimError:
await present_embed(
ctx=ctx,
title="Error: No character claimed",
description="You must claim a character before you can update its bio.\nTo claim a character, use `/character claim`.",
level="error",
ephemeral=True,
)
return
except TraitNotFoundError as e:
await present_embed(
ctx=ctx,
title="Error: Trait not found",
description=str(e),
level="error",
ephemeral=True,
)
return

@roll.command(description="Simple dice roll of any size.")
async def simple(
self,
Expand Down
3 changes: 3 additions & 0 deletions src/valentina/models/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
"""Constants for Valentina models."""
from enum import Enum

# maximum number of options in a discord select menu
MAX_OPTION_LIST_SIZE = 25


class MaxTraitValue(Enum):
"""Maximum value for a trait."""
Expand Down
10 changes: 5 additions & 5 deletions src/valentina/models/dicerolls.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,14 @@ def embed_color(self) -> int:
def takeaway(self) -> str:
"""The roll's main takeaway--i.e. "SUCCESS", "FAILURE", etc."""
if self.dice_type != DiceType.D10:
return f"Rolled {self.pool}d{self.dice_type.value}"
return "Dice roll"
if self.is_botch:
return f"{self.result} successes • BOTCH!"
return f"{self.result} SUCCESSES • BOTCH!"
if self.is_critical:
return f"{self.result} successes • CRITICAL SUCCESS!"
return f"{self.result} SUCCESSES • CRITICAL SUCCESS!"
if self.is_success:
return f"{self.result} successes"
return f"{self.result} SUCCESSES"
if self.is_failure:
return f"{self.result} successes"
return f"{self.result} SUCCESSES"

return None
36 changes: 28 additions & 8 deletions src/valentina/views/roll_display.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Display and manipulate roll outcomes."""
from datetime import datetime

import discord

from valentina.models.dicerolls import DiceRoll
Expand All @@ -7,40 +9,58 @@
class RollDisplay:
"""Display and manipulate roll outcomes."""

def __init__(self, ctx: discord.ApplicationContext, roll: DiceRoll, comment: str = None):
def __init__(
self,
ctx: discord.ApplicationContext,
roll: DiceRoll,
comment: str = None,
trait_one_name: str = None,
trait_one_value: int = 0,
trait_two_name: str = None,
trait_two_value: int = 0,
):
self.ctx = ctx
self.roll = roll
self.comment = comment
self.trait_one_name = trait_one_name
self.trait_one_value = trait_one_value
self.trait_two_name = trait_two_name
self.trait_two_value = trait_two_value

async def get_embed(self) -> discord.Embed:
"""The graphical representation of the roll."""
title = self.roll.takeaway

embed = discord.Embed(title=title, colour=self.roll.embed_color)
embed.set_author(
name=self.ctx.author.display_name, icon_url=self.ctx.author.display_avatar.url
)

# Thumbnail
embed.set_thumbnail(
url=self.roll.thumbnail_url,
)
embed.description = f"\u200b\n{self.ctx.author.mention} rolled {self.roll.pool}{self.roll.dice_type.name.lower()}"

roll_string = ""
for die in self.roll.roll:
roll_string += f"`{die}` "

# Fields
embed.add_field(name="\u200b", value="**ROLL DETAILS**", inline=False)
embed.add_field(name="Roll", value=roll_string, inline=False)
if self.roll.dice_type.name == "D10":
embed.add_field(name="Pool", value=str(self.roll.pool), inline=True)
embed.add_field(name="Difficulty", value=str(self.roll.difficulty), inline=True)
embed.add_field(name="Dice Type", value=str(self.roll.dice_type.name), inline=True)

# Footer
if self.comment is not None:
embed.set_footer(text=self.comment)
if self.trait_one_name:
embed.add_field(name="\u200b", value="**Rolled Traits**", inline=False)
embed.add_field(name=self.trait_one_name, value=str(self.trait_one_value), inline=True)

if self.trait_two_name:
embed.add_field(name=self.trait_two_name, value=str(self.trait_two_value), inline=True)

if self.comment:
embed.add_field(name="\u200b", value=f"**Comment**\n {self.comment}", inline=False)

embed.timestamp = datetime.now()
return embed

async def display(self) -> None:
Expand Down

0 comments on commit ae30c47

Please sign in to comment.