refactor modifiers
This commit is contained in:
@@ -59,8 +59,11 @@ class SQLDatabaseManager:
|
||||
tm.abort()
|
||||
raise
|
||||
|
||||
def add_or_update(self, *args, **kwargs):
|
||||
self.session.add(*args, **kwargs)
|
||||
def add_or_update(self, record, *args, **kwargs):
|
||||
if not isinstance(record, list):
|
||||
record = [record]
|
||||
for rec in record:
|
||||
self.session.add(rec, *args, **kwargs)
|
||||
self.session.flush()
|
||||
|
||||
def query(self, *args, **kwargs):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .character import *
|
||||
from .classes import *
|
||||
from .log import *
|
||||
from .modifiers import *
|
||||
from .property import *
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from sqlalchemy import Column, Enum, ForeignKey, Integer, Float, String, Text, UniqueConstraint
|
||||
from sqlalchemy import Column, Enum, ForeignKey, Integer, String, Text, UniqueConstraint
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy.orm import relationship, validates
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from ttfrog.db.base import BaseObject, CreatureTypesEnum, SavingThrowsMixin, SizesEnum, SkillsMixin, SlugMixin
|
||||
from ttfrog.db.schema.modifiers import Modifier, ModifierMixin
|
||||
|
||||
__all__ = [
|
||||
"Ancestry",
|
||||
"AncestryTrait",
|
||||
"AncestryTraitMap",
|
||||
"AncestryModifier",
|
||||
"CharacterClassMap",
|
||||
"CharacterClassAttributeMap",
|
||||
"Character",
|
||||
@@ -40,10 +38,9 @@ class AncestryTraitMap(BaseObject):
|
||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20})
|
||||
|
||||
|
||||
# XXX: Replace this with a many-to-many on the Modifiers table. Will need for proficiecies too.
|
||||
class Ancestry(BaseObject):
|
||||
class Ancestry(BaseObject, ModifierMixin):
|
||||
"""
|
||||
A character ancestry ("race"), which has zero or more AncestryTraits.
|
||||
A character ancestry ("race"), which has zero or more AncestryTraits and Modifiers.
|
||||
"""
|
||||
|
||||
__tablename__ = "ancestry"
|
||||
@@ -52,8 +49,7 @@ class Ancestry(BaseObject):
|
||||
creature_type = Column(Enum(CreatureTypesEnum), nullable=False, default="humanoid")
|
||||
size = Column(Enum(SizesEnum), nullable=False, default="Medium")
|
||||
speed = Column(Integer, nullable=False, default=30, info={"min": 0, "max": 99})
|
||||
_traits = relationship("AncestryTraitMap", lazy="immediate")
|
||||
modifiers = relationship("AncestryModifier", lazy="immediate")
|
||||
_traits = relationship("AncestryTraitMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||
|
||||
@property
|
||||
def traits(self):
|
||||
@@ -65,33 +61,10 @@ class Ancestry(BaseObject):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_modifier(self, modifier):
|
||||
if modifier not in self.modifiers:
|
||||
self.modifiers.append(modifier)
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class AncestryModifier(BaseObject):
|
||||
"""
|
||||
A modifier granted to a character via its Ancestry.
|
||||
"""
|
||||
|
||||
__tablename__ = "ancestry_modifier"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False)
|
||||
name = Column(String, nullable=False)
|
||||
target = Column(String, nullable=False)
|
||||
absolute_value = Column(Integer)
|
||||
relative_value = Column(Integer)
|
||||
multiply_value = Column(Float)
|
||||
new_value = Column(String)
|
||||
description = Column(String, nullable=False)
|
||||
|
||||
|
||||
class AncestryTrait(BaseObject):
|
||||
"""
|
||||
A trait granted to a character via its Ancestry.
|
||||
@@ -142,20 +115,7 @@ class CharacterClassAttributeMap(BaseObject):
|
||||
)
|
||||
|
||||
|
||||
class Modifier(BaseObject):
|
||||
__tablename__ = "modifier"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
|
||||
target = Column(String, nullable=False)
|
||||
absolute_value = Column(Integer)
|
||||
relative_value = Column(Integer)
|
||||
multiply_value = Column(Float)
|
||||
new_value = Column(String)
|
||||
description = Column(String, nullable=False)
|
||||
|
||||
|
||||
class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
__tablename__ = "character"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, default="New Character", nullable=False)
|
||||
@@ -174,7 +134,6 @@ class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
class_map = relationship("CharacterClassMap", cascade="all,delete,delete-orphan")
|
||||
class_list = association_proxy("class_map", "id", creator=class_map_creator)
|
||||
|
||||
_modifiers = relationship("Modifier", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||
_modify_ok = [
|
||||
"armor_class",
|
||||
"max_hit_points",
|
||||
@@ -196,12 +155,10 @@ class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
|
||||
@property
|
||||
def modifiers(self):
|
||||
all_modifiers = defaultdict(list)
|
||||
for mod in self.ancestry.modifiers:
|
||||
all_modifiers[mod.target].append(mod)
|
||||
for mod in self._modifiers:
|
||||
all_modifiers[mod.target].append(mod)
|
||||
return all_modifiers
|
||||
unified = {}
|
||||
unified.update(**self.ancestry.modifiers)
|
||||
unified.update(**super().modifiers)
|
||||
return unified
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
@@ -302,7 +259,6 @@ class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def apply_modifiers(self, target, initial):
|
||||
modifiers = list(reversed(self.modifiers.get(target, [])))
|
||||
if isinstance(initial, int):
|
||||
@@ -318,13 +274,3 @@ class Character(BaseObject, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||
if new:
|
||||
return new[0].new_value
|
||||
return initial
|
||||
|
||||
|
||||
|
||||
def add_modifier(self, modifier):
|
||||
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}.")
|
||||
self._modifiers.append(modifier)
|
||||
|
||||
def remove_modifier(self, modifier):
|
||||
self._modifiers = [mod for mod in self._modifiers if mod != modifier]
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from sqlalchemy import Column, Float, ForeignKey, Integer, String, UniqueConstraint
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from ttfrog.db.base import BaseObject
|
||||
|
||||
|
||||
class ModifierMap(BaseObject):
|
||||
"""
|
||||
Creates a many-to-many between Modifier and any model inheriting from the ModifierMixin.
|
||||
"""
|
||||
|
||||
__tablename__ = "modifier_map"
|
||||
__table_args__ = (UniqueConstraint("primary_table_name", "primary_table_id", "modifier_id"),)
|
||||
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 = relationship("Modifier", uselist=False, lazy="immediate")
|
||||
|
||||
|
||||
class Modifier(BaseObject):
|
||||
"""
|
||||
Modifiers modify the base value of an existing attribute on another table.
|
||||
|
||||
Modifiers are applied by the Character class, but may be associated with any model via the
|
||||
ModifierMixIn model; refer to the Ancestry class for an example.
|
||||
"""
|
||||
|
||||
__tablename__ = "modifier"
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
target = Column(String, nullable=False)
|
||||
absolute_value = Column(Integer)
|
||||
relative_value = Column(Integer)
|
||||
multiply_value = Column(Float)
|
||||
new_value = Column(String)
|
||||
name = Column(String, nullable=False)
|
||||
description = Column(String)
|
||||
|
||||
|
||||
class ModifierMixin:
|
||||
"""
|
||||
Add modifiers to an existing class.
|
||||
|
||||
Attributes:
|
||||
modifier_map - get/set a list of Modifier records associated with the parent
|
||||
modifiers - read-only dict of lists of modifiers keyed on Modifier.target
|
||||
|
||||
Methods:
|
||||
add_modifier - Add a Modifier association to the modifier_map
|
||||
remove_modifier - Remove a modifier association from the modifier_map
|
||||
|
||||
Example:
|
||||
|
||||
>>> class Item(BaseObject, ModifierMixin):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String, nullable=False)
|
||||
>>> dwarven_belt = Item(name="Dwarven Belt")
|
||||
>>> dwarven_belt.add_modifier(Modifier(name="STR+1", target="strength", relative_value=1))
|
||||
>>> dwarven_belt.modifiers
|
||||
{'strength': [Modifier(id=1, target='strength', name='STR+1', relative_value=1 ... ]}
|
||||
"""
|
||||
|
||||
@declared_attr
|
||||
def modifier_map(cls):
|
||||
return relationship(
|
||||
"ModifierMap",
|
||||
primaryjoin=f"ModifierMap.primary_table_id == foreign({cls.__name__}.id)",
|
||||
cascade="all,delete,delete-orphan",
|
||||
single_parent=True,
|
||||
uselist=True,
|
||||
lazy="immediate",
|
||||
)
|
||||
|
||||
@property
|
||||
def modifiers(self):
|
||||
all_modifiers = defaultdict(list)
|
||||
for mapping in self.modifier_map:
|
||||
all_modifiers[mapping.modifier.target].append(mapping.modifier)
|
||||
return all_modifiers
|
||||
|
||||
def add_modifier(self, modifier):
|
||||
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}.")
|
||||
if [mod for mod in self.modifier_map if mod.modifier == modifier]:
|
||||
return False
|
||||
self.modifier_map.append(
|
||||
ModifierMap(
|
||||
primary_table_name=self.__tablename__,
|
||||
primary_table_id=self.id,
|
||||
modifier=modifier,
|
||||
)
|
||||
)
|
||||
return True
|
||||
|
||||
def remove_modifier(self, modifier):
|
||||
if modifier.id not in [mod.modifier_id for mod in self.modifier_map]:
|
||||
return False
|
||||
self.modifier_map = [mapping for mapping in self.modifier_map if mapping.modifier != modifier]
|
||||
return True
|
||||
Reference in New Issue
Block a user