convert to modern MappedAsDataclass models
This commit is contained in:
parent
1ff0e5ca7d
commit
3980be5f07
|
@ -2,9 +2,9 @@ import enum
|
||||||
|
|
||||||
import nanoid
|
import nanoid
|
||||||
from nanoid_dictionary import human_alphabet
|
from nanoid_dictionary import human_alphabet
|
||||||
from pyramid_sqlalchemy import BaseObject as _BaseObject
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
from sqlalchemy import Column, String
|
from sqlalchemy import Column, String
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass
|
||||||
|
|
||||||
|
|
||||||
def genslug():
|
def genslug():
|
||||||
|
@ -19,7 +19,7 @@ class SlugMixin:
|
||||||
return "-".join([self.slug, slugify(self.name.title().replace(" ", ""), ok="", only_ascii=True, lower=False)])
|
return "-".join([self.slug, slugify(self.name.title().replace(" ", ""), ok="", only_ascii=True, lower=False)])
|
||||||
|
|
||||||
|
|
||||||
class BaseObject(_BaseObject):
|
class BaseObject(MappedAsDataclass, DeclarativeBase):
|
||||||
"""
|
"""
|
||||||
Allows for iterating over Model objects' column names and values
|
Allows for iterating over Model objects' column names and values
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -7,30 +7,30 @@ def bootstrap():
|
||||||
db.init()
|
db.init()
|
||||||
with db.transaction():
|
with db.transaction():
|
||||||
# ancestries
|
# ancestries
|
||||||
human = schema.Ancestry(name="human")
|
human = schema.Ancestry("human")
|
||||||
tiefling = schema.Ancestry(name="tiefling")
|
tiefling = schema.Ancestry("tiefling")
|
||||||
tiefling.add_modifier(schema.Modifier(name="Ability Score Increase", target="intelligence", relative_value=1))
|
tiefling.add_modifier(schema.Modifier("Ability Score Increase", target="intelligence", relative_value=1))
|
||||||
tiefling.add_modifier(schema.Modifier(name="Ability Score Increase", target="charisma", relative_value=2))
|
tiefling.add_modifier(schema.Modifier("Ability Score Increase", target="charisma", relative_value=2))
|
||||||
darkvision = schema.AncestryTrait(
|
darkvision = schema.AncestryTrait(
|
||||||
name="Darkvision",
|
"Darkvision",
|
||||||
description=(
|
description=(
|
||||||
"You can see in dim light within 60 feet of you as if it were bright light, and in darkness as if it "
|
"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."
|
"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))
|
darkvision.add_modifier(schema.Modifier("Darkvision", target="vision_in_darkness", absolute_value=120))
|
||||||
tiefling.add_trait(darkvision)
|
tiefling.add_trait(darkvision)
|
||||||
|
|
||||||
# classes
|
# classes
|
||||||
fighter = schema.CharacterClass(name="fighter", hit_dice="1d10", hit_dice_stat="CON")
|
fighter = schema.CharacterClass("fighter", hit_dice="1d10", hit_dice_stat="CON")
|
||||||
rogue = schema.CharacterClass(name="rogue", hit_dice="1d8", hit_dice_stat="DEX")
|
rogue = schema.CharacterClass("rogue", hit_dice="1d8", hit_dice_stat="DEX")
|
||||||
|
|
||||||
# characters
|
# characters
|
||||||
sabetha = schema.Character(name="Sabetha", ancestry=tiefling)
|
sabetha = schema.Character("Sabetha", ancestry=tiefling)
|
||||||
sabetha.add_class(fighter, level=2)
|
sabetha.add_class(fighter, level=2)
|
||||||
sabetha.add_class(rogue, level=3)
|
sabetha.add_class(rogue, level=3)
|
||||||
|
|
||||||
bob = schema.Character(name="Bob", ancestry=human)
|
bob = schema.Character("Bob", ancestry=human)
|
||||||
|
|
||||||
# persist all the records we've created
|
# persist all the records we've created
|
||||||
db.add_or_update([sabetha, bob])
|
db.add_or_update([sabetha, bob])
|
||||||
|
|
|
@ -6,8 +6,7 @@ from contextlib import contextmanager
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
||||||
import transaction
|
import transaction
|
||||||
from pyramid_sqlalchemy import Session, init_sqlalchemy
|
from pyramid_sqlalchemy.meta import Session
|
||||||
from pyramid_sqlalchemy import metadata as _metadata
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
import ttfrog.db.schema
|
import ttfrog.db.schema
|
||||||
|
@ -43,7 +42,7 @@ class SQLDatabaseManager:
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def metadata(self):
|
def metadata(self):
|
||||||
return _metadata
|
return ttfrog.db.schema.BaseObject.metadata
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def tables(self):
|
def tables(self):
|
||||||
|
@ -77,7 +76,8 @@ class SQLDatabaseManager:
|
||||||
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
return base64.urlsafe_b64encode(sha1bytes.digest()).decode("ascii")[:10]
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
init_sqlalchemy(self.engine)
|
self.session.configure(bind=self.engine, autoflush=False)
|
||||||
|
self.metadata.bind = self.engine
|
||||||
self.metadata.create_all(self.engine)
|
self.metadata.create_all(self.engine)
|
||||||
|
|
||||||
def dump(self, names: list = []):
|
def dump(self, names: list = []):
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from sqlalchemy import Column, Enum, ForeignKey, Integer, String, Text, UniqueConstraint
|
from sqlalchemy import ForeignKey, Text, UniqueConstraint
|
||||||
from sqlalchemy.ext.associationproxy import association_proxy
|
from sqlalchemy.ext.associationproxy import association_proxy
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from ttfrog.db.base import BaseObject, CreatureTypesEnum, SavingThrowsMixin, SizesEnum, SkillsMixin, SlugMixin
|
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
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
@ -31,11 +32,11 @@ def attr_map_creator(fields):
|
||||||
class AncestryTraitMap(BaseObject):
|
class AncestryTraitMap(BaseObject):
|
||||||
__tablename__ = "trait_map"
|
__tablename__ = "trait_map"
|
||||||
__table_args__ = (UniqueConstraint("ancestry_id", "ancestry_trait_id"),)
|
__table_args__ = (UniqueConstraint("ancestry_id", "ancestry_trait_id"),)
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"))
|
ancestry_id: Mapped[int] = mapped_column(ForeignKey("ancestry.id"))
|
||||||
ancestry_trait_id = Column(Integer, ForeignKey("ancestry_trait.id"))
|
ancestry_trait_id: Mapped[int] = mapped_column(ForeignKey("ancestry_trait.id"), init=False)
|
||||||
trait = relationship("AncestryTrait", uselist=False, lazy="immediate")
|
trait: Mapped["AncestryTrait"] = relationship(uselist=False, lazy="immediate")
|
||||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20})
|
level: Mapped[int] = mapped_column(nullable=False, info={"min": 1, "max": 20})
|
||||||
|
|
||||||
|
|
||||||
class Ancestry(BaseObject, ModifierMixin):
|
class Ancestry(BaseObject, ModifierMixin):
|
||||||
|
@ -44,15 +45,20 @@ class Ancestry(BaseObject, ModifierMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "ancestry"
|
__tablename__ = "ancestry"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, index=True, unique=True)
|
name: Mapped[str] = mapped_column(unique=True, nullable=False)
|
||||||
creature_type = Column(Enum(CreatureTypesEnum), nullable=False, default="humanoid")
|
|
||||||
size = Column(Enum(SizesEnum), nullable=False, default="Medium")
|
creature_type: Mapped[str] = mapped_column(nullable=False, default="humanoid")
|
||||||
walk_speed = Column(Integer, nullable=False, default=30, info={"min": 0, "max": 99})
|
size: Mapped[str] = mapped_column(nullable=False, default="medium")
|
||||||
_fly_speed = Column(Integer, info={"min": 0, "max": 99})
|
walk_speed: Mapped[int] = mapped_column(nullable=False, default=30, info={"min": 0, "max": 99})
|
||||||
_climb_speed = Column(Integer, info={"min": 0, "max": 99})
|
|
||||||
_swim_speed = Column(Integer, info={"min": 0, "max": 99})
|
_fly_speed: Mapped[int] = mapped_column(init=False, nullable=True, info={"min": 0, "max": 99})
|
||||||
_traits = relationship("AncestryTraitMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
_climb_speed: Mapped[int] = mapped_column(init=False, nullable=True, info={"min": 0, "max": 99})
|
||||||
|
_swim_speed: Mapped[int] = mapped_column(init=False, nullable=True, info={"min": 0, "max": 99})
|
||||||
|
|
||||||
|
_traits = relationship(
|
||||||
|
"AncestryTraitMap", init=False, uselist=True, cascade="all,delete,delete-orphan", lazy="immediate"
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def traits(self):
|
def traits(self):
|
||||||
|
@ -71,8 +77,12 @@ class Ancestry(BaseObject, ModifierMixin):
|
||||||
return self._swim_speed or int(self.speed / 2)
|
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 not self._traits or trait not in self._traits:
|
||||||
self._traits.append(AncestryTraitMap(ancestry_id=self.id, trait=trait, level=level))
|
mapping = AncestryTraitMap(ancestry_id=self.id, trait=trait, level=level)
|
||||||
|
if not self._traits:
|
||||||
|
self._traits = [mapping]
|
||||||
|
else:
|
||||||
|
self._traits.append(mapping)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -86,9 +96,9 @@ class AncestryTrait(BaseObject, ModifierMixin):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "ancestry_trait"
|
__tablename__ = "ancestry_trait"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, nullable=False)
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
description = Column(Text)
|
description: Mapped[Text] = mapped_column(Text, default="")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -97,13 +107,14 @@ class AncestryTrait(BaseObject, ModifierMixin):
|
||||||
class CharacterClassMap(BaseObject):
|
class CharacterClassMap(BaseObject):
|
||||||
__tablename__ = "class_map"
|
__tablename__ = "class_map"
|
||||||
__table_args__ = (UniqueConstraint("character_id", "character_class_id"),)
|
__table_args__ = (UniqueConstraint("character_id", "character_class_id"),)
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
|
character: Mapped["Character"] = relationship(uselist=False, viewonly=True)
|
||||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), nullable=False)
|
character_class: Mapped["CharacterClass"] = relationship(lazy="immediate")
|
||||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20}, default=1)
|
|
||||||
|
|
||||||
character_class = relationship("CharacterClass", lazy="immediate")
|
character_id: Mapped[int] = mapped_column(ForeignKey("character.id"), init=False, nullable=False)
|
||||||
character = relationship("Character", uselist=False, viewonly=True)
|
character_class_id: Mapped[int] = mapped_column(ForeignKey("character_class.id"), init=False, nullable=False)
|
||||||
|
|
||||||
|
level: Mapped[int] = mapped_column(nullable=False, info={"min": 1, "max": 20}, default=1)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{self.character.name}, {self.character_class.name}, level {self.level}"
|
return "{self.character.name}, {self.character_class.name}, level {self.level}"
|
||||||
|
@ -112,12 +123,12 @@ class CharacterClassMap(BaseObject):
|
||||||
class CharacterClassAttributeMap(BaseObject):
|
class CharacterClassAttributeMap(BaseObject):
|
||||||
__tablename__ = "character_class_attribute_map"
|
__tablename__ = "character_class_attribute_map"
|
||||||
__table_args__ = (UniqueConstraint("character_id", "class_attribute_id"),)
|
__table_args__ = (UniqueConstraint("character_id", "class_attribute_id"),)
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
character_id = Column(Integer, ForeignKey("character.id"), nullable=False)
|
character_id: Mapped[int] = mapped_column(ForeignKey("character.id"), nullable=False)
|
||||||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False)
|
class_attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), nullable=False)
|
||||||
option_id = Column(Integer, ForeignKey("class_attribute_option.id"), nullable=False)
|
option_id: Mapped[int] = mapped_column(ForeignKey("class_attribute_option.id"), nullable=False)
|
||||||
|
|
||||||
class_attribute = relationship("ClassAttribute", lazy="immediate")
|
class_attribute: Mapped["ClassAttribute"] = relationship(lazy="immediate")
|
||||||
option = relationship("ClassAttributeOption", lazy="immediate")
|
option = relationship("ClassAttributeOption", lazy="immediate")
|
||||||
|
|
||||||
character_class = relationship(
|
character_class = relationship(
|
||||||
|
@ -132,22 +143,23 @@ class CharacterClassAttributeMap(BaseObject):
|
||||||
|
|
||||||
class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsMixin):
|
||||||
__tablename__ = "character"
|
__tablename__ = "character"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, default="New Character", nullable=False)
|
|
||||||
armor_class = Column(Integer, default=10, nullable=False, info={"min": 1, "max": 99})
|
|
||||||
hit_points = Column(Integer, default=1, nullable=False, info={"min": 0, "max": 999})
|
|
||||||
max_hit_points = Column(Integer, default=10, nullable=False, info={"min": 0, "max": 999})
|
|
||||||
temp_hit_points = Column(Integer, default=0, nullable=False, info={"min": 0, "max": 999})
|
|
||||||
strength = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
|
||||||
dexterity = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
|
||||||
constitution = 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})
|
|
||||||
charisma = Column(Integer, nullable=False, default=10, info={"min": 0, "max": 30})
|
|
||||||
|
|
||||||
_vision = Column(Integer, info={"min": 0})
|
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})
|
||||||
|
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})
|
||||||
|
|
||||||
proficiencies = Column(String)
|
_vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0})
|
||||||
|
|
||||||
|
proficiencies: Mapped[str] = mapped_column(nullable=False, default="")
|
||||||
|
|
||||||
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)
|
||||||
|
@ -155,8 +167,8 @@ class Character(BaseObject, ModifierMixin, SlugMixin, SavingThrowsMixin, SkillsM
|
||||||
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)
|
||||||
|
|
||||||
ancestry_id = Column(Integer, ForeignKey("ancestry.id"), nullable=False, default="1")
|
ancestry_id: Mapped[int] = mapped_column(ForeignKey("ancestry.id"), nullable=False, default="1")
|
||||||
ancestry = relationship("Ancestry", uselist=False)
|
ancestry: Mapped["Ancestry"] = relationship(uselist=False, default=None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def modifiers(self):
|
def modifiers(self):
|
||||||
|
@ -255,7 +267,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=newclass, level=level))
|
self.class_list.append(CharacterClassMap(character=self, 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
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from sqlalchemy import Column, Enum, ForeignKey, Integer, String
|
from sqlalchemy import ForeignKey
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from ttfrog.db.base import BaseObject, SavingThrowsMixin, SkillsMixin, StatsEnum
|
from ttfrog.db.base import BaseObject, SavingThrowsMixin, SkillsMixin
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"ClassAttributeMap",
|
"ClassAttributeMap",
|
||||||
|
@ -15,16 +15,16 @@ __all__ = [
|
||||||
|
|
||||||
class ClassAttributeMap(BaseObject):
|
class ClassAttributeMap(BaseObject):
|
||||||
__tablename__ = "class_attribute_map"
|
__tablename__ = "class_attribute_map"
|
||||||
class_attribute_id = Column(Integer, ForeignKey("class_attribute.id"), primary_key=True)
|
class_attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), primary_key=True)
|
||||||
character_class_id = Column(Integer, ForeignKey("character_class.id"), primary_key=True)
|
character_class_id: Mapped[int] = mapped_column(ForeignKey("character_class.id"), primary_key=True)
|
||||||
level = Column(Integer, nullable=False, info={"min": 1, "max": 20}, default=1)
|
level: Mapped[int] = mapped_column(nullable=False, info={"min": 1, "max": 20}, default=1)
|
||||||
attribute = relationship("ClassAttribute", uselist=False, viewonly=True, lazy="immediate")
|
attribute = relationship("ClassAttribute", uselist=False, viewonly=True, lazy="immediate")
|
||||||
|
|
||||||
|
|
||||||
class ClassAttribute(BaseObject):
|
class ClassAttribute(BaseObject):
|
||||||
__tablename__ = "class_attribute"
|
__tablename__ = "class_attribute"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, nullable=False)
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate")
|
options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@ -33,18 +33,18 @@ class ClassAttribute(BaseObject):
|
||||||
|
|
||||||
class ClassAttributeOption(BaseObject):
|
class ClassAttributeOption(BaseObject):
|
||||||
__tablename__ = "class_attribute_option"
|
__tablename__ = "class_attribute_option"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, nullable=False)
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
attribute_id = Column(Integer, ForeignKey("class_attribute.id"), nullable=False)
|
attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class CharacterClass(BaseObject, SavingThrowsMixin, SkillsMixin):
|
class CharacterClass(BaseObject, SavingThrowsMixin, SkillsMixin):
|
||||||
__tablename__ = "character_class"
|
__tablename__ = "character_class"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, index=True, unique=True)
|
name: Mapped[str] = mapped_column(index=True, unique=True)
|
||||||
hit_dice = Column(String, default="1d6")
|
hit_dice: Mapped[str] = mapped_column(default="1d6")
|
||||||
hit_dice_stat = Column(Enum(StatsEnum))
|
hit_dice_stat: Mapped[str] = mapped_column(default="")
|
||||||
proficiencies = Column(String)
|
proficiencies: Mapped[str] = mapped_column(default="")
|
||||||
attributes = relationship("ClassAttributeMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
attributes = relationship("ClassAttributeMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from sqlalchemy import Column, Float, ForeignKey, Integer, String, UniqueConstraint
|
from sqlalchemy import ForeignKey, UniqueConstraint
|
||||||
from sqlalchemy.ext.declarative import declared_attr
|
from sqlalchemy.ext.declarative import declared_attr
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||||
|
|
||||||
from ttfrog.db.base import BaseObject
|
from ttfrog.db.base import BaseObject
|
||||||
|
|
||||||
|
@ -14,12 +14,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: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
modifier_id = Column(Integer, ForeignKey("modifier.id"), nullable=False)
|
modifier_id: Mapped[int] = mapped_column(ForeignKey("modifier.id"), init=False)
|
||||||
modifier = relationship("Modifier", uselist=False, lazy="immediate")
|
modifier: Mapped["Modifier"] = relationship(uselist=False, lazy="immediate")
|
||||||
|
|
||||||
primary_table_name = Column(String, nullable=False)
|
primary_table_name: Mapped[str] = mapped_column(nullable=False)
|
||||||
primary_table_id = Column(Integer, nullable=False)
|
primary_table_id: Mapped[int] = mapped_column(nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class Modifier(BaseObject):
|
class Modifier(BaseObject):
|
||||||
|
@ -31,14 +31,14 @@ class Modifier(BaseObject):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "modifier"
|
__tablename__ = "modifier"
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
target = Column(String, nullable=False)
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
absolute_value = Column(Integer)
|
target: Mapped[str] = mapped_column(nullable=False)
|
||||||
relative_value = Column(Integer)
|
absolute_value: Mapped[int] = mapped_column(nullable=True, default=None)
|
||||||
multiply_value = Column(Float)
|
relative_value: Mapped[int] = mapped_column(nullable=True, default=None)
|
||||||
new_value = Column(String)
|
multiply_value: Mapped[float] = mapped_column(nullable=True, default=None)
|
||||||
name = Column(String, nullable=False)
|
new_value: Mapped[str] = mapped_column(nullable=True, default=None)
|
||||||
description = Column(String)
|
description: Mapped[str] = mapped_column(default="")
|
||||||
|
|
||||||
|
|
||||||
class ModifierMixin:
|
class ModifierMixin:
|
||||||
|
@ -56,14 +56,16 @@ class ModifierMixin:
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
>>> class Item(BaseObject, ModifierMixin):
|
>>> class Item(BaseObject, ModifierMixin):
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
||||||
name = Column(String, nullable=False)
|
name: Mapped[str] = mapped_column(nullable=False)
|
||||||
>>> dwarven_belt = Item(name="Dwarven Belt")
|
>>> dwarven_belt = Item(name="Dwarven Belt")
|
||||||
>>> dwarven_belt.add_modifier(Modifier(name="STR+1", target="strength", relative_value=1))
|
>>> dwarven_belt.add_modifier(Modifier(name="STR+1", target="strength", relative_value=1))
|
||||||
>>> dwarven_belt.modifiers
|
>>> dwarven_belt.modifiers
|
||||||
{'strength': [Modifier(id=1, target='strength', name='STR+1', relative_value=1 ... ]}
|
{'strength': [Modifier(id=1, target='strength', name='STR+1', relative_value=1 ... ]}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_modifiable_attributes = dict()
|
||||||
|
|
||||||
@declared_attr
|
@declared_attr
|
||||||
def modifier_map(cls):
|
def modifier_map(cls):
|
||||||
return relationship(
|
return relationship(
|
||||||
|
@ -104,7 +106,36 @@ class ModifierMixin:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_modifier(self, modifier):
|
def remove_modifier(self, modifier):
|
||||||
if modifier.id not in [mod.modifier_id for mod in self.modifier_map]:
|
if modifier not in self.modifiers[modifier.target]:
|
||||||
return False
|
return False
|
||||||
self.modifier_map = [mapping for mapping in self.modifier_map if mapping.modifier != modifier]
|
self.modifier_map = [mapping for mapping in self.modifier_map if mapping.modifier != modifier]
|
||||||
return True
|
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__}."
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user