Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chargen): correctly create werewolves and changelings #186

Merged
merged 1 commit into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
676 changes: 473 additions & 203 deletions src/valentina/constants.py

Large diffs are not rendered by default.

157 changes: 140 additions & 17 deletions src/valentina/controllers/rng_chargen.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
RNGCharLevel,
TraitCategory,
VampireClan,
WerewolfAuspice,
WerewolfBreed,
WerewolfTribe,
)
from valentina.models import Campaign, Character, CharacterSheetSection, CharacterTrait, User
from valentina.utils import random_num
Expand Down Expand Up @@ -258,6 +261,7 @@
character = await self.random_backgrounds(character)
character = await self.random_willpower(character)
character = await self.random_hunter_traits(character)
character = await self.random_werewolf_traits(character)
return await self.concept_special_abilities(character)

async def random_attributes(self, character: Character) -> Character:
Expand Down Expand Up @@ -570,28 +574,39 @@
Character: The updated character with newly generated willpower
and potentially humanity traits.
"""
# Because Hunters, werewolves, and changelings have their own willpower system, we don't need to generate it for them here.
if character.char_class in (CharClass.HUNTER, CharClass.WEREWOLF, CharClass.CHANGELING):
return character

logger.debug(f"Generate willpower values for {character.name}")

if not any(x.name for x in character.traits if x.name == "Self-Control"): # type: ignore [attr-defined]
return character
willpower = CharacterTrait(

Check warning on line 584 in src/valentina/controllers/rng_chargen.py

View check run for this annotation

Codecov / codecov/patch

src/valentina/controllers/rng_chargen.py#L584

Added line #L584 was not covered by tests
name="Willpower",
value=4, # Default to 4 if no Self-Control trait is found
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=10,
)
else:
courage = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Courage"
)
self_control = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Self-Control"
)
conscience = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Conscience"
)

courage = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Courage"
)
self_control = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Self-Control"
)
conscience = next(
x for x in cast(list[CharacterTrait], character.traits) if x.name == "Conscience"
)
willpower = CharacterTrait(
name="Willpower",
value=self_control.value + courage.value,
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=10,
)

willpower = CharacterTrait(
name="Willpower",
value=self_control.value + courage.value,
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=10,
)
await willpower.insert()
character.traits.append(willpower)

Expand Down Expand Up @@ -721,3 +736,111 @@
)
await character.save()
return character

async def random_werewolf_traits(self, character: Character) -> Character:
"""Randomly generate werewolf and changeling breed, traits, etc for the character.

TODO: Disambiguate Changelings and Werewolves

Args:
character (Character): The character for which to generate werewolf traits.

Returns:
Character: The updated character with newly generated werewolf traits.
"""
if character.char_class not in (CharClass.WEREWOLF, CharClass.CHANGELING):
return character

breed = WerewolfBreed.random_member()
character.breed = breed.name
auspice = WerewolfAuspice.random_member()
character.auspice = auspice.name
tribe = WerewolfTribe.random_member()
character.tribe = tribe.name

character.totem = tribe.value.totem

willpower = CharacterTrait(
name="Willpower",
value=tribe.value.starting_willpower,
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=10,
)
await willpower.insert()
character.traits.append(willpower)

gnosis = CharacterTrait(
name="Gnosis",
value=breed.value.starting_gnosis,
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=10,
)
await gnosis.insert()
character.traits.append(gnosis)

rage = CharacterTrait(
name="Rage",
value=auspice.value.starting_rage,
character=str(character.id),
category_name=TraitCategory.OTHER.name,
max_value=get_max_trait_value("Rage", TraitCategory.OTHER.name),
)
await rage.insert()
character.traits.append(rage)

rank = CharacterTrait(
name="Rank",
value=1,
character=str(character.id),
category_name=TraitCategory.RENOWN.name,
max_value=5,
)
await rank.insert()
character.traits.append(rank)

glory = CharacterTrait(
name="Glory",
value=auspice.value.starting_glory,
character=str(character.id),
category_name=TraitCategory.RENOWN.name,
max_value=get_max_trait_value("Glory", TraitCategory.RENOWN.name),
)
await glory.insert()
character.traits.append(glory)

honor = CharacterTrait(
name="Honor",
value=auspice.value.starting_honor,
character=str(character.id),
category_name=TraitCategory.RENOWN.name,
max_value=get_max_trait_value("Honor", TraitCategory.RENOWN.name),
)
await honor.insert()
character.traits.append(honor)

wisdom = CharacterTrait(
name="Wisdom",
value=auspice.value.starting_wisdom,
character=str(character.id),
category_name=TraitCategory.RENOWN.name,
max_value=get_max_trait_value("Wisdom", TraitCategory.RENOWN.name),
)
await wisdom.insert()
character.traits.append(wisdom)

gifts = set(auspice.value.starting_gifts + breed.value.starting_gifts)
for gift in gifts:
trait = CharacterTrait(
name=gift,
value=1,
character=str(character.id),
category_name=TraitCategory.GIFTS.name,
max_value=1,
)
await trait.insert()
character.traits.append(trait)

await character.save()
return character
13 changes: 1 addition & 12 deletions src/valentina/discord/characters/chargen.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,20 +560,9 @@ async def present_character_choices(self) -> None:

# Generate 3 characters
characters = [
await self.engine.generate_base_character(chargen_character=True) for _ in range(3)
await self.engine.generate_full_character(chargen_character=True) for _ in range(3)
]

# TODO: Add traits to each character
for character in characters:
await self.engine.random_attributes(character)
await self.engine.random_abilities(character)
await self.engine.random_disciplines(character)
await self.engine.random_virtues(character)
await self.engine.random_backgrounds(character)
await self.engine.random_willpower(character)
await self.engine.random_hunter_traits(character)
await self.engine.concept_special_abilities(character)

# Add the pages to the paginator
description = f"## Created {len(characters)} {p.plural_noun('character', len(characters))} for you to choose from\n"
character_list = [
Expand Down
5 changes: 4 additions & 1 deletion src/valentina/discord/views/character_sheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __embed1( # noqa: C901
inline=True,
)

if character.char_class == CharClass.WEREWOLF:
if character.char_class in (CharClass.WEREWOLF, CharClass.CHANGELING):
embed.add_field(
name="Tribe", value=character.tribe.title() if character.tribe else "-", inline=True
)
Expand All @@ -98,6 +98,9 @@ def __embed1( # noqa: C901
embed.add_field(
name="Breed", value=character.breed.title() if character.breed else "-", inline=True
)
embed.add_field(
name="totem", value=character.totem.title() if character.totem else "-", inline=True
)

# Add the trait sections to the sheet
# Sort by character sheet section
Expand Down
21 changes: 15 additions & 6 deletions src/valentina/models/character.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class Character(Document):

# Profile
age: int | None = None
auspice: str | None = None
auspice: str | None = None # WerewolfAuspice enum name
bio: str | None = None
breed: str | None = None # WerewolfBreed enum name or other
clan_name: str | None = None # VampireClan enum name
Expand All @@ -164,7 +164,8 @@ class Character(Document):
nature: str | None = None
sire: str | None = None
tradition: str | None = None
tribe: str | None = None
tribe: str | None = None # WerewolfTribe enum name or other
totem: str | None = None

@before_event(Insert, Replace, Save, Update, SaveChanges)
async def update_modified_date(self) -> None:
Expand Down Expand Up @@ -431,23 +432,31 @@ def sheet_section_top_items(self) -> dict[str, str]:
self.auspice
if self.auspice
else "-"
if self.char_class_name.lower() == "werewolf"
if self.char_class_name.lower() in ("werewolf", "changeling")
else None,
),
(
"Breed",
self.breed
if self.breed
else "-"
if self.char_class_name.lower() == "werewolf"
if self.char_class_name.lower() in ("werewolf", "changeling")
else None,
),
(
"Totem",
self.totem
if self.totem
else "-"
if self.char_class_name.lower() in ("werewolf", "changeling")
else None,
),
(
"Clan",
self.clan_name
if self.clan_name
else "-"
if self.char_class_name.lower() == "vampire"
if self.char_class_name.lower() in ("vampire")
else None,
),
(
Expand Down Expand Up @@ -481,7 +490,7 @@ def sheet_section_top_items(self) -> dict[str, str]:
self.tribe
if self.tribe
else "-"
if self.char_class_name.lower() == "werewolf"
if self.char_class_name.lower() in ("werewolf", "changeling")
else None,
),
("Date of Birth", self.dob.strftime("%Y-%m-%d") if self.dob else "-"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class VampireClassSpecifics(QuartForm):


class WerewolfClassSpecifics(QuartForm):
"""Class specific items for Werewolfs."""
"""Class specific items for Werewolves."""

title = "Werewolf Class Specifics"
prefix = "werewolf"
Expand Down
12 changes: 1 addition & 11 deletions src/valentina/webui/blueprints/character_create/route_rng.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,9 @@ async def get(self) -> str:

# Create three characters for the user to choose from
characters = [
await chargen.generate_base_character(chargen_character=True) for _ in range(3)
await chargen.generate_full_character(chargen_character=True) for _ in range(3)
]

for character in characters:
await chargen.random_attributes(character)
await chargen.random_abilities(character)
await chargen.random_disciplines(character)
await chargen.random_virtues(character)
await chargen.random_backgrounds(character)
await chargen.random_willpower(character)
await chargen.random_hunter_traits(character)
await chargen.concept_special_abilities(character)

# Add the created characters to the session so they can be acted upon in the next step
# use a dictionary with the index as the key
session["RNG_DRAFT_CHARACTERS"] = {
Expand Down
19 changes: 18 additions & 1 deletion tests/controllers/test_chargen.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,18 @@
RNGCharLevel,
TraitCategory,
VampireClan,
WerewolfAuspice,
WerewolfBreed,
WerewolfTribe,
)
from valentina.controllers import RNGCharGen
from valentina.models import Character, CharacterTrait


@pytest.mark.drop_db
@pytest.mark.parametrize(("char_class"), [(None), (CharClass.VAMPIRE), (CharClass.HUNTER)])
@pytest.mark.parametrize(
("char_class"), [(None), (CharClass.VAMPIRE), (CharClass.HUNTER), (CharClass.WEREWOLF)]
)
async def test_generate_full_character(
user_factory, campaign_factory, mock_ctx1, mocker, char_class
):
Expand Down Expand Up @@ -73,6 +78,18 @@ async def test_generate_full_character(
== 1
)

if character.char_class_name == CharClass.WEREWOLF.name:
assert character.tribe in WerewolfTribe.__members__
assert character.auspice in WerewolfAuspice.__members__
assert character.breed in WerewolfBreed.__members__
for trait in ("Rage", "Gnosis", "Willpower", "Rank", "Glory", "Honor", "Wisdom"):
assert (
await CharacterTrait.find(
CharacterTrait.character == str(character.id), CharacterTrait.name == trait
).count()
== 1
)

assert await Character.get(character.id, fetch_links=True) == character


Expand Down
Loading