Skip to content

Commit

Permalink
feat(webui): storytellers can update traits for any character (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
natelandau authored Oct 23, 2024
1 parent 2f23b70 commit d16bd4b
Show file tree
Hide file tree
Showing 18 changed files with 422 additions and 381 deletions.
27 changes: 16 additions & 11 deletions src/valentina/controllers/trait_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def __init__(self, character: Character, user: User) -> None:
self.character = character
self.user = user

def _can_trait_be_upgraded(self, trait: CharacterTrait, amount: int = 1) -> bool:
def can_trait_be_upgraded(self, trait: CharacterTrait, amount: int = 1) -> bool:
"""Check if the trait can be upgraded.
Args:
Expand All @@ -34,7 +34,7 @@ def _can_trait_be_upgraded(self, trait: CharacterTrait, amount: int = 1) -> bool

return True

def _can_trait_be_downgraded(self, trait: CharacterTrait, amount: int = 1) -> bool:
def can_trait_be_downgraded(self, trait: CharacterTrait, amount: int = 1) -> bool:
"""Check if the trait can be downgraded.
Args:
Expand All @@ -50,16 +50,21 @@ def _can_trait_be_downgraded(self, trait: CharacterTrait, amount: int = 1) -> bo

return True

async def _save_trait(self, trait: CharacterTrait) -> None:
"""Saves the updates to the trait and adds the trait to the character if it's not already there.
async def _save_trait(self, trait: CharacterTrait) -> CharacterTrait:
"""Saves the updates to the trait and adds the trait to the character if it's not already there. We call the character.add_trait() method to confirm the trait is linked to the character and does not already exist.
Args:
trait (CharacterTrait): The trait to add.
"""
await trait.save()
Returns:
CharacterTrait: The saved trait.
Raises:
errors.TraitExistsError: If the trait already exists. Inherited from the character.add_trait() method.
"""
await self.character.fetch_all_links()
await self.character.add_trait(character_trait=trait)
await self.character.add_trait(trait)
return trait

def cost_to_upgrade(self, trait: CharacterTrait, amount: int = 1) -> int:
"""Calculate the cost to upgrade a trait.
Expand Down Expand Up @@ -145,7 +150,7 @@ async def downgrade_with_freebie(
Returns:
CharacterTrait: The downgraded trait.
"""
if self._can_trait_be_downgraded(trait, amount):
if self.can_trait_be_downgraded(trait, amount):
savings_from_downgrade = self.savings_from_downgrade(trait, amount)

self.character.freebie_points = self.character.freebie_points + savings_from_downgrade
Expand All @@ -169,7 +174,7 @@ async def downgrade_with_xp(
Returns:
CharacterTrait: The downgraded trait.
"""
if self._can_trait_be_downgraded(trait, amount):
if self.can_trait_be_downgraded(trait, amount):
savings_from_downgrade = self.savings_from_downgrade(trait, amount)

await self.user.add_campaign_xp(
Expand All @@ -190,7 +195,7 @@ async def upgrade_with_freebie(self, trait: CharacterTrait, amount: int = 1) ->
Returns:
CharacterTrait: The upgraded trait.
"""
self._can_trait_be_upgraded(trait, amount)
self.can_trait_be_upgraded(trait, amount)

cost_to_upgrade = self.cost_to_upgrade(trait, amount)

Expand Down Expand Up @@ -219,7 +224,7 @@ async def upgrade_with_xp(
Returns:
CharacterTrait: The upgraded trait.
"""
self._can_trait_be_upgraded(trait, amount)
self.can_trait_be_upgraded(trait, amount)

cost_to_upgrade = self.cost_to_upgrade(trait, amount)

Expand Down
12 changes: 9 additions & 3 deletions src/valentina/discord/cogs/characters.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
present_embed,
show_sheet,
)
from valentina.models import AWSService, Character, CharacterSheetSection, User
from valentina.models import AWSService, Character, CharacterSheetSection, CharacterTrait, User
from valentina.utils import errors
from valentina.utils.helpers import (
fetch_data_from_url,
Expand Down Expand Up @@ -488,8 +488,14 @@ async def add_trait(
)
if not is_confirmed:
return

await character.add_trait(category, name.title(), value, max_value=max_value)
trait = CharacterTrait(
name=name.title(),
category_name=category.name,
value=value,
max_value=max_value or 5,
character=str(character.id),
)
await character.add_trait(trait)

await interaction.edit_original_response(embed=confirmation_embed, view=None)

Expand Down
11 changes: 9 additions & 2 deletions src/valentina/discord/cogs/storyteller.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
sheet_embed,
show_sheet,
)
from valentina.models import AWSService, Character, User
from valentina.models import AWSService, Character, CharacterTrait, User
from valentina.utils.helpers import (
fetch_data_from_url,
)
Expand Down Expand Up @@ -459,7 +459,14 @@ async def add_trait(
if not is_confirmed:
return

await character.add_trait(category, name.title(), value, max_value=max_value)
trait = CharacterTrait(
name=name.title(),
category_name=category.name,
value=value,
max_value=max_value,
character=str(character.id),
)
await character.add_trait(trait)

await interaction.edit_original_response(embed=confirmation_embed, view=None)

Expand Down
80 changes: 21 additions & 59 deletions src/valentina/models/character.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from valentina.models.aws import AWSService
from valentina.utils import errors
from valentina.utils.helpers import get_max_trait_value, num_to_circles, time_now
from valentina.utils.helpers import num_to_circles, time_now

from .note import Note

Expand Down Expand Up @@ -269,28 +269,12 @@ async def add_image(self, extension: str, data: bytes) -> str: # pragma: no cov

async def add_trait(
self,
category: TraitCategory | None = None,
name: str | None = None,
value: int | None = 0,
max_value: int | None = None,
display_on_sheet: bool = True,
is_custom: bool = True,
character_trait: CharacterTrait | None = None,
trait: "CharacterTrait",
) -> "CharacterTrait":
"""Create a new trait for the character.
Add a new trait to the character's list of traits. Check if the trait already exists,
determine if it's a custom trait, and set the appropriate maximum value. Save the new
trait to the database and update the character's trait list.
"""Associate a trait with the character.
Args:
category (TraitCategory): The category of the trait.
name (str): The name of the trait.
value (int): The initial value of the trait.
max_value (int | None, optional): The maximum value for the trait. Defaults to None.
display_on_sheet (bool, optional): Whether to display the trait on the character sheet. Defaults to True.
is_custom (bool, optional): Whether the trait is custom. Defaults to True.
character_trait (CharacterTrait): An existing trait object to add to the character. Defaults to None.
trait (CharacterTrait): The trait to add to the character.
Returns:
CharacterTrait: The newly created trait object.
Expand All @@ -300,50 +284,28 @@ async def add_trait(
"""
await self.fetch_all_links()

if character_trait:
if character_trait.character != str(self.id):
character_trait.character = str(self.id)

await character_trait.save()

if character_trait not in self.traits:
self.traits.append(character_trait)
await self.save()
return character_trait
for existing_trait in cast(list[CharacterTrait], self.traits):
# If the trait already exists in the character's trait list, return it
if trait.id == existing_trait.id:
await trait.save()
return trait

if not category or not name or not value:
msg = "Category, name, and value are required to create a new trait."
raise ValueError(msg)
# Because a user can manually create a trait with the same name, we need to check for that as well
if (
trait.name.lower() == existing_trait.name.lower()
and trait.category_name == existing_trait.category_name
):
msg = f"Trait named '{trait.name}' already exists in category '{trait.category_name}' for character '{self.name}'"
raise errors.TraitExistsError(msg)

# Check if the trait already exists
for trait in cast(list[CharacterTrait], self.traits):
if trait.name == name and trait.category_name == category.name.upper():
raise errors.TraitExistsError

# Check if the trait is custom
if name.lower() in [x.lower() for x in category.value.COMMON] + [
x.lower() for x in getattr(category.value, self.char_class_name, [])
]:
is_custom = False
max_value = get_max_trait_value(name, category.name)

# Create the new trait
new_trait = CharacterTrait(
category_name=category.name,
character=str(self.id),
name=name,
value=value,
display_on_sheet=display_on_sheet,
is_custom=is_custom,
max_value=max_value or get_max_trait_value(name, category.name),
)
await new_trait.save()
if trait.character != str(self.id):
trait.character = str(self.id)

# Add the new trait to the character
self.traits.append(new_trait)
await trait.save()
self.traits.append(trait)
await self.save()

return new_trait
return trait

async def associate_with_campaign(self, new_campaign: "Campaign") -> bool:
"""Associate a character with a campaign.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ async def get(self) -> str:
character_type=request.args.get("character_type", "player"),
campaign_id=request.args.get("campaign_id", session.get("ACTIVE_CAMPAIGN_ID", "")),
),
error_msg=request.args.get("error_msg", ""),
success_msg=request.args.get("success_msg", ""),
info_msg=request.args.get("info_msg", ""),
warning_msg=request.args.get("warning_msg", ""),
)

async def post(self) -> str | Response:
Expand Down Expand Up @@ -454,6 +458,9 @@ async def post(self, character_id: str) -> str | Response:
url_for(
"character_view.view",
character_id=character_id,
success_msg="Character created successfully!",
error_msg=request.args.get("error_msg", ""),
success_msg=request.args.get("success_msg", ""),
info_msg=request.args.get("info_msg", ""),
warning_msg=request.args.get("warning_msg", ""),
)
)
4 changes: 4 additions & 0 deletions src/valentina/webui/blueprints/character_create/route_rng.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ async def get(self) -> str:
remaining_xp=remaining_xp,
rng_characters=characters,
character_type=request.args.get("character_type", "player"),
error_msg=request.args.get("error_msg", ""),
success_msg=request.args.get("success_msg", ""),
info_msg=request.args.get("info_msg", ""),
warning_msg=request.args.get("warning_msg", ""),
)

async def post(self) -> str | Response:
Expand Down
15 changes: 13 additions & 2 deletions src/valentina/webui/blueprints/character_edit/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,22 @@

blueprint.add_url_rule(
"/character/<string:character_id>/spendfreebie",
view_func=SpendPoints.as_view("freebie", spend_type=SpendPointsType.FREEBIE),
view_func=SpendPoints.as_view(
SpendPointsType.FREEBIE.value, spend_type=SpendPointsType.FREEBIE
),
methods=["GET", "POST"],
)
blueprint.add_url_rule(
"/character/<string:character_id>/spendexperience",
view_func=SpendPoints.as_view("experience", spend_type=SpendPointsType.EXPERIENCE),
view_func=SpendPoints.as_view(
SpendPointsType.EXPERIENCE.value, spend_type=SpendPointsType.EXPERIENCE
),
methods=["GET", "POST"],
)
blueprint.add_url_rule(
"/character/<string:character_id>/spendstoryteller",
view_func=SpendPoints.as_view(
SpendPointsType.STORYTELLER.value, spend_type=SpendPointsType.STORYTELLER
),
methods=["GET", "POST"],
)
Loading

0 comments on commit d16bd4b

Please sign in to comment.