modifiable columns subclass int/str
This commit is contained in:
parent
3980be5f07
commit
3292b11d89
|
@ -26,7 +26,7 @@ def bootstrap():
|
|||
rogue = schema.CharacterClass("rogue", hit_dice="1d8", hit_dice_stat="DEX")
|
||||
|
||||
# characters
|
||||
sabetha = schema.Character("Sabetha", ancestry=tiefling)
|
||||
sabetha = schema.Character("Sabetha", ancestry=tiefling, _intelligence=14)
|
||||
sabetha.add_class(fighter, level=2)
|
||||
sabetha.add_class(rogue, level=3)
|
||||
|
||||
|
@ -34,3 +34,5 @@ def bootstrap():
|
|||
|
||||
# persist all the records we've created
|
||||
db.add_or_update([sabetha, bob])
|
||||
|
||||
print(f"{sabetha.intelligence.bonus = }, {sabetha.size = }")
|
||||
|
|
|
@ -76,7 +76,7 @@ class SQLDatabaseManager:
|
|||
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
||||
|
||||
def init(self):
|
||||
self.session.configure(bind=self.engine, autoflush=False)
|
||||
self.session.configure(bind=self.engine)
|
||||
self.metadata.bind = self.engine
|
||||
self.metadata.create_all(self.engine)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|||
|
||||
from ttfrog.db.base import BaseObject, SavingThrowsMixin, SkillsMixin, SlugMixin
|
||||
from ttfrog.db.schema.classes import CharacterClass, ClassAttribute
|
||||
from ttfrog.db.schema.modifiers import Modifier, ModifierMixin
|
||||
from ttfrog.db.schema.modifiers import Modifier, ModifierMixin, Stat
|
||||
|
||||
__all__ = [
|
||||
"Ancestry",
|
||||
|
@ -141,21 +141,36 @@ class CharacterClassAttributeMap(BaseObject):
|
|||
)
|
||||
|
||||
|
||||
class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin, ModifierMixin):
|
||||
__tablename__ = "character"
|
||||
|
||||
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||
|
||||
name: Mapped[str] = mapped_column(default="New Character", nullable=False)
|
||||
armor_class: Mapped[int] = mapped_column(default=10, nullable=False, info={"min": 1, "max": 99})
|
||||
hit_points: Mapped[int] = mapped_column(default=1, nullable=False, info={"min": 0, "max": 999})
|
||||
max_hit_points: Mapped[int] = mapped_column(default=10, nullable=False, info={"min": 0, "max": 999})
|
||||
_armor_class: Mapped[int] = mapped_column(default=10, nullable=False, info={"min": 1, "max": 99, "modify": True})
|
||||
_hit_points: Mapped[int] = mapped_column(default=1, nullable=False, info={"min": 0, "max": 999, "modify": True})
|
||||
_max_hit_points: Mapped[int] = mapped_column(
|
||||
default=10, nullable=False, info={"min": 0, "max": 999, "modify": True}
|
||||
)
|
||||
temp_hit_points: Mapped[int] = mapped_column(default=0, nullable=False, info={"min": 0, "max": 999})
|
||||
strength: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
dexterity: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
constitution: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
intelligence: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
wisdom: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
charisma: Mapped[int] = mapped_column(nullable=False, default=10, info={"min": 0, "max": 30})
|
||||
_strength: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
_dexterity: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
_constitution: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
_intelligence: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
_wisdom: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
_charisma: Mapped[int] = mapped_column(
|
||||
nullable=False, default=10, info={"min": 0, "max": 30, "modify": True, "modify_class": Stat}
|
||||
)
|
||||
|
||||
_vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0})
|
||||
|
||||
|
@ -187,38 +202,6 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
|||
def traits(self):
|
||||
return self.ancestry.traits
|
||||
|
||||
@property
|
||||
def AC(self):
|
||||
return self.apply_modifiers("armor_class", self.armor_class)
|
||||
|
||||
@property
|
||||
def HP(self):
|
||||
return self.apply_modifiers("max_hit_points", self.max_hit_points)
|
||||
|
||||
@property
|
||||
def STR(self):
|
||||
return self.apply_modifiers("strength", self.strength)
|
||||
|
||||
@property
|
||||
def DEX(self):
|
||||
return self.apply_modifiers("dexterity", self.dexterity)
|
||||
|
||||
@property
|
||||
def CON(self):
|
||||
return self.apply_modifiers("constitution", self.constitution)
|
||||
|
||||
@property
|
||||
def INT(self):
|
||||
return self.apply_modifiers("intelligence", self.intelligence)
|
||||
|
||||
@property
|
||||
def WIS(self):
|
||||
return self.apply_modifiers("wisdom", self.wisdom)
|
||||
|
||||
@property
|
||||
def CHA(self):
|
||||
return self.apply_modifiers("charisma", self.charisma)
|
||||
|
||||
@property
|
||||
def speed(self):
|
||||
return self.apply_modifiers("speed", self.ancestry.speed)
|
||||
|
@ -233,15 +216,11 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
|||
|
||||
@property
|
||||
def fly_speed(self):
|
||||
return self.apply_modifiers("fly_speed", self.ancestry._fly_speed)
|
||||
return self.apply_modifiers("fly_speed", self.ancestry.fly_speed)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return self.apply_modifiers("size", self.ancestry.size)
|
||||
|
||||
@property
|
||||
def vision(self):
|
||||
return self.apply_modifiers("vision", self._vision)
|
||||
return self._apply_modifiers("size", self.ancestry.size)
|
||||
|
||||
@property
|
||||
def vision_in_darkness(self):
|
||||
|
@ -295,21 +274,3 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
|||
)
|
||||
return True
|
||||
return False
|
||||
|
||||
def apply_modifiers(self, target, initial):
|
||||
modifiers = list(reversed(self.modifiers.get(target, [])))
|
||||
if initial is None:
|
||||
return initial
|
||||
if isinstance(initial, int):
|
||||
absolute = [mod for mod in modifiers if mod.absolute_value is not None]
|
||||
if absolute:
|
||||
return absolute[0].absolute_value
|
||||
multiple = [mod for mod in modifiers if mod.multiply_value is not None]
|
||||
if multiple:
|
||||
return int(initial * multiple[0].multiply_value + 0.5)
|
||||
return initial + sum(mod.relative_value for mod in modifiers if mod.relative_value is not None)
|
||||
|
||||
new = [mod for mod in modifiers if mod.new_value is not None]
|
||||
if new:
|
||||
return new[0].new_value
|
||||
return initial
|
||||
|
|
|
@ -7,6 +7,34 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|||
from ttfrog.db.base import BaseObject
|
||||
|
||||
|
||||
class Modifiable:
|
||||
def __new__(cls, base, modified=None):
|
||||
cls.base = base
|
||||
return super().__new__(cls, modified)
|
||||
|
||||
|
||||
class ModifiableStr(Modifiable, str):
|
||||
"""
|
||||
A string that also has a '.base' property.
|
||||
"""
|
||||
|
||||
|
||||
class ModifiableInt(Modifiable, int):
|
||||
"""
|
||||
An integer that also has a '.base' property
|
||||
"""
|
||||
|
||||
|
||||
class Stat(ModifiableInt):
|
||||
"""
|
||||
Same as a Score except it also has a bonus for STR, DEX, CON, etc.
|
||||
"""
|
||||
|
||||
@property
|
||||
def bonus(self):
|
||||
return int((self - 10) / 2)
|
||||
|
||||
|
||||
class ModifierMap(BaseObject):
|
||||
"""
|
||||
Creates a many-to-many between Modifier and any model inheriting from the ModifierMixin.
|
||||
|
@ -64,8 +92,6 @@ class ModifierMixin:
|
|||
{'strength': [Modifier(id=1, target='strength', name='STR+1', relative_value=1 ... ]}
|
||||
"""
|
||||
|
||||
_modifiable_attributes = dict()
|
||||
|
||||
@declared_attr
|
||||
def modifier_map(cls):
|
||||
return relationship(
|
||||
|
@ -111,31 +137,42 @@ class ModifierMixin:
|
|||
self.modifier_map = [mapping for mapping in self.modifier_map if mapping.modifier != modifier]
|
||||
return True
|
||||
|
||||
def apply_modifiers(self, target, initial):
|
||||
if not self._modifiable_attributes:
|
||||
raise NotImplementedError(
|
||||
f"You must define the '_modifiable_attributes' property on {self.__class__.__name__}."
|
||||
)
|
||||
def _apply_modifiers(self, target, initial, modify_class=None):
|
||||
if not modify_class:
|
||||
modify_class = globals()["ModifiableInt"] if isinstance(initial, int) else globals()["ModifiableStr"]
|
||||
|
||||
# get the modifiers in order from most to least recent
|
||||
modifiers = list(reversed(self.modifiers.get(target, [])))
|
||||
if initial is None:
|
||||
return initial
|
||||
|
||||
if isinstance(initial, int):
|
||||
absolute = [mod for mod in modifiers if mod.absolute_value is not None]
|
||||
if absolute:
|
||||
return absolute[0].absolute_value
|
||||
multiple = [mod for mod in modifiers if mod.multiply_value is not None]
|
||||
if multiple:
|
||||
return int(initial * multiple[0].multiply_value + 0.5)
|
||||
return initial + sum(mod.relative_value for mod in modifiers if mod.relative_value is not None)
|
||||
modified = absolute[0].absolute_value
|
||||
else:
|
||||
multiple = [mod for mod in modifiers if mod.multiply_value is not None]
|
||||
if multiple:
|
||||
modified = int(initial * multiple[0].multiply_value + 0.5)
|
||||
else:
|
||||
modified = initial + sum(mod.relative_value for mod in modifiers if mod.relative_value is not None)
|
||||
else:
|
||||
new = [mod for mod in modifiers if mod.new_value is not None]
|
||||
if new:
|
||||
modified = new[0].new_value
|
||||
else:
|
||||
modified = initial
|
||||
|
||||
new = [mod for mod in modifiers if mod.new_value is not None]
|
||||
if new:
|
||||
return new[0].new_value
|
||||
return initial
|
||||
return modify_class(base=initial, modified=modified)
|
||||
|
||||
def __setattr__(self, attr_name, value):
|
||||
col = getattr(self.__table__.columns, f"_{attr_name}", None)
|
||||
if col is not None and col.info.get("modify", False):
|
||||
raise AttributeError(f"You cannot set .{attr_name}. Did you mean ._{attr_name}?")
|
||||
return super().__setattr__(attr_name, value)
|
||||
|
||||
def __getattr__(self, attr_name):
|
||||
prop = self._modifiable_attributes.get(attr_name, None)
|
||||
if not prop:
|
||||
raise AttributeError(f"Attribute not found: {attr_name}")
|
||||
return self.apply_modifiers(attr_name, getattr(self, prop))
|
||||
col = getattr(self.__table__.columns, f"_{attr_name}", None)
|
||||
if col is not None and col.info.get("modify", False):
|
||||
return self._apply_modifiers(
|
||||
attr_name, getattr(self, col.name), modify_class=col.info.get("modify_class", None)
|
||||
)
|
||||
return super().__getattr__(attr_name)
|
||||
|
|
Loading…
Reference in New Issue
Block a user