fix tests

This commit is contained in:
evilchili 2024-06-30 16:09:20 -07:00
parent a8bb6de008
commit 68251ff4e9
5 changed files with 59 additions and 47 deletions

View File

@ -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"

View File

@ -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 *

View File

@ -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:

View File

@ -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)

View File

@ -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