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.schema.classes import CharacterClass, ClassFeature
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
__all__ = [
@ -32,6 +32,7 @@ def skill_creator(fields):
return fields
return CharacterSkillMap(**fields)
def condition_creator(fields):
if isinstance(fields, CharacterConditionMap):
return fields
@ -165,14 +166,16 @@ class CharacterClassFeatureMap(BaseObject):
uselist=False,
)
class CharacterConditionMap(BaseObject):
__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)
condition_id: Mapped[int] = mapped_column(ForeignKey("condition.id"))
character_id: Mapped[int] = mapped_column(ForeignKey("character.id"), nullable=True, default=None)
condition = relationship("Condition", lazy="immediate")
class Character(BaseObject, SlugMixin, ModifierMixin):
__tablename__ = "character"
@ -207,9 +210,15 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
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})
_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})
_actions_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})
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 = 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)
character_class_feature_map = relationship("CharacterClassFeatureMap", cascade="all,delete,delete-orphan")

View File

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

View File

@ -1,7 +1,6 @@
import json
from ttfrog.db import schema
from ttfrog.db.schema.conditions import conditions
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
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="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"))
db.add_or_update(incapacitated)
assert carl.actions_per_turn == 1
@ -421,32 +422,47 @@ def test_condition_immunity(db, bootstrap):
db.add_or_update(carl)
assert carl.has_condition(poisoned)
def test_partial_immunities(db, bootstrap):
"""
Test that individual modifiers applied by a condition can be negated even if not immune to the condition.
"""
with db.transaction():
tiefling = db.Ancestry.filter_by(name="tiefling").one()
carl = schema.Character(name="Carl", ancestry=tiefling)
# Create some modifiers and conditions for this test
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)
poison_immunity = schema.Modifier("Poison Immunity", target=DamageType.poison, new_value=Defenses.immune)
# incapacitated applies several modifiers
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="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"))
# petrified incapacitates you, and makes you immune to poison
# petrified applies several modifiers but also incapacitates you!
petrified = schema.Condition(name=Conditions.petrified)
assert petrified.add_modifier(poison_immunity)
assert petrified.add_modifier(cannot_move)
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
carl.add_condition(poisoned)
db.add_or_update(carl)
assert not carl.immune(DamageType.poison)
assert carl.add_condition(poisoned)
db.add_or_update(carl)
# petrify carl
assert not carl.immune(Conditions.petrified)
@ -458,6 +474,8 @@ def test_partial_immunities(db, bootstrap):
# but carl still can't move
assert carl.actions_per_turn == 0
assert carl.speed == 0
assert carl.fly_speed == 0
# greater restoration ftw!
assert carl.remove_condition(petrified)