122 lines
4.7 KiB
Python
122 lines
4.7 KiB
Python
import itertools
|
|
from collections import defaultdict
|
|
|
|
from sqlalchemy import ForeignKey, UniqueConstraint
|
|
from sqlalchemy.ext.associationproxy import association_proxy
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from ttfrog.db.base import BaseObject
|
|
from ttfrog.db.schema.property import Skill
|
|
|
|
__all__ = [
|
|
"ClassAttributeMap",
|
|
"ClassAttribute",
|
|
"ClassAttributeOption",
|
|
"CharacterClass",
|
|
"Skill",
|
|
"ClassSkillMap",
|
|
]
|
|
|
|
|
|
def skill_creator(fields):
|
|
if isinstance(fields, ClassSkillMap):
|
|
return fields
|
|
return ClassSkillMap(**fields)
|
|
|
|
|
|
class ClassSkillMap(BaseObject):
|
|
__tablename__ = "class_skill_map"
|
|
__table_args__ = (UniqueConstraint("skill_id", "character_class_id"),)
|
|
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
skill_id: Mapped[int] = mapped_column(ForeignKey("skill.id"))
|
|
character_class_id: Mapped[int] = mapped_column(ForeignKey("character_class.id"))
|
|
proficient: Mapped[bool] = mapped_column(default=True)
|
|
expert: Mapped[bool] = mapped_column(default=False)
|
|
skill = relationship("Skill", lazy="immediate")
|
|
|
|
|
|
class ClassAttributeMap(BaseObject):
|
|
__tablename__ = "class_attribute_map"
|
|
class_attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), primary_key=True)
|
|
character_class_id: Mapped[int] = mapped_column(ForeignKey("character_class.id"), primary_key=True)
|
|
level: Mapped[int] = mapped_column(nullable=False, info={"min": 1, "max": 20}, default=1)
|
|
attribute = relationship("ClassAttribute", uselist=False, viewonly=True, lazy="immediate")
|
|
|
|
|
|
class ClassAttribute(BaseObject):
|
|
__tablename__ = "class_attribute"
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(nullable=False)
|
|
options = relationship("ClassAttributeOption", cascade="all,delete,delete-orphan", lazy="immediate")
|
|
|
|
def add_option(self, **kwargs):
|
|
option = ClassAttributeOption(attribute_id=self.id, **kwargs)
|
|
if not self.options or option not in self.options:
|
|
option.attribute_id = self.id
|
|
if not self.options:
|
|
self.options = [option]
|
|
else:
|
|
self.options.append(option)
|
|
return True
|
|
return False
|
|
|
|
def __repr__(self):
|
|
return f"{self.id}: {self.name}"
|
|
|
|
|
|
class ClassAttributeOption(BaseObject):
|
|
__tablename__ = "class_attribute_option"
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(nullable=False)
|
|
attribute_id: Mapped[int] = mapped_column(ForeignKey("class_attribute.id"), nullable=True)
|
|
|
|
|
|
class CharacterClass(BaseObject):
|
|
__tablename__ = "character_class"
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(index=True, unique=True)
|
|
hit_dice: Mapped[str] = mapped_column(default="1d6")
|
|
hit_dice_stat: Mapped[str] = mapped_column(default="")
|
|
starting_skills: int = mapped_column(nullable=False, default=0)
|
|
|
|
attributes = relationship("ClassAttributeMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
|
|
|
_skills = relationship("ClassSkillMap", cascade="all,delete,delete-orphan", lazy="immediate")
|
|
skills = association_proxy("_skills", "skill", creator=skill_creator)
|
|
|
|
def add_skill(self, skill, expert=False):
|
|
if not self.skills or skill not in self.skills:
|
|
if not self.id:
|
|
raise Exception(f"Cannot add a skill before the class has been persisted.")
|
|
mapping = ClassSkillMap(character_class_id=self.id, skill_id=skill.id, proficient=True, expert=expert)
|
|
self.skills.append(mapping)
|
|
return True
|
|
return False
|
|
|
|
def add_attribute(self, attribute, level=1):
|
|
if not self.attributes or attribute not in self.attributes:
|
|
mapping = ClassAttributeMap(character_class_id=self.id, class_attribute_id=attribute.id, level=level)
|
|
if not self.attributes:
|
|
self.attributes = [mapping]
|
|
else:
|
|
self.attributes.append(mapping)
|
|
return True
|
|
return False
|
|
|
|
@property
|
|
def attributes_by_level(self):
|
|
by_level = defaultdict(list)
|
|
for mapping in self.attributes:
|
|
by_level[mapping.level].append(mapping.attribute)
|
|
return by_level
|
|
|
|
def attribute(self, name: str):
|
|
for mapping in self.attributes:
|
|
if mapping.attribute.name.lower() == name.lower():
|
|
return mapping.attribute
|
|
return None
|
|
|
|
def attributes_at_level(self, level: int):
|
|
return list(itertools.chain(*[attrs for lvl, attrs in self.attributes_by_level.items() if lvl <= level]))
|