fixing modifier bugs, fixing traits, adding speed attrs
This commit is contained in:
parent
5db6e40eae
commit
1ff0e5ca7d
|
@ -116,7 +116,7 @@ def dump(context: typer.Context):
|
||||||
"""
|
"""
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
|
|
||||||
db.init()
|
setup(context)
|
||||||
print(db.dump(context.args))
|
print(db.dump(context.args))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,192 +1,36 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
from sqlalchemy.exc import IntegrityError
|
|
||||||
|
|
||||||
from ttfrog.db import schema
|
from ttfrog.db import schema
|
||||||
from ttfrog.db.manager import db
|
from ttfrog.db.manager import db
|
||||||
|
|
||||||
# move this to json or whatever
|
|
||||||
data = {
|
|
||||||
"CharacterClass": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "fighter",
|
|
||||||
"hit_dice": "1d10",
|
|
||||||
"hit_dice_stat": "CON",
|
|
||||||
"proficiencies": "all armor, all shields, simple weapons, martial weapons",
|
|
||||||
"saving_throws": ["STR, CON"],
|
|
||||||
"skills": [
|
|
||||||
"Acrobatics",
|
|
||||||
"Animal Handling",
|
|
||||||
"Athletics",
|
|
||||||
"History",
|
|
||||||
"Insight",
|
|
||||||
"Intimidation",
|
|
||||||
"Perception",
|
|
||||||
"Survival",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "rogue",
|
|
||||||
"hit_dice": "1d8",
|
|
||||||
"hit_dice_stat": "DEX",
|
|
||||||
"proficiencies": "simple weapons, hand crossbows, longswords, rapiers, shortswords",
|
|
||||||
"saving_throws": ["DEX", "INT"],
|
|
||||||
"skills": [
|
|
||||||
"Acrobatics",
|
|
||||||
"Athletics",
|
|
||||||
"Deception",
|
|
||||||
"Insight",
|
|
||||||
"Intimidation",
|
|
||||||
"Investigation",
|
|
||||||
"Perception",
|
|
||||||
"Performance",
|
|
||||||
"Persuasion",
|
|
||||||
"Sleight of Hand",
|
|
||||||
"Stealth",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"Skill": [
|
|
||||||
{"name": "Acrobatics"},
|
|
||||||
{"name": "Animal Handling"},
|
|
||||||
{"name": "Athletics"},
|
|
||||||
{"name": "Deception"},
|
|
||||||
{"name": "History"},
|
|
||||||
{"name": "Insight"},
|
|
||||||
{"name": "Intimidation"},
|
|
||||||
{"name": "Investigation"},
|
|
||||||
{"name": "Perception"},
|
|
||||||
{"name": "Performance"},
|
|
||||||
{"name": "Persuasion"},
|
|
||||||
{"name": "Sleight of Hand"},
|
|
||||||
{"name": "Stealth"},
|
|
||||||
{"name": "Survival"},
|
|
||||||
],
|
|
||||||
"Ancestry": [
|
|
||||||
{"id": 1, "name": "human", "creature_type": "humanoid"},
|
|
||||||
{"id": 2, "name": "dragonborn", "creature_type": "humanoid"},
|
|
||||||
{"id": 3, "name": "tiefling", "creature_type": "humanoid"},
|
|
||||||
{"id": 4, "name": "elf", "creature_type": "humanoid"},
|
|
||||||
],
|
|
||||||
"AncestryTrait": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "+1 to All Ability Scores",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"name": "Breath Weapon",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": 3,
|
|
||||||
"name": "Darkvision",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"AncestryTraitMap": [
|
|
||||||
{"ancestry_id": 1, "ancestry_trait_id": 1, "level": 1}, # human +1 to scores
|
|
||||||
{"ancestry_id": 2, "ancestry_trait_id": 2, "level": 1}, # dragonborn breath weapon
|
|
||||||
{"ancestry_id": 3, "ancestry_trait_id": 3, "level": 1}, # tiefling darkvision
|
|
||||||
{"ancestry_id": 2, "ancestry_trait_id": 2, "level": 1}, # elf darkvision
|
|
||||||
],
|
|
||||||
"CharacterClassMap": [
|
|
||||||
{
|
|
||||||
"character_id": 1,
|
|
||||||
"character_class_id": 1,
|
|
||||||
"level": 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"character_id": 1,
|
|
||||||
"character_class_id": 2,
|
|
||||||
"level": 3,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"Character": [
|
|
||||||
{
|
|
||||||
"id": 1,
|
|
||||||
"name": "Sabetha",
|
|
||||||
"ancestry_id": 1,
|
|
||||||
"armor_class": 10,
|
|
||||||
"max_hit_points": 14,
|
|
||||||
"hit_points": 14,
|
|
||||||
"temp_hit_points": 0,
|
|
||||||
"speed": 30,
|
|
||||||
"str": 16,
|
|
||||||
"dex": 12,
|
|
||||||
"con": 18,
|
|
||||||
"int": 11,
|
|
||||||
"wis": 12,
|
|
||||||
"cha": 8,
|
|
||||||
"proficiencies": "all armor, all shields, simple weapons, martial weapons",
|
|
||||||
"saving_throws": ["STR", "CON"],
|
|
||||||
"skills": ["Acrobatics", "Animal Handling"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"ClassAttribute": [
|
|
||||||
{"id": 1, "name": "Fighting Style"},
|
|
||||||
{"id": 2, "name": "Another Attribute"},
|
|
||||||
],
|
|
||||||
"ClassAttributeOption": [
|
|
||||||
{"id": 1, "attribute_id": 1, "name": "Archery"},
|
|
||||||
{"id": 2, "attribute_id": 1, "name": "Battlemaster"},
|
|
||||||
{"id": 3, "attribute_id": 2, "name": "Another Option 1"},
|
|
||||||
{"id": 4, "attribute_id": 2, "name": "Another Option 2"},
|
|
||||||
],
|
|
||||||
"ClassAttributeMap": [
|
|
||||||
{"class_attribute_id": 1, "character_class_id": 1, "level": 2}, # Fighter: Fighting Style
|
|
||||||
{"class_attribute_id": 2, "character_class_id": 1, "level": 1}, # Fighter: Another Attr
|
|
||||||
],
|
|
||||||
"CharacterClassAttributeMap": [
|
|
||||||
{"character_id": 1, "class_attribute_id": 2, "option_id": 4}, # Sabetha, another option, option 2
|
|
||||||
{"character_id": 1, "class_attribute_id": 1, "option_id": 1}, # Sabetha, fighting style, archery
|
|
||||||
],
|
|
||||||
"Modifier": [
|
|
||||||
# Humans
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "str"},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "dex"},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "con"},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "int"},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "wis"},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 1, "value": "+1", "type": "stat", "target": "cha"},
|
|
||||||
# Dragonborn
|
|
||||||
{
|
|
||||||
"source_table_name": "ancestry_trait",
|
|
||||||
"source_table_id": 2,
|
|
||||||
"value": "60",
|
|
||||||
"type": "attribute ",
|
|
||||||
"target": "Darkvision",
|
|
||||||
},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 2, "value": "+1", "type": "stat", "target": ""},
|
|
||||||
{"source_table_name": "ancestry_trait", "source_table_id": 2, "value": "+1", "type": "stat", "target": ""},
|
|
||||||
# Fighting Style: Archery
|
|
||||||
{
|
|
||||||
"source_table_name": "class_attribute",
|
|
||||||
"source_table_id": 1,
|
|
||||||
"value": "+2",
|
|
||||||
"type": "weapon ",
|
|
||||||
"target": "ranged",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def bootstrap():
|
def bootstrap():
|
||||||
"""
|
db.metadata.drop_all(bind=db.engine)
|
||||||
Initialize the database with source data. Idempotent; will skip anything that already exists.
|
|
||||||
"""
|
|
||||||
db.init()
|
db.init()
|
||||||
for table, records in data.items():
|
|
||||||
model = getattr(schema, table)
|
|
||||||
|
|
||||||
for rec in records:
|
|
||||||
obj = model(**rec)
|
|
||||||
try:
|
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
db.session.add(obj)
|
# ancestries
|
||||||
logging.info(f"Created {table} {obj}")
|
human = schema.Ancestry(name="human")
|
||||||
except IntegrityError as e:
|
tiefling = schema.Ancestry(name="tiefling")
|
||||||
if "UNIQUE constraint failed" in str(e):
|
tiefling.add_modifier(schema.Modifier(name="Ability Score Increase", target="intelligence", relative_value=1))
|
||||||
logging.info(f"Skipping existing {table} {obj}")
|
tiefling.add_modifier(schema.Modifier(name="Ability Score Increase", target="charisma", relative_value=2))
|
||||||
continue
|
darkvision = schema.AncestryTrait(
|
||||||
raise
|
name="Darkvision",
|
||||||
|
description=(
|
||||||
|
"You can see in dim light within 60 feet of you as if it were bright light, and in darkness as if it "
|
||||||
|
"were dim light. You can’t discern color in darkness, only shades of gray."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
darkvision.add_modifier(schema.Modifier(name="Darkvision", target="vision_in_darkness", absolute_value=120))
|
||||||
|
tiefling.add_trait(darkvision)
|
||||||
|
|
||||||
|
# classes
|
||||||
|
fighter = schema.CharacterClass(name="fighter", hit_dice="1d10", hit_dice_stat="CON")
|
||||||
|
rogue = schema.CharacterClass(name="rogue", hit_dice="1d8", hit_dice_stat="DEX")
|
||||||
|
|
||||||
|
# characters
|
||||||
|
sabetha = schema.Character(name="Sabetha", ancestry=tiefling)
|
||||||
|
sabetha.add_class(fighter, level=2)
|
||||||
|
sabetha.add_class(rogue, level=3)
|
||||||
|
|
||||||
|
bob = schema.Character(name="Bob", ancestry=human)
|
||||||
|
|
||||||
|
# persist all the records we've created
|
||||||
|
db.add_or_update([sabetha, bob])
|
||||||
|
|
|
@ -34,7 +34,7 @@ class AncestryTraitMap(BaseObject):
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"))
|
ancestry_id = Column(Integer, ForeignKey("ancestry.id"))
|
||||||
ancestry_trait_id = Column(Integer, ForeignKey("ancestry_trait.id"))
|
ancestry_trait_id = Column(Integer, ForeignKey("ancestry_trait.id"))
|
||||||
trait = relationship("AncestryTrait", lazy="immediate")
|
trait = relationship("AncestryTrait", uselist=False, lazy="immediate")
|
||||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20})
|
level = Column(Integer, nullable=False, info={"min": 1, "max": 20})
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,16 +48,31 @@ class Ancestry(BaseObject, ModifierMixin):
|
||||||
name = Column(String, index=True, unique=True)
|
name = Column(String, index=True, unique=True)
|
||||||
creature_type = Column(Enum(CreatureTypesEnum), nullable=False, default="humanoid")
|
creature_type = Column(Enum(CreatureTypesEnum), nullable=False, default="humanoid")
|
||||||
size = Column(Enum(SizesEnum), nullable=False, default="Medium")
|
size = Column(Enum(SizesEnum), nullable=False, default="Medium")
|
||||||
speed = Column(Integer, nullable=False, default=30, info={"min": 0, "max": 99})
|
walk_speed = Column(Integer, nullable=False, default=30, info={"min": 0, "max": 99})
|
||||||
|
_fly_speed = Column(Integer, info={"min": 0, "max": 99})
|
||||||
|
_climb_speed = Column(Integer, info={"min": 0, "max": 99})
|
||||||
|
_swim_speed = Column(Integer, info={"min": 0, "max": 99})
|
||||||
_traits = relationship("AncestryTraitMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
_traits = relationship("AncestryTraitMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def traits(self):
|
def traits(self):
|
||||||
return [mapping.trait for mapping in self._traits]
|
return [mapping.trait for mapping in self._traits]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def speed(self):
|
||||||
|
return self.walk_speed
|
||||||
|
|
||||||
|
@property
|
||||||
|
def climb_speed(self):
|
||||||
|
return self._climb_speed or int(self.speed / 2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swim_speed(self):
|
||||||
|
return self._swim_speed or int(self.speed / 2)
|
||||||
|
|
||||||
def add_trait(self, trait, level=1):
|
def add_trait(self, trait, level=1):
|
||||||
if trait not in self.traits:
|
if trait not in self._traits:
|
||||||
self._traits.append(AncestryTraitMap(ancestry_id=self.id, ancestry_trait_id=trait.id, level=level))
|
self._traits.append(AncestryTraitMap(ancestry_id=self.id, trait=trait, level=level))
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -65,7 +80,7 @@ class Ancestry(BaseObject, ModifierMixin):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class AncestryTrait(BaseObject):
|
class AncestryTrait(BaseObject, ModifierMixin):
|
||||||
"""
|
"""
|
||||||
A trait granted to a character via its Ancestry.
|
A trait granted to a character via its Ancestry.
|
||||||
"""
|
"""
|
||||||
|
@ -129,24 +144,14 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
intelligence = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
intelligence = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
||||||
wisdom = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
wisdom = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
||||||
charisma = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
charisma = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
||||||
|
|
||||||
|
_vision = Column(Integer, info={"min": 0})
|
||||||
|
|
||||||
proficiencies = Column(String)
|
proficiencies = Column(String)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
_modify_ok = [
|
|
||||||
"armor_class",
|
|
||||||
"max_hit_points",
|
|
||||||
"strength",
|
|
||||||
"dexterity",
|
|
||||||
"constitution",
|
|
||||||
"intelligence",
|
|
||||||
"wisdom",
|
|
||||||
"charisma",
|
|
||||||
"speed",
|
|
||||||
"size",
|
|
||||||
]
|
|
||||||
|
|
||||||
character_class_attribute_map = relationship("CharacterClassAttributeMap", cascade="all,delete,delete-orphan")
|
character_class_attribute_map = relationship("CharacterClassAttributeMap", cascade="all,delete,delete-orphan")
|
||||||
attribute_list = association_proxy("character_class_attribute_map", "id", creator=attr_map_creator)
|
attribute_list = association_proxy("character_class_attribute_map", "id", creator=attr_map_creator)
|
||||||
|
|
||||||
|
@ -157,6 +162,8 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
def modifiers(self):
|
def modifiers(self):
|
||||||
unified = {}
|
unified = {}
|
||||||
unified.update(**self.ancestry.modifiers)
|
unified.update(**self.ancestry.modifiers)
|
||||||
|
for trait in self.traits:
|
||||||
|
unified.update(**trait.modifiers)
|
||||||
unified.update(**super().modifiers)
|
unified.update(**super().modifiers)
|
||||||
return unified
|
return unified
|
||||||
|
|
||||||
|
@ -204,10 +211,30 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
def speed(self):
|
def speed(self):
|
||||||
return self.apply_modifiers("speed", self.ancestry.speed)
|
return self.apply_modifiers("speed", self.ancestry.speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def climb_speed(self):
|
||||||
|
return self.apply_modifiers("climb_speed", self.ancestry.climb_speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def swim_speed(self):
|
||||||
|
return self.apply_modifiers("swim_speed", self.ancestry.swim_speed)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fly_speed(self):
|
||||||
|
return self.apply_modifiers("fly_speed", self.ancestry._fly_speed)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self):
|
def size(self):
|
||||||
return self.apply_modifiers("size", self.ancestry.size)
|
return self.apply_modifiers("size", self.ancestry.size)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vision(self):
|
||||||
|
return self.apply_modifiers("vision", self._vision)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vision_in_darkness(self):
|
||||||
|
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):
|
||||||
return sum(mapping.level for mapping in self.class_map)
|
return sum(mapping.level for mapping in self.class_map)
|
||||||
|
@ -228,7 +255,7 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
level_in_class = level_in_class[0]
|
level_in_class = level_in_class[0]
|
||||||
level_in_class.level = level
|
level_in_class.level = level
|
||||||
else:
|
else:
|
||||||
self.class_list.append(CharacterClassMap(character_id=self.id, character_class_id=newclass.id, level=level))
|
self.class_list.append(CharacterClassMap(character_id=self.id, character_class=newclass, level=level))
|
||||||
for lvl in range(1, level + 1):
|
for lvl in range(1, level + 1):
|
||||||
if not newclass.attributes_by_level[lvl]:
|
if not newclass.attributes_by_level[lvl]:
|
||||||
continue
|
continue
|
||||||
|
@ -252,15 +279,15 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
if attribute.name in self.class_attributes:
|
if attribute.name in self.class_attributes:
|
||||||
return True
|
return True
|
||||||
self.attribute_list.append(
|
self.attribute_list.append(
|
||||||
CharacterClassAttributeMap(
|
CharacterClassAttributeMap(character_id=self.id, class_attribute=attribute, option=option)
|
||||||
character_id=self.id, class_attribute_id=attribute.id, option_id=option.id
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def apply_modifiers(self, target, initial):
|
def apply_modifiers(self, target, initial):
|
||||||
modifiers = list(reversed(self.modifiers.get(target, [])))
|
modifiers = list(reversed(self.modifiers.get(target, [])))
|
||||||
|
if initial is None:
|
||||||
|
return initial
|
||||||
if isinstance(initial, int):
|
if isinstance(initial, int):
|
||||||
absolute = [mod for mod in modifiers if mod.absolute_value is not None]
|
absolute = [mod for mod in modifiers if mod.absolute_value is not None]
|
||||||
if absolute:
|
if absolute:
|
||||||
|
|
|
@ -15,11 +15,12 @@ class ModifierMap(BaseObject):
|
||||||
__tablename__ = "modifier_map"
|
__tablename__ = "modifier_map"
|
||||||
__table_args__ = (UniqueConstraint("primary_table_name", "primary_table_id", "modifier_id"),)
|
__table_args__ = (UniqueConstraint("primary_table_name", "primary_table_id", "modifier_id"),)
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
primary_table_name = Column(String, nullable=False)
|
|
||||||
primary_table_id = Column(Integer, nullable=False)
|
|
||||||
modifier_id = Column(Integer, ForeignKey("modifier.id"), nullable=False)
|
modifier_id = Column(Integer, ForeignKey("modifier.id"), nullable=False)
|
||||||
modifier = relationship("Modifier", uselist=False, lazy="immediate")
|
modifier = relationship("Modifier", uselist=False, lazy="immediate")
|
||||||
|
|
||||||
|
primary_table_name = Column(String, nullable=False)
|
||||||
|
primary_table_id = Column(Integer, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class Modifier(BaseObject):
|
class Modifier(BaseObject):
|
||||||
"""
|
"""
|
||||||
|
@ -67,8 +68,14 @@ class ModifierMixin:
|
||||||
def modifier_map(cls):
|
def modifier_map(cls):
|
||||||
return relationship(
|
return relationship(
|
||||||
"ModifierMap",
|
"ModifierMap",
|
||||||
primaryjoin=f"ModifierMap.primary_table_id == foreign({cls.__name__}.id)",
|
primaryjoin=(
|
||||||
|
"and_("
|
||||||
|
f"foreign(ModifierMap.primary_table_name)=='{cls.__tablename__}', "
|
||||||
|
f"foreign(ModifierMap.primary_table_id)=={cls.__name__}.id"
|
||||||
|
")"
|
||||||
|
),
|
||||||
cascade="all,delete,delete-orphan",
|
cascade="all,delete,delete-orphan",
|
||||||
|
overlaps="modifier_map,modifier_map",
|
||||||
single_parent=True,
|
single_parent=True,
|
||||||
uselist=True,
|
uselist=True,
|
||||||
lazy="immediate",
|
lazy="immediate",
|
||||||
|
@ -84,6 +91,7 @@ class ModifierMixin:
|
||||||
def add_modifier(self, modifier):
|
def add_modifier(self, modifier):
|
||||||
if modifier.absolute_value is not None and modifier.relative_value is not None and modifier.multiple_value:
|
if modifier.absolute_value is not None and modifier.relative_value is not None and modifier.multiple_value:
|
||||||
raise AttributeError(f"You must provide only one of absolute, relative, and multiple values {modifier}.")
|
raise AttributeError(f"You must provide only one of absolute, relative, and multiple values {modifier}.")
|
||||||
|
|
||||||
if [mod for mod in self.modifier_map if mod.modifier == modifier]:
|
if [mod for mod in self.modifier_map if mod.modifier == modifier]:
|
||||||
return False
|
return False
|
||||||
self.modifier_map.append(
|
self.modifier_map.append(
|
||||||
|
|
|
@ -97,7 +97,7 @@ def test_ancestries(db):
|
||||||
porc = schema.Ancestry(
|
porc = schema.Ancestry(
|
||||||
name="Pygmy Orc",
|
name="Pygmy Orc",
|
||||||
size="Small",
|
size="Small",
|
||||||
speed=25,
|
walk_speed=25,
|
||||||
)
|
)
|
||||||
db.add_or_update(porc)
|
db.add_or_update(porc)
|
||||||
assert porc.name == "Pygmy Orc"
|
assert porc.name == "Pygmy Orc"
|
||||||
|
@ -141,7 +141,8 @@ def test_modifiers(db, classes_factory, ancestries_factory):
|
||||||
|
|
||||||
# no modifiers; speed is ancestry speed
|
# no modifiers; speed is ancestry speed
|
||||||
carl = schema.Character(name="Carl", ancestry=ancestries["elf"])
|
carl = schema.Character(name="Carl", ancestry=ancestries["elf"])
|
||||||
db.add_or_update(carl)
|
marx = schema.Character(name="Marx", ancestry=ancestries["human"])
|
||||||
|
db.add_or_update([carl, marx])
|
||||||
assert carl.speed == carl.ancestry.speed == 30
|
assert carl.speed == carl.ancestry.speed == 30
|
||||||
|
|
||||||
cold = schema.Modifier(target="speed", relative_value=-10, name="Cold")
|
cold = schema.Modifier(target="speed", relative_value=-10, name="Cold")
|
||||||
|
@ -154,6 +155,9 @@ def test_modifiers(db, classes_factory, ancestries_factory):
|
||||||
assert carl.add_modifier(cold)
|
assert carl.add_modifier(cold)
|
||||||
assert carl.speed == 20
|
assert carl.speed == 20
|
||||||
|
|
||||||
|
# make sure modifiers only apply to carl. Carl is having a bad day.
|
||||||
|
assert marx.speed == 30
|
||||||
|
|
||||||
# speed is doubled
|
# speed is doubled
|
||||||
assert carl.remove_modifier(cold)
|
assert carl.remove_modifier(cold)
|
||||||
assert carl.add_modifier(hasted)
|
assert carl.add_modifier(hasted)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user