refactor modifiers

This commit is contained in:
evilchili
2024-04-21 21:30:24 -07:00
parent 36f6f831d9
commit 5db6e40eae
6 changed files with 154 additions and 86 deletions
+5 -2
View File
@@ -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
View File
@@ -1,4 +1,5 @@
from .character import *
from .classes import *
from .log import *
from .modifiers import *
from .property import *
+11 -65
View File
@@ -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]
+102
View File
@@ -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