fix tests
This commit is contained in:
parent
a8bb6de008
commit
68251ff4e9
|
@ -9,7 +9,7 @@ packages = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.11"
|
||||||
python-dotenv = "^0.21.0"
|
python-dotenv = "^0.21.0"
|
||||||
typer = "^0.9.0"
|
typer = "^0.9.0"
|
||||||
rich = "^13.7.0"
|
rich = "^13.7.0"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from .character import *
|
from .character import *
|
||||||
from .classes import *
|
from .classes import *
|
||||||
|
from .constants import *
|
||||||
from .log import *
|
from .log import *
|
||||||
from .modifiers import *
|
from .modifiers import *
|
||||||
from .skill import *
|
from .skill import *
|
||||||
|
|
|
@ -6,6 +6,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, ClassAttribute
|
from ttfrog.db.schema.classes import CharacterClass, ClassAttribute
|
||||||
|
from ttfrog.db.schema.constants import Conditions, DamageType, Defenses
|
||||||
from ttfrog.db.schema.modifiers import 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
|
||||||
|
|
||||||
|
@ -212,6 +213,7 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
)
|
)
|
||||||
|
|
||||||
_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})
|
||||||
|
|
||||||
class_map = relationship("CharacterClassMap", cascade="all,delete,delete-orphan")
|
class_map = relationship("CharacterClassMap", cascade="all,delete,delete-orphan")
|
||||||
class_list = association_proxy("class_map", "id", creator=class_map_creator)
|
class_list = association_proxy("class_map", "id", creator=class_map_creator)
|
||||||
|
@ -272,6 +274,10 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
def traits(self):
|
def traits(self):
|
||||||
return self.ancestry.traits
|
return self.ancestry.traits
|
||||||
|
|
||||||
|
@property
|
||||||
|
def initiative(self):
|
||||||
|
return self._apply_modifiers("initiative", self.dexterity.bonus)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def speed(self):
|
def speed(self):
|
||||||
return self._apply_modifiers("speed", self.ancestry.speed)
|
return self._apply_modifiers("speed", self.ancestry.speed)
|
||||||
|
@ -294,7 +300,7 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def vision_in_darkness(self):
|
def vision_in_darkness(self):
|
||||||
return self.apply_modifiers("vision_in_darkness", self.vision if self.vision is not None else 0)
|
return self._apply_modifiers("vision_in_darkness", self.vision if self.vision is not None else 0)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def level(self):
|
def level(self):
|
||||||
|
@ -314,23 +320,26 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
return None
|
return None
|
||||||
return mapping[0]
|
return mapping[0]
|
||||||
|
|
||||||
def immune(self, damage_type: str, magical: bool = False):
|
def immune(self, damage_type: DamageType):
|
||||||
return self.defense(damage_type, magical) == 'immune'
|
return self.defense(damage_type) == Defenses.immune
|
||||||
|
|
||||||
def resistant(self, damage_type: str, magical: bool = False):
|
def resistant(self, damage_type: DamageType):
|
||||||
return self.defense(damage_type, magical) == 'resistant'
|
return self.defense(damage_type) == Defenses.resistant.value
|
||||||
|
|
||||||
def vulnerable(self, damage_type: str, magical: bool = False):
|
def vulnerable(self, damage_type: DamageType):
|
||||||
return self.defense(damage_type, magical) == 'vulnerable'
|
return self.defense(damage_type) == Defenses.vulnerable
|
||||||
|
|
||||||
def absorbs(self, damage_type: str, magical: bool = False):
|
def absorbs(self, damage_type: DamageType):
|
||||||
return self.defense(damage_type, magical) == 'absorbs'
|
return self.defense(damage_type) == Defenses.absorbs
|
||||||
|
|
||||||
def defense(self, damage_type: str, magical: bool = False):
|
def conditions(self):
|
||||||
attr_name = damage_type
|
return [self._apply_modifiers(f"conditions.{name}") for name in Conditions]
|
||||||
if magical:
|
|
||||||
attr_name = f"magical_{attr_name}"
|
def condition(self, condition_name: str):
|
||||||
return self._apply_modifiers(f"defenses.{attr_name}", None)
|
return self._apply_modifiers(f"conditions.{condition_name}", False)
|
||||||
|
|
||||||
|
def defense(self, damage_type: DamageType):
|
||||||
|
return self._apply_modifiers(damage_type, None)
|
||||||
|
|
||||||
def check_modifier(self, skill: Skill, save: bool = False):
|
def check_modifier(self, skill: Skill, save: bool = False):
|
||||||
# if the skill is not assigned, but we have modifiers, apply them to zero.
|
# if the skill is not assigned, but we have modifiers, apply them to zero.
|
||||||
|
@ -387,13 +396,13 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
self._hit_dice.append(HitDie(character_id=self.id, character_class_id=newclass.id))
|
self._hit_dice.append(HitDie(character_id=self.id, character_class_id=newclass.id))
|
||||||
|
|
||||||
def remove_class(self, target):
|
def remove_class(self, target):
|
||||||
|
self.class_map = [m for m in self.class_map if m.character_class != target]
|
||||||
for mapping in self.character_class_attribute_map:
|
for mapping in self.character_class_attribute_map:
|
||||||
if mapping.character_class == target:
|
if mapping.character_class == target:
|
||||||
self.remove_class_attribute(mapping.class_attribute)
|
self.remove_class_attribute(mapping.class_attribute)
|
||||||
for skill in target.skills:
|
for skill in target.skills:
|
||||||
self.remove_skill(skill, proficient=True, expert=False, character_class=target)
|
self.remove_skill(skill, proficient=True, expert=False, character_class=target)
|
||||||
self._hit_dice = [die for die in self._hit_dice if die.character_class != target]
|
self._hit_dice = [die for die in self._hit_dice if die.character_class != target]
|
||||||
self.class_map = [m for m in self.class_map if m.character_class != target]
|
|
||||||
|
|
||||||
def remove_class_attribute(self, attribute):
|
def remove_class_attribute(self, attribute):
|
||||||
self.character_class_attribute_map = [
|
self.character_class_attribute_map = [
|
||||||
|
@ -474,15 +483,15 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
|
||||||
def apply_healing(self, value: int):
|
def apply_healing(self, value: int):
|
||||||
self.hit_points = min(self.hit_points + value, self._max_hit_points)
|
self.hit_points = min(self.hit_points + value, self._max_hit_points)
|
||||||
|
|
||||||
def apply_damage(self, value: int, damage_type: str, magical=False):
|
def apply_damage(self, value: int, damage_type: DamageType):
|
||||||
total = value
|
total = value
|
||||||
if self.absorbs(damage_type, magical):
|
if self.absorbs(damage_type):
|
||||||
return self.apply_healing(total)
|
return self.apply_healing(total)
|
||||||
if self.immune(damage_type, magical):
|
if self.immune(damage_type):
|
||||||
return
|
return
|
||||||
if self.resistant(damage_type, magical):
|
if self.resistant(damage_type):
|
||||||
total = int(value / 2)
|
total = int(value / 2)
|
||||||
elif self.vulnerable(damage_type, magical):
|
elif self.vulnerable(damage_type):
|
||||||
total = value * 2
|
total = value * 2
|
||||||
|
|
||||||
if total <= self.temp_hit_points:
|
if total <= self.temp_hit_points:
|
||||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
||||||
|
|
||||||
from ttfrog.db import schema
|
from ttfrog.db import schema
|
||||||
from ttfrog.db.manager import db as _db
|
from ttfrog.db.manager import db as _db
|
||||||
|
from ttfrog.db.schema.constants import DamageType, Defenses
|
||||||
|
|
||||||
FIXTURE_PATH = Path(__file__).parent / "fixtures"
|
FIXTURE_PATH = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
|
@ -49,14 +50,12 @@ def bootstrap(db):
|
||||||
darkvision.add_modifier(schema.Modifier("Darkvision", target="vision_in_darkness", absolute_value=120))
|
darkvision.add_modifier(schema.Modifier("Darkvision", target="vision_in_darkness", absolute_value=120))
|
||||||
tiefling.add_trait(darkvision)
|
tiefling.add_trait(darkvision)
|
||||||
|
|
||||||
# resistant to both magical and non-magical sources of fire
|
# resistant to fire
|
||||||
infernal_origin = schema.AncestryTrait("Infernal Origin")
|
infernal_origin = schema.AncestryTrait("Infernal Origin")
|
||||||
infernal_origin.add_modifier(schema.Modifier("Infernal Origin", target="defenses.fire", new_value="resistant"))
|
|
||||||
infernal_origin.add_modifier(
|
infernal_origin.add_modifier(
|
||||||
schema.Modifier("Infernal Origin", target="defenses.magical_fire", new_value="resistant")
|
schema.Modifier("Infernal Origin", target=DamageType.fire, new_value=Defenses.resistant)
|
||||||
)
|
)
|
||||||
tiefling.add_trait(infernal_origin)
|
tiefling.add_trait(infernal_origin)
|
||||||
db.add_or_update(tiefling)
|
|
||||||
|
|
||||||
dragonborn = schema.Ancestry("dragonborn")
|
dragonborn = schema.Ancestry("dragonborn")
|
||||||
dragonborn.add_trait(darkvision)
|
dragonborn.add_trait(darkvision)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ttfrog.db import schema
|
from ttfrog.db import schema
|
||||||
|
from ttfrog.db.schema.constants import DamageType, Defenses
|
||||||
|
|
||||||
|
|
||||||
def test_manage_character(db, bootstrap):
|
def test_manage_character(db, bootstrap):
|
||||||
|
@ -96,6 +97,14 @@ def test_manage_character(db, bootstrap):
|
||||||
assert char.check_modifier(athletics) == char.proficiency_bonus + char.strength.bonus == 3
|
assert char.check_modifier(athletics) == char.proficiency_bonus + char.strength.bonus == 3
|
||||||
assert char.check_modifier(acrobatics) == char.proficiency_bonus + char.dexterity.bonus == 3
|
assert char.check_modifier(acrobatics) == char.proficiency_bonus + char.dexterity.bonus == 3
|
||||||
|
|
||||||
|
# assert dexterity bonus apply to initiative
|
||||||
|
char._dexterity = 17
|
||||||
|
assert char.dexterity.bonus == 3
|
||||||
|
assert char.initiative == char.dexterity.bonus == 3
|
||||||
|
char.add_modifier(schema.Modifier("+1 initiative", target="initiative", relative_value=1))
|
||||||
|
assert char.initiative == 4
|
||||||
|
char._dexterity = 10
|
||||||
|
|
||||||
# multiclass
|
# multiclass
|
||||||
char.add_class(rogue, level=1)
|
char.add_class(rogue, level=1)
|
||||||
db.add_or_update(char)
|
db.add_or_update(char)
|
||||||
|
@ -279,45 +288,39 @@ def test_defenses(db, bootstrap):
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
tiefling = db.Ancestry.filter_by(name="tiefling").one()
|
tiefling = db.Ancestry.filter_by(name="tiefling").one()
|
||||||
carl = schema.Character(name="Carl", ancestry=tiefling)
|
carl = schema.Character(name="Carl", ancestry=tiefling)
|
||||||
assert carl.resistant("fire", magical=False)
|
assert carl.resistant(DamageType.fire)
|
||||||
assert carl.resistant("fire", magical=True)
|
carl.apply_damage(5, DamageType.fire)
|
||||||
carl.apply_damage(5, "fire", magical=True)
|
|
||||||
assert carl.hit_points == 8 # half damage
|
assert carl.hit_points == 8 # half damage
|
||||||
|
|
||||||
immunity = [
|
immunity = [
|
||||||
schema.Modifier("Fire Immunity", target="defenses.fire", new_value="immune"),
|
schema.Modifier("Fire Immunity", target=DamageType.fire, new_value=Defenses.immune),
|
||||||
schema.Modifier("Fire Immunity", target="defenses.magical_fire", new_value="immune")
|
|
||||||
]
|
]
|
||||||
for i in immunity:
|
for i in immunity:
|
||||||
carl.add_modifier(i)
|
carl.add_modifier(i)
|
||||||
assert carl.immune("fire")
|
assert carl.immune(DamageType.fire)
|
||||||
carl.apply_damage(5, "fire", magical=True)
|
carl.apply_damage(5, DamageType.fire)
|
||||||
carl.apply_damage(5, "fire", magical=False)
|
|
||||||
assert carl.hit_points == 8 # no damage
|
assert carl.hit_points == 8 # no damage
|
||||||
|
|
||||||
vulnerability = [
|
vulnerability = [
|
||||||
schema.Modifier("Fire Vulnerability", target="defenses.fire", new_value="vulnerable"),
|
schema.Modifier("Fire Vulnerability", target=DamageType.fire, new_value=Defenses.vulnerable),
|
||||||
schema.Modifier("Fire Vulnerability", target="defenses.magical_fire", new_value="vulnerable")
|
|
||||||
]
|
]
|
||||||
for i in vulnerability:
|
for i in vulnerability:
|
||||||
carl.add_modifier(i)
|
carl.add_modifier(i)
|
||||||
assert carl.vulnerable("fire")
|
assert carl.vulnerable(DamageType.fire)
|
||||||
assert not carl.immune("fire")
|
assert not carl.immune(DamageType.fire)
|
||||||
carl.apply_damage(2, "fire", magical=True)
|
carl.apply_damage(2, DamageType.fire)
|
||||||
assert carl.hit_points == 4 # double damage
|
assert carl.hit_points == 4 # double damage
|
||||||
|
|
||||||
absorbs = [
|
absorbs = [schema.Modifier("Absorbs Non-Magical Fire", target=DamageType.fire, new_value=Defenses.absorbs)]
|
||||||
schema.Modifier("Absorbs Non-Magical Fire", target="defenses.fire", new_value="absorbs"),
|
|
||||||
]
|
|
||||||
carl.add_modifier(absorbs[0])
|
carl.add_modifier(absorbs[0])
|
||||||
carl.apply_damage(20, "fire", magical=False)
|
carl.apply_damage(20, DamageType.fire)
|
||||||
assert carl.hit_points == carl._max_hit_points == 10
|
assert carl.hit_points == carl._max_hit_points == 10
|
||||||
|
|
||||||
for i in immunity + vulnerability + absorbs:
|
for i in immunity + vulnerability + absorbs:
|
||||||
carl.remove_modifier(i)
|
carl.remove_modifier(i)
|
||||||
carl.apply_damage(5, "fire", magical=True)
|
carl.apply_damage(5, DamageType.fire)
|
||||||
assert carl.resistant("fire")
|
assert carl.resistant(DamageType.fire)
|
||||||
assert not carl.immune("fire")
|
assert not carl.immune(DamageType.fire)
|
||||||
assert not carl.vulnerable("fire")
|
assert not carl.vulnerable(DamageType.fire)
|
||||||
assert not carl.absorbs("fire")
|
assert not carl.absorbs(DamageType.fire)
|
||||||
assert carl.hit_points == 8 # half damage
|
assert carl.hit_points == 8 # half damage
|
||||||
|
|
Loading…
Reference in New Issue
Block a user