adding inventories

This commit is contained in:
evilchili 2024-07-28 13:55:19 -07:00
parent b68dda2b77
commit 3f45dbe9b9
6 changed files with 158 additions and 11 deletions

View File

@ -8,8 +8,10 @@ from ttfrog.db.base import BaseObject, SlugMixin
from ttfrog.db.schema.classes import CharacterClass, ClassFeature from ttfrog.db.schema.classes import CharacterClass, ClassFeature
from ttfrog.db.schema.constants import DamageType, Defenses from ttfrog.db.schema.constants import DamageType, Defenses
from ttfrog.db.schema.modifiers import Modifier, ModifierMixin, Stat from ttfrog.db.schema.modifiers import Modifier, ModifierMixin, Stat
from ttfrog.db.schema.inventory import Inventory, InventoryMap, InventoryType
from ttfrog.db.schema.skill import Skill from ttfrog.db.schema.skill import Skill
__all__ = [ __all__ = [
"Ancestry", "Ancestry",
"AncestryTrait", "AncestryTrait",
@ -33,6 +35,12 @@ def skill_creator(fields):
return CharacterSkillMap(**fields) return CharacterSkillMap(**fields)
def inventory_creator(fields):
if isinstance(fields, InventoryMap):
return fields
return InventoryMap(**fields)
def condition_creator(fields): def condition_creator(fields):
if isinstance(fields, CharacterConditionMap): if isinstance(fields, CharacterConditionMap):
return fields return fields
@ -219,6 +227,9 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
_reactions_per_turn: Mapped[int] = mapped_column( _reactions_per_turn: Mapped[int] = mapped_column(
nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True} nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}
) )
_attacks_per_action: Mapped[int] = mapped_column(
nullable=False, default=1, info={"min": 0, "max": 99, "modifiable": True}
)
vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0, "modifiable": True}) vision: Mapped[int] = mapped_column(default=None, nullable=True, info={"min": 0, "modifiable": True})
exhaustion: Mapped[int] = mapped_column(nullable=False, default=0, info={"min": 0, "max": 5}) exhaustion: Mapped[int] = mapped_column(nullable=False, default=0, info={"min": 0, "max": 5})
@ -235,11 +246,14 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
conditions = association_proxy("_conditions", "condition", creator=condition_creator) conditions = association_proxy("_conditions", "condition", creator=condition_creator)
character_class_feature_map = relationship("CharacterClassFeatureMap", cascade="all,delete,delete-orphan") character_class_feature_map = relationship("CharacterClassFeatureMap", cascade="all,delete,delete-orphan")
feature_list = association_proxy("character_class_feature_map", "id", creator=attr_map_creator) features = association_proxy("character_class_feature_map", "id", creator=attr_map_creator)
ancestry_id: Mapped[int] = mapped_column(ForeignKey("ancestry.id"), nullable=False, default="1") ancestry_id: Mapped[int] = mapped_column(ForeignKey("ancestry.id"), nullable=False, default="1")
ancestry: Mapped["Ancestry"] = relationship(uselist=False, default=None) ancestry: Mapped["Ancestry"] = relationship(uselist=False, default=None)
_inventories = relationship("Inventory", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate")
inventories = association_proxy("_inventories", "id", creator=inventory_creator)
_hit_dice = relationship("HitDie", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate") _hit_dice = relationship("HitDie", uselist=True, cascade="all,delete,delete-orphan", lazy="immediate")
@property @property
@ -323,6 +337,14 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
def class_features(self): def class_features(self):
return dict([(mapping.class_feature.name, mapping.option) for mapping in self.character_class_feature_map]) return dict([(mapping.class_feature.name, mapping.option) for mapping in self.character_class_feature_map])
@property
def equipment(self):
return [inv for inv in self._inventories if inv.inventory_type == InventoryType.EQUIPMENT][0]
@property
def spells(self):
return [inv for inv in self._inventories if inv.inventory_type == InventoryType.SPELL][0]
def level_in_class(self, charclass): def level_in_class(self, charclass):
mapping = [mapping for mapping in self.class_map if mapping.character_class_id == charclass.id] mapping = [mapping for mapping in self.class_map if mapping.character_class_id == charclass.id]
if not mapping: if not mapping:
@ -423,7 +445,7 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
return False return False
if feature not in mapping.character_class.features_at_level(mapping.level): if feature not in mapping.character_class.features_at_level(mapping.level):
return False return False
self.feature_list.append( self.features.append(
CharacterClassFeatureMap( CharacterClassFeatureMap(
character_id=self.id, character_id=self.id,
class_feature_id=feature.id, class_feature_id=feature.id,
@ -548,3 +570,9 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
Skill.name.in_(("strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma")) Skill.name.in_(("strength", "dexterity", "constitution", "intelligence", "wisdom", "charisma"))
): ):
self.add_skill(skill, proficient=False, expert=False) self.add_skill(skill, proficient=False, expert=False)
for inventory_type in InventoryType:
self._inventories.append(
Inventory(inventory_type=inventory_type, character_id=self.id)
)
session.add(self)

View File

@ -0,0 +1,57 @@
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, EnumField
from ttfrog.db.schema.item import Item, ItemType
class InventoryType(EnumField):
EQUIPMENT = "EQUIPMENT"
SPELL = "SPELL"
inventory_type_map = {
InventoryType.EQUIPMENT: [
ItemType.ITEM,
ItemType.SPELL,
],
InventoryType.SPELL: [
ItemType.SPELL
]
}
def inventory_map_creator(fields):
if isinstance(fields, InventoryMap):
return fields
return InventoryMap(**fields)
class Inventory(BaseObject):
__tablename__ = "inventory"
__table_args__ = (UniqueConstraint("character_id", "inventory_type"), )
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
character_id: Mapped[int] = mapped_column(ForeignKey("character.id"))
inventory_type: Mapped[InventoryType] = mapped_column(nullable=False)
_inventory_map = relationship("InventoryMap", lazy="immediate", uselist=True)
inventory_map = association_proxy("_inventory_map", "id", creator=inventory_map_creator)
def add(self, item):
if item.item_type not in inventory_type_map[self.inventory_type]:
return False
self.inventory_map.append(InventoryMap(inventory_id=self.id, item_id=item.id))
def __iter__(self):
yield from [mapping.item for mapping in self._inventory_map]
class InventoryMap(BaseObject):
__tablename__ = "inventory_map"
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
inventory_id: Mapped[int] = mapped_column(ForeignKey("inventory.id"))
item_id: Mapped[int] = mapped_column(ForeignKey("item.id"))
item: Mapped["Item"] = relationship(uselist=False, lazy="immediate", viewonly=True, init=False)
equipped: Mapped[bool] = mapped_column(default=False)
count: Mapped[int] = mapped_column(nullable=False, default=1)

View File

@ -0,0 +1,37 @@
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column
from ttfrog.db.base import BaseObject, EnumField
from ttfrog.db.schema.modifiers import ConditionMixin
__all__ = [
"Item",
"Spell",
]
class ItemType(EnumField):
ITEM = "ITEM"
SPELL = "SPELL"
class Item(BaseObject, ConditionMixin):
__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)
consumable: Mapped[bool] = mapped_column(default=False)
item_type: Mapped[ItemType] = mapped_column(default=ItemType.ITEM, nullable=False)
class Spell(Item):
__tablename__ = "spell"
__mapper_args__ = {
"polymorphic_identity": ItemType.SPELL
}
id: Mapped[int] = mapped_column(ForeignKey("item.id"), primary_key=True)
level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0)
concentration: Mapped[bool] = mapped_column(default=False)

View File

@ -282,15 +282,7 @@ class ModifierMixin:
raise AttributeError(f"No such attribute on {self.__class__.__name__} object: {attr_name}.") raise AttributeError(f"No such attribute on {self.__class__.__name__} object: {attr_name}.")
class Condition(BaseObject): class ConditionMixin:
__tablename__ = "condition"
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(default="")
_modifiers = relationship("Modifier", uselist=True, cascade="all,delete,delete-orphan")
_parent_condition_id: Mapped[int] = mapped_column(ForeignKey("condition.id"), nullable=True, default=None)
conditions = relationship("Condition", lazy="immediate", uselist=True)
@property @property
def modifiers(self): def modifiers(self):
@ -331,6 +323,17 @@ class Condition(BaseObject):
self.conditions = [c for c in self.conditions if c != condition] self.conditions = [c for c in self.conditions if c != condition]
return True return True
class Condition(BaseObject, ConditionMixin):
__tablename__ = "condition"
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(default="")
_modifiers = relationship("Modifier", uselist=True, cascade="all,delete,delete-orphan")
_parent_condition_id: Mapped[int] = mapped_column(ForeignKey("condition.id"), nullable=True, default=None)
conditions = relationship("Condition", lazy="immediate", uselist=True)
def __str___(self): def __str___(self):
return self.name return self.name

View File

@ -104,3 +104,9 @@ def bootstrap(db):
# persist all the records we've created # persist all the records we've created
db.add_or_update([foo, bar]) db.add_or_update([foo, bar])
@pytest.fixture
def carl(db, bootstrap):
tiefling = db.Ancestry.filter_by(name="tiefling").one()
carl = schema.Character(name="Carl", ancestry=tiefling)
return carl

16
test/test_inventories.py Normal file
View File

@ -0,0 +1,16 @@
from ttfrog.db.schema.item import Item, ItemType
def test_equipment_inventory(db, carl):
with db.transaction():
# trigger the creation of inventory mappings
db.add_or_update(carl)
# create an item
ten_foot_pole = Item(name="10ft. Pole", item_type=ItemType.ITEM, consumable=False)
db.add_or_update(ten_foot_pole)
# add the item to carl's inventory
carl.equipment.add(ten_foot_pole)
db.add_or_update(carl)
assert ten_foot_pole in carl.equipment