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
def modifiers(self):
unified = {}
unified.update(**self.ancestry.modifiers)
for trait in self.traits:
unified.update(**trait.modifiers)
for condition in self.conditions:
unified.update(**condition.modifiers)
unified.update(**super().modifiers)
unified = defaultdict(list)
def merge_modifiers(object_list):
for obj in object_list:
for target, mods in obj.modifiers.items():
unified[target] += mods
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
@property
@ -384,6 +396,30 @@ class Character(BaseObject, SlugMixin, ModifierMixin):
def equipment(self):
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):
if mapping.equipped:
return False

View File

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

View File

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

View File

@ -1,8 +1,10 @@
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.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__ = [
"Item",
@ -10,20 +12,48 @@ __all__ = [
]
class ItemType(EnumField):
ITEM = "ITEM"
SPELL = "SPELL"
SCROLL = "SCROLL"
ITEM_TYPES = [
"ITEM",
"SPELL",
"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"
__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)
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):
@ -33,3 +63,32 @@ class Spell(Item):
level: Mapped[int] = mapped_column(nullable=False, info={"min": 0, "max": 9}, default=0)
concentration: Mapped[bool] = mapped_column(default=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)
target: Mapped[str] = mapped_column(nullable=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)
multiply_value: Mapped[float] = 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.
"""
if attr_name.startswith('_'):
return None
col = getattr(self.__table__.columns, f"_{attr_name}", None)
if col is None:
return None
@ -279,7 +282,7 @@ class ModifierMixin:
self._get_modifiable_base(col.info.get("modifiable_base", col.name)),
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: