improve conditions test

This commit is contained in:
evilchili 2024-07-13 13:08:47 -07:00
parent e2ff1eb027
commit b68dda2b77
3 changed files with 49 additions and 19 deletions

View File

@ -7,7 +7,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
from ttfrog.db.base import BaseObject, SlugMixin from ttfrog.db.base import BaseObject, SlugMixin
from ttfrog.db.schema.classes import CharacterClass, ClassFeature from ttfrog.db.schema.classes import CharacterClass, ClassFeature
from ttfrog.db.schema.constants import DamageType, Defenses from ttfrog.db.schema.constants import DamageType, Defenses
from ttfrog.db.schema.modifiers import Condition, Modifier, ModifierMixin, Stat from ttfrog.db.schema.modifiers import Modifier, ModifierMixin, Stat
from ttfrog.db.schema.skill import Skill from ttfrog.db.schema.skill import Skill
__all__ = [ __all__ = [
@ -32,6 +32,7 @@ def skill_creator(fields):
return fields return fields
return CharacterSkillMap(**fields) return CharacterSkillMap(**fields)
def condition_creator(fields): def condition_creator(fields):
if isinstance(fields, CharacterConditionMap): if isinstance(fields, CharacterConditionMap):
return fields return fields
@ -165,14 +166,16 @@ class CharacterClassFeatureMap(BaseObject):
uselist=False, uselist=False,
) )
class CharacterConditionMap(BaseObject): class CharacterConditionMap(BaseObject):
__tablename__ = "character_condition_map" __tablename__ = "character_condition_map"
__table_args__ = (UniqueConstraint("condition_id", "character_id"), ) __table_args__ = (UniqueConstraint("condition_id", "character_id"),)
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True) id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
condition_id: Mapped[int] = mapped_column(ForeignKey("condition.id")) condition_id: Mapped[int] = mapped_column(ForeignKey("condition.id"))
character_id: Mapped[int] = mapped_column(ForeignKey("character.id"), nullable=True, default=None) character_id: Mapped[int] = mapped_column(ForeignKey("character.id"), nullable=True, default=None)
condition = relationship("Condition", lazy="immediate") condition = relationship("Condition", lazy="immediate")
class Character(BaseObject, SlugMixin, ModifierMixin): class Character(BaseObject, SlugMixin, ModifierMixin):
__tablename__ = "character" __tablename__ = "character"
@ -207,9 +210,15 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
nullable=False, default=10, info={"min": 0, "max": 30, "modifiable_class": Stat} nullable=False, default=10, info={"min": 0, "max": 30, "modifiable_class": Stat}
) )
_actions_per_turn: Mapped[int] = mapped_column(nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}) _actions_per_turn: Mapped[int] = mapped_column(
_bonus_actions_per_turn: Mapped[int] = mapped_column(nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}) nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}
_reactions_per_turn: Mapped[int] = mapped_column(nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}) )
_bonus_actions_per_turn: Mapped[int] = mapped_column(
nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}
)
_reactions_per_turn: Mapped[int] = mapped_column(
nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}
)
vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0, "modifiable": True}) vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0, "modifiable": True})
exhaustion: Mapped[int] = mapped_column(nullable=False, default=0, info={"min": 0, "max": 5}) exhaustion: Mapped[int] = mapped_column(nullable=False, default=0, info={"min": 0, "max": 5})
@ -220,7 +229,9 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
_skills = relationship("CharacterSkillMap", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate") _skills = relationship("CharacterSkillMap", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate")
skills = association_proxy("_skills", "skill", creator=skill_creator) skills = association_proxy("_skills", "skill", creator=skill_creator)
_conditions = relationship("CharacterConditionMap", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate") _conditions = relationship(
"CharacterConditionMap", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate"
)
conditions = association_proxy("_conditions", "condition", creator=condition_creator) conditions = association_proxy("_conditions", "condition", creator=condition_creator)
character_class_feature_map = relationship("CharacterClassFeatureMap", cascade="all,delete,delete-orphan") character_class_feature_map = relationship("CharacterClassFeatureMap", cascade="all,delete,delete-orphan")

View File

@ -1,13 +1,14 @@
from collections import defaultdict from collections import defaultdict
from typing import Any, Union from typing import Any, Union
from sqlalchemy import ForeignKey, UniqueConstraint, String from sqlalchemy import ForeignKey, String, UniqueConstraint
from sqlalchemy.ext.declarative import declared_attr from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.orm import Mapped, mapped_column, relationship
# from sqlalchemy.ext.associationproxy import association_proxy
from ttfrog.db.base import BaseObject from ttfrog.db.base import BaseObject
# from sqlalchemy.ext.associationproxy import association_proxy
class Modifiable: class Modifiable:
def __new__(cls, base, modified=None): def __new__(cls, base, modified=None):
@ -334,7 +335,7 @@ class Condition(BaseObject):
return self.name return self.name
def __repr__(self): def __repr__(self):
mods = '' mods = ""
if self._modifiers: if self._modifiers:
mods = "\n" + "\n".join([f" - {mod}" for mod in self._modifiers]) mods = "\n" + "\n".join([f" - {mod}" for mod in self._modifiers])
return f"{self.name}{mods}" return f"{self.name}{mods}"

View File

@ -1,7 +1,6 @@
import json import json
from ttfrog.db import schema from ttfrog.db import schema
from ttfrog.db.schema.conditions import conditions
from ttfrog.db.schema.constants import Conditions, DamageType, Defenses from ttfrog.db.schema.constants import Conditions, DamageType, Defenses
@ -227,7 +226,9 @@ def test_modifiers(db, bootstrap):
# test sets of modifiers from conditions are applied when a condition is active # test sets of modifiers from conditions are applied when a condition is active
incapacitated = schema.Condition(name="incapacitated") incapacitated = schema.Condition(name="incapacitated")
incapacitated.add_modifier(schema.Modifier(target="actions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(schema.Modifier(target="actions_per_turn", absolute_value=0, name="Incapacitated"))
incapacitated.add_modifier(schema.Modifier(target="bonus_actions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(
schema.Modifier(target="bonus_actions_per_turn", absolute_value=0, name="Incapacitated")
)
incapacitated.add_modifier(schema.Modifier(target="reactions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(schema.Modifier(target="reactions_per_turn", absolute_value=0, name="Incapacitated"))
db.add_or_update(incapacitated) db.add_or_update(incapacitated)
assert carl.actions_per_turn == 1 assert carl.actions_per_turn == 1
@ -421,32 +422,47 @@ def test_condition_immunity(db, bootstrap):
db.add_or_update(carl) db.add_or_update(carl)
assert carl.has_condition(poisoned) assert carl.has_condition(poisoned)
def test_partial_immunities(db, bootstrap): def test_partial_immunities(db, bootstrap):
""" """
Test that individual modifiers applied by a condition can be negated even if not immune to the condition. Test that individual modifiers applied by a condition can be negated even if not immune to the condition.
""" """
with db.transaction(): with db.transaction():
tiefling = db.Ancestry.filter_by(name="tiefling").one() # Create some modifiers and conditions for this test
carl = schema.Character(name="Carl", ancestry=tiefling) fly = schema.Modifier(target="fly_speed", absolute_value=30, name="Fly Spell")
cannot_move = schema.Modifier(name="Cannot Move (Petrified", target="speed", absolute_value=0)
poisoned = schema.Condition(name=DamageType.poison) poisoned = schema.Condition(name=DamageType.poison)
poison_immunity = schema.Modifier("Poison Immunity", target=DamageType.poison, new_value=Defenses.immune) poison_immunity = schema.Modifier("Poison Immunity", target=DamageType.poison, new_value=Defenses.immune)
# incapacitated applies several modifiers
incapacitated = schema.Condition(name=Conditions.incapacitated) incapacitated = schema.Condition(name=Conditions.incapacitated)
incapacitated.add_modifier(schema.Modifier(target="actions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(schema.Modifier(target="actions_per_turn", absolute_value=0, name="Incapacitated"))
incapacitated.add_modifier(schema.Modifier(target="bonus_actions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(
schema.Modifier(target="bonus_actions_per_turn", absolute_value=0, name="Incapacitated")
)
incapacitated.add_modifier(schema.Modifier(target="reactions_per_turn", absolute_value=0, name="Incapacitated")) incapacitated.add_modifier(schema.Modifier(target="reactions_per_turn", absolute_value=0, name="Incapacitated"))
# petrified incapacitates you, and makes you immune to poison # petrified applies several modifiers but also incapacitates you!
petrified = schema.Condition(name=Conditions.petrified) petrified = schema.Condition(name=Conditions.petrified)
assert petrified.add_modifier(poison_immunity) assert petrified.add_modifier(poison_immunity)
assert petrified.add_modifier(cannot_move)
assert petrified.add_condition(incapacitated) assert petrified.add_condition(incapacitated)
db.add_or_update([poisoned, poison_immunity, incapacitated, petrified]) db.add_or_update([fly, cannot_move, poisoned, poison_immunity, incapacitated, petrified])
# hi carl
tiefling = db.Ancestry.filter_by(name="tiefling").one()
carl = schema.Character(name="Carl", ancestry=tiefling)
# carl casts fly!
assert carl.fly_speed is None
assert carl.add_modifier(fly)
assert carl.fly_speed == carl.speed == carl.ancestry.speed
db.add_or_update(carl)
# poison carl # poison carl
carl.add_condition(poisoned)
db.add_or_update(carl)
assert not carl.immune(DamageType.poison) assert not carl.immune(DamageType.poison)
assert carl.add_condition(poisoned)
db.add_or_update(carl)
# petrify carl # petrify carl
assert not carl.immune(Conditions.petrified) assert not carl.immune(Conditions.petrified)
@ -458,6 +474,8 @@ def test_partial_immunities(db, bootstrap):
# but carl still can't move # but carl still can't move
assert carl.actions_per_turn == 0 assert carl.actions_per_turn == 0
assert carl.speed == 0
assert carl.fly_speed == 0
# greater restoration ftw! # greater restoration ftw!
assert carl.remove_condition(petrified) assert carl.remove_condition(petrified)