Implement conditions
This commit is contained in:
+1
-1
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user