adding weapons and attunement

This commit is contained in:
evilchili 2024-08-03 17:46:12 -07:00
parent 708d6fe9e9
commit b7732f1581
5 changed files with 120 additions and 28 deletions

View File

@ -316,13 +316,25 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
@property @property
def modifiers(self): def modifiers(self):
unified = {} unified = defaultdict(list)
unified.update(**self.ancestry.modifiers)
for trait in self.traits: def merge_modifiers(object_list):
unified.update(**trait.modifiers) for obj in object_list:
for condition in self.conditions: for target, mods in obj.modifiers.items():
unified.update(**condition.modifiers) unified[target] += mods
unified.update(**super().modifiers)
merge_modifiers([self.ancestry])
merge_modifiers(self.traits)
merge_modifiers(self.conditions)
for mapping in self.equipped_items:
for (target, mods) in mapping.item.modifiers.items():
for mod in mods:
if mod.requires_attunement and not mapping.attuned:
continue
unified[target].append(mod)
merge_modifiers([super()])
return unified return unified
@property @property
@ -384,6 +396,30 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
def equipment(self): def equipment(self):
return [inv for inv in self._inventories if inv.inventory_type == InventoryType.EQUIPMENT][0] return [inv for inv in self._inventories if inv.inventory_type == InventoryType.EQUIPMENT][0]
@property
def equipped_items(self):
return [item for item in self.equipment if item.equipped]
@property
def attuned_items(self):
return [item for item in self.equipment if item.attuned]
def attune(self, mapping):
if mapping.attuned:
return False
if not mapping.item.requires_attunement:
return False
if len(self.attuned_items) >= 3:
return False
mapping.attuned = True
return True
def unattune(self, mapping):
if not mapping.attuned:
return False
mapping.attuned = False
return True
def equip(self, mapping): def equip(self, mapping):
if mapping.equipped: if mapping.equipped:
return False return False

View File

@ -43,6 +43,8 @@ class DamageType(StrEnum):
adamantium_piercing = auto() adamantium_piercing = auto()
adamantium_slashing = auto() adamantium_slashing = auto()
adamantium_bludgeoning = auto() adamantium_bludgeoning = auto()
ranged_weapon_attacks = auto()
melee_weapon_attacks = auto()
class Defenses(StrEnum): class Defenses(StrEnum):

View File

@ -13,6 +13,9 @@ class InventoryType(EnumField):
inventory_type_map = { inventory_type_map = {
InventoryType.EQUIPMENT: [ InventoryType.EQUIPMENT: [
ItemType.WEAPON,
ItemType.ARMOR,
ItemType.SHIELD,
ItemType.ITEM, ItemType.ITEM,
ItemType.SCROLL, ItemType.SCROLL,
], ],
@ -54,18 +57,6 @@ class Inventory(BaseObject):
self.inventory_map.remove(mapping.id) self.inventory_map.remove(mapping.id)
return True return True
def equip(self, mapping):
if mapping.equipped:
return False
mapping.equipped = True
return True
def unequip(self, mapping):
if not mapping.equipped:
return False
mapping.equipped = False
return True
def __contains__(self, obj): def __contains__(self, obj):
for mapping in self._inventory_map: for mapping in self._inventory_map:
if mapping.item == obj: if mapping.item == obj:
@ -85,6 +76,7 @@ class InventoryMap(BaseObject):
inventory: Mapped["Inventory"] = relationship(uselist=False, viewonly=True, init=False) inventory: Mapped["Inventory"] = relationship(uselist=False, viewonly=True, init=False)
equipped: Mapped[bool] = mapped_column(default=False) equipped: Mapped[bool] = mapped_column(default=False)
attuned: Mapped[bool] = mapped_column(default=False)
count: Mapped[int] = mapped_column(nullable=False, default=1) count: Mapped[int] = mapped_column(nullable=False, default=1)
always_prepared: Mapped[bool] = mapped_column(default=False) always_prepared: Mapped[bool] = mapped_column(default=False)

View File

@ -1,8 +1,10 @@
from sqlalchemy import ForeignKey, String from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column, relationship
from ttfrog.db.base import BaseObject, EnumField from ttfrog.db.base import BaseObject, EnumField
from ttfrog.db.schema.modifiers import ConditionMixin from ttfrog.db.schema.constants import DamageType
from ttfrog.db.schema.modifiers import ModifierMixin
from ttfrog.db.schema.classes import CharacterClass
__all__ = [ __all__ = [
"Item", "Item",
@ -10,20 +12,48 @@ __all__ = [
] ]
class ItemType(EnumField): ITEM_TYPES = [
ITEM = "ITEM" "ITEM",
SPELL = "SPELL" "SPELL",
SCROLL = "SCROLL" "SCROLL",
"WEAPON",
"ARMOR",
"SHIELD",
]
RARITY = [
"Common",
"Uncommon",
"Rare",
"Very Rare",
"Legendary",
"Artifact"
]
class Item(BaseObject, ConditionMixin): ItemType = EnumField("ItemType", ((k, k) for k in ITEM_TYPES))
Rarity = EnumField("Rarity", ((k, k) for k in RARITY))
class Item(BaseObject, ModifierMixin):
__tablename__ = "item" __tablename__ = "item"
__mapper_args__ = {"polymorphic_identity": ItemType.ITEM, "polymorphic_on": "item_type"} __mapper_args__ = {"polymorphic_identity": ItemType.ITEM, "polymorphic_on": "item_type"}
id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True) id: Mapped[int] = mapped_column(init=False, primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(collation="NOCASE"), nullable=False, unique=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) consumable: Mapped[bool] = mapped_column(default=False)
count: Mapped[int] = mapped_column(nullable=False, default=1) count: Mapped[int] = mapped_column(nullable=False, default=1)
item_type: Mapped[ItemType] = mapped_column(default=ItemType.ITEM, nullable=False)
_class_restrictions: Mapped[int] = mapped_column(ForeignKey("character_class.id"), nullable=True, default=None)
class_restrictions: Mapped["CharacterClass"] = relationship(init=False)
# _spells: Mapped[int] = mapped_column(ForeignKey("spell.id"), nullable=True, default=None)
# spells: Mapped["Spell"] = relationship(init=False)
class Spell(Item): class Spell(Item):
@ -33,3 +63,32 @@ class Spell(Item):
level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0) level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0)
concentration: Mapped[bool] = mapped_column(default=False) concentration: Mapped[bool] = mapped_column(default=False)
item_type: Mapped[ItemType] = mapped_column(default=ItemType.SPELL, init=False) item_type: Mapped[ItemType] = mapped_column(default=ItemType.SPELL, init=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)
damage_die: Mapped[str] = mapped_column(nullable=False, default="1d6")
damage_type: Mapped[DamageType] = mapped_column(nullable=False, default=DamageType.slashing)
item_type: Mapped[ItemType] = mapped_column(default=ItemType.WEAPON)
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)
@property
def ranged(self):
return self.attack_range > 0

View File

@ -66,6 +66,7 @@ class Modifier(BaseObject):
name: Mapped[str] = mapped_column(nullable=False) name: Mapped[str] = mapped_column(nullable=False)
target: Mapped[str] = mapped_column(nullable=False) target: Mapped[str] = mapped_column(nullable=False)
stacks: Mapped[bool] = mapped_column(nullable=False, default=False) stacks: Mapped[bool] = mapped_column(nullable=False, default=False)
requires_attunement: Mapped[bool] = mapped_column(nullable=False, default=False)
absolute_value: Mapped[int] = mapped_column(nullable=True, default=None) absolute_value: Mapped[int] = mapped_column(nullable=True, default=None)
multiply_value: Mapped[float] = mapped_column(nullable=True, default=None) multiply_value: Mapped[float] = mapped_column(nullable=True, default=None)
multiply_attribute: Mapped[str] = mapped_column(nullable=True, default=None) multiply_attribute: Mapped[str] = mapped_column(nullable=True, default=None)
@ -174,6 +175,8 @@ class ModifierMixin:
Returns the matching column if it was found, or None. Returns the matching column if it was found, or None.
""" """
if attr_name.startswith('_'):
return None
col = getattr(self.__table__.columns, f"_{attr_name}", None) col = getattr(self.__table__.columns, f"_{attr_name}", None)
if col is None: if col is None:
return None return None
@ -279,7 +282,7 @@ class ModifierMixin:
self._get_modifiable_base(col.info.get("modifiable_base", col.name)), self._get_modifiable_base(col.info.get("modifiable_base", col.name)),
modifiable_class=col.info.get("modifiable_class", None), modifiable_class=col.info.get("modifiable_class", None),
) )
raise AttributeError(f"No such attribute on {self.__class__.__name__} object: {attr_name}.") raise AttributeError(f"{self.__class__.__name__} object does not have the attribute '{attr_name}'")
class ConditionMixin: class ConditionMixin: