152 lines
5.7 KiB
Python
152 lines
5.7 KiB
Python
from typing import List
|
|
|
|
from sqlalchemy import ForeignKey, String
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from ttfrog.db.base import BaseObject, EnumField
|
|
from ttfrog.db.schema.classes import CharacterClass
|
|
from ttfrog.db.schema.constants import DamageType
|
|
from ttfrog.db.schema.modifiers import ModifierMixin
|
|
|
|
__all__ = [
|
|
"Item",
|
|
"Spell",
|
|
]
|
|
|
|
|
|
ITEM_TYPES = [
|
|
"ITEM",
|
|
"SPELL",
|
|
"SCROLL",
|
|
"WEAPON",
|
|
"ARMOR",
|
|
"SHIELD",
|
|
"CONTAINER",
|
|
]
|
|
|
|
RECHARGE_TIMES = [
|
|
"short rest",
|
|
"long rest",
|
|
"dawn",
|
|
]
|
|
|
|
COST_TYPES = ["Action", "Bonus Action", "Reaction"]
|
|
|
|
RARITY = ["Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Artifact"]
|
|
|
|
|
|
ItemType = EnumField("ItemType", ((k, k) for k in ITEM_TYPES))
|
|
Rarity = EnumField("Rarity", ((k, k) for k in RARITY))
|
|
RechargeTime = EnumField("RechargeTime", ((k.replace(" ", "_").upper(), k) for k in RECHARGE_TIMES))
|
|
Cost = EnumField("Cost", ((k, k) for k in COST_TYPES))
|
|
|
|
|
|
def item_property_creator(fields):
|
|
if isinstance(fields, list):
|
|
for f in fields:
|
|
yield f
|
|
elif isinstance(fields, ItemProperty):
|
|
return fields
|
|
return ItemProperty(**fields)
|
|
|
|
|
|
class Item(BaseObject, ModifierMixin):
|
|
__tablename__ = "item"
|
|
__mapper_args__ = {"polymorphic_identity": ItemType.ITEM, "polymorphic_on": "item_type"}
|
|
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(collation="NOCASE"), nullable=False, unique=True)
|
|
description: Mapped[str] = mapped_column(String, nullable=True, default=None)
|
|
|
|
item_type: Mapped[ItemType] = mapped_column(default=ItemType.ITEM, nullable=False)
|
|
rarity: Mapped[Rarity] = mapped_column(default=Rarity.Common, nullable=False)
|
|
requires_attunement: Mapped[bool] = mapped_column(nullable=False, default=False)
|
|
|
|
consumable: Mapped[bool] = mapped_column(default=False)
|
|
count: Mapped[int] = mapped_column(nullable=False, default=1)
|
|
|
|
charges: Mapped[int] = mapped_column(nullable=True, info={"min": 0}, default=None)
|
|
recharge_time: Mapped[RechargeTime] = mapped_column(default=RechargeTime.LONG_REST)
|
|
recharge_amount: Mapped[str] = mapped_column(String(collation="NOCASE"), default="1")
|
|
|
|
_class_restrictions: Mapped[int] = mapped_column(ForeignKey("character_class.id"), nullable=True, default=None)
|
|
class_restrictions: Mapped["CharacterClass"] = relationship(init=False)
|
|
|
|
properties: Mapped[List["ItemProperty"]] = relationship(
|
|
uselist=True, cascade="all,delete,delete-orphan", lazy="immediate", default_factory=lambda: []
|
|
)
|
|
|
|
# _spells: Mapped[int] = mapped_column(ForeignKey("spell.id"), nullable=True, default=None)
|
|
# spells: Mapped["Spell"] = relationship(init=False)
|
|
|
|
@property
|
|
def has_charges(self):
|
|
return self.charges is not None
|
|
|
|
|
|
class Spell(Item):
|
|
__tablename__ = "spell"
|
|
__mapper_args__ = {"polymorphic_identity": ItemType.SPELL}
|
|
id: Mapped[int] = mapped_column(ForeignKey("item.id"), primary_key=True, init=False)
|
|
item_type: Mapped[ItemType] = ItemType.SPELL
|
|
|
|
level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0)
|
|
concentration: Mapped[bool] = mapped_column(default=False)
|
|
|
|
|
|
class Weapon(Item):
|
|
__tablename__ = "weapon"
|
|
__mapper_args__ = {"polymorphic_identity": ItemType.WEAPON}
|
|
id: Mapped[int] = mapped_column(ForeignKey("item.id"), primary_key=True, init=False)
|
|
item_type: Mapped[ItemType] = ItemType.WEAPON
|
|
|
|
damage_die: Mapped[str] = mapped_column(nullable=False, default="1d6")
|
|
damage_type: Mapped[DamageType] = mapped_column(nullable=False, default=DamageType.slashing)
|
|
attack_range: Mapped[int] = mapped_column(nullable=False, info={"min": 0}, default=0)
|
|
attack_range_long: Mapped[int] = mapped_column(nullable=True, info={"min": 0}, default=None)
|
|
targets: Mapped[int] = mapped_column(nullable=False, info={"min": 1}, default=1)
|
|
martial: Mapped[bool] = mapped_column(default=False)
|
|
melee: Mapped[bool] = mapped_column(default=False)
|
|
ammunition: Mapped[bool] = mapped_column(default=False)
|
|
finesse: Mapped[bool] = mapped_column(default=False)
|
|
heavy: Mapped[bool] = mapped_column(default=False)
|
|
light: Mapped[bool] = mapped_column(default=False)
|
|
loading: Mapped[bool] = mapped_column(default=False)
|
|
reach: Mapped[bool] = mapped_column(default=False)
|
|
thrown: Mapped[bool] = mapped_column(default=False)
|
|
two_handed: Mapped[bool] = mapped_column(default=False)
|
|
versatile: Mapped[bool] = mapped_column(default=False)
|
|
silvered: Mapped[bool] = mapped_column(default=False)
|
|
adamantine: Mapped[bool] = mapped_column(default=False)
|
|
magical: Mapped[bool] = mapped_column(default=False)
|
|
|
|
@property
|
|
def ranged(self):
|
|
return self.attack_range > 0
|
|
|
|
|
|
class Shield(Item):
|
|
__tablename__ = "shield"
|
|
__mapper_args__ = {"polymorphic_identity": ItemType.SHIELD}
|
|
id: Mapped[int] = mapped_column(ForeignKey("item.id"), primary_key=True, init=False)
|
|
item_type: Mapped[ItemType] = ItemType.SHIELD
|
|
|
|
|
|
class Armor(Item):
|
|
__tablename__ = "armor"
|
|
__mapper_args__ = {"polymorphic_identity": ItemType.ARMOR}
|
|
id: Mapped[int] = mapped_column(ForeignKey("item.id"), primary_key=True, init=False)
|
|
item_type: Mapped[ItemType] = ItemType.ARMOR
|
|
|
|
|
|
class ItemProperty(BaseObject):
|
|
__tablename__ = "item_property"
|
|
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
|
|
name: Mapped[str] = mapped_column(String(collation="NOCASE"), nullable=False, unique=True)
|
|
description: Mapped[str] = mapped_column(String, nullable=True, default=None)
|
|
charge_cost: Mapped[int] = mapped_column(nullable=True, info={"min": 0}, default=None)
|
|
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"), default=0)
|
|
|
|
# action/reaction/bonus
|
|
# modifiers?
|