Implement conditions

This commit is contained in:
evilchili
2024-07-13 12:30:43 -07:00
parent da1b4223ea
commit e2ff1eb027
6 changed files with 295 additions and 88 deletions
+1 -1
View File
@@ -86,7 +86,7 @@ def bootstrap(db):
# add skills
fighter.add_skill(acrobatics)
fighter.add_skill(athletics)
fighter.add_attribute(fighting_style, level=2)
fighter.add_feature(fighting_style, level=2)
db.add_or_update(fighter)
assert acrobatics in fighter.skills
assert athletics in fighter.skills
+119 -22
View File
@@ -1,7 +1,8 @@
import json
from ttfrog.db import schema
from ttfrog.db.schema.constants import DamageType, Defenses, Conditions
from ttfrog.db.schema.conditions import conditions
from ttfrog.db.schema.constants import Conditions, DamageType, Defenses
def test_manage_character(db, bootstrap):
@@ -71,14 +72,14 @@ def test_manage_character(db, bootstrap):
db.add_or_update(char)
assert char.levels == {"fighter": 1}
assert char.level == 1
assert char.class_attributes == {}
assert char.class_features == {}
# 'fighting style' is available, but not at this level
fighting_style = fighter.attribute("Fighting Style")
assert char.has_class_attribute(fighting_style) is False
assert char.add_class_attribute(fighter, fighting_style, fighting_style.options[0]) is False
fighting_style = fighter.feature("Fighting Style")
assert char.has_class_feature(fighting_style) is False
assert char.add_class_feature(fighter, fighting_style, fighting_style.options[0]) is False
db.add_or_update(char)
assert char.class_attributes == {}
assert char.class_features == {}
# level up
char.add_class(fighter, level=7)
@@ -87,10 +88,10 @@ def test_manage_character(db, bootstrap):
assert char.level == 7
# Assert the fighting style is added automatically and idempotent...ly?
assert char.has_class_attribute(fighting_style)
assert char.class_attributes[fighting_style.name] == fighting_style.options[0]
assert char.add_class_attribute(fighter, fighting_style, fighting_style.options[0]) is False
assert char.has_class_attribute(fighting_style)
assert char.has_class_feature(fighting_style)
assert char.class_features[fighting_style.name] == fighting_style.options[0]
assert char.add_class_feature(fighter, fighting_style, fighting_style.options[0]) is False
assert char.has_class_feature(fighting_style)
db.add_or_update(char)
athletics = db.Skill.filter_by(name="athletics").one()
@@ -113,18 +114,9 @@ def test_manage_character(db, bootstrap):
db.add_or_update(char)
assert char.level == 8
assert char.levels == {"fighter": 7, "rogue": 1}
assert list(char.classes.keys()) == ['fighter', 'rogue']
assert list(char.classes.keys()) == ["fighter", "rogue"]
assert sum([len(dice) for dice in char.hit_dice.values()]) == char.level == 8
# test conditions
assert not char.condition(Conditions.frightened)
char.add_condition(Conditions.frightened)
assert char.condition(Conditions.frightened)
char.add_modifier(
schema.Modifier("Immunity: Frightened", target=Conditions.frightened, new_value=Defenses.immune)
)
assert not char.condition(Conditions.frightened)
# use a hit die
char.spend_hit_die(char.hit_dice["rogue"][0])
assert len([die for die in char.hit_dice_available if die.character_class.name == "rogue"]) == 0
@@ -145,7 +137,7 @@ def test_manage_character(db, bootstrap):
char.add_class(fighter, level=0)
db.add_or_update(char)
assert char.levels == {}
assert char.class_attributes == {}
assert char.class_features == {}
# verify the proficiencies etc. added by the classes have been removed
assert athletics not in char.skills
@@ -156,7 +148,7 @@ def test_manage_character(db, bootstrap):
# ensure we're not persisting any orphan records in the map tables
dump = json.loads(db.dump())
assert not [m for m in dump["character_class_attribute_map"] if m["character_id"] == char.id]
assert not [m for m in dump["character_class_feature_map"] if m["character_id"] == char.id]
assert not [m for m in dump["class_map"] if m["character_id"] == char.id]
@@ -232,6 +224,27 @@ def test_modifiers(db, bootstrap):
reduced = schema.Modifier(target="size", new_value="Tiny", name="Reduced")
fly = schema.Modifier(target="fly_speed", absolute_value=30, name="Fly Spell")
# 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="reactions_per_turn", absolute_value=0, name="Incapacitated"))
db.add_or_update(incapacitated)
assert carl.actions_per_turn == 1
assert carl.bonus_actions_per_turn == 1
assert carl.reactions_per_turn == 1
assert carl.add_condition(incapacitated)
db.add_or_update(carl)
assert carl.has_condition(incapacitated)
assert carl.actions_per_turn == 0
assert carl.bonus_actions_per_turn == 0
assert carl.reactions_per_turn == 0
assert carl.remove_condition(incapacitated)
db.add_or_update(carl)
assert carl.actions_per_turn == 1
assert carl.bonus_actions_per_turn == 1
assert carl.reactions_per_turn == 1
# reduce speed by 10
assert carl.add_modifier(cold)
assert carl.speed == 20
@@ -249,6 +262,7 @@ def test_modifiers(db, bootstrap):
# speed is doubled
assert carl.remove_modifier(cold)
assert carl.speed == 30
assert carl.fly_speed == 30
assert carl.add_modifier(hasted)
assert carl.speed == 60
@@ -319,6 +333,7 @@ def test_modifiers(db, bootstrap):
# ensure you can't remove a skill already removed
assert not carl.remove_skill(athletics, proficient=True, expert=False, character_class=None)
def test_defenses(db, bootstrap):
with db.transaction():
tiefling = db.Ancestry.filter_by(name="tiefling").one()
@@ -369,3 +384,85 @@ def test_defenses(db, bootstrap):
assert not carl.vulnerable(DamageType.fire)
assert not carl.absorbs(DamageType.fire)
assert carl.hit_points == 8 # half damage
def test_condition_immunity(db, bootstrap):
"""
Test immunities prevent conditions from being applied
"""
with db.transaction():
tiefling = db.Ancestry.filter_by(name="tiefling").one()
carl = schema.Character(name="Carl", ancestry=tiefling)
poisoned = schema.Condition(name=DamageType.poison)
poison_immunity = schema.Modifier("Poison Immunity", target=DamageType.poison, new_value=Defenses.immune)
db.add_or_update([carl, poisoned, poison_immunity])
# poison carl
assert carl.add_condition(poisoned)
db.add_or_update(carl)
assert carl.has_condition(poisoned)
# grant carl immunity, which must remove the poisoned condition
assert carl.add_modifier(poison_immunity)
db.add_or_update(carl)
assert not carl.has_condition(poisoned)
# ensure that carl cannot be poisoned while immune to poison
assert not carl.add_condition(poisoned)
# remove the immunity and ensure the previous poison doesn't come back
assert carl.remove_modifier(poison_immunity)
db.add_or_update(carl)
assert not carl.immune(str(poisoned))
assert not carl.has_condition(poisoned)
# carl can be poisoned again
assert carl.add_condition(poisoned)
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)
poisoned = schema.Condition(name=DamageType.poison)
poison_immunity = schema.Modifier("Poison Immunity", target=DamageType.poison, new_value=Defenses.immune)
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="reactions_per_turn", absolute_value=0, name="Incapacitated"))
# petrified incapacitates you, and makes you immune to poison
petrified = schema.Condition(name=Conditions.petrified)
assert petrified.add_modifier(poison_immunity)
assert petrified.add_condition(incapacitated)
db.add_or_update([poisoned, poison_immunity, incapacitated, petrified])
# poison carl
carl.add_condition(poisoned)
db.add_or_update(carl)
assert not carl.immune(DamageType.poison)
# petrify carl
assert not carl.immune(Conditions.petrified)
assert carl.add_condition(petrified)
db.add_or_update(carl)
# while petrified, carl is immune to poison
assert carl.immune(DamageType.poison)
# but carl still can't move
assert carl.actions_per_turn == 0
# greater restoration ftw!
assert carl.remove_condition(petrified)
db.add_or_update(carl)
# ...but he's still poisoned. Sorry carl :(
assert carl.has_condition(poisoned)
assert not carl.immune(DamageType.poison)