From 68a8f4920bfa02033314756a7284dbf4f7349e85 Mon Sep 17 00:00:00 2001 From: evilchili Date: Thu, 29 Aug 2024 16:41:15 -0700 Subject: [PATCH] move inventory verbs to inventory map from character --- src/ttfrog/db/schema/character.py | 42 --------------------- src/ttfrog/db/schema/inventory.py | 62 +++++++++++++++++++++++++++++++ test/test_inventories.py | 41 ++++++++++---------- test/test_items.py | 14 +++---- 4 files changed, 89 insertions(+), 70 deletions(-) diff --git a/src/ttfrog/db/schema/character.py b/src/ttfrog/db/schema/character.py index 2afe3c9..459be5b 100644 --- a/src/ttfrog/db/schema/character.py +++ b/src/ttfrog/db/schema/character.py @@ -10,7 +10,6 @@ from ttfrog.db.base import BaseObject, SlugMixin from ttfrog.db.schema.classes import CharacterClass, ClassFeature from ttfrog.db.schema.constants import DamageType, Defenses from ttfrog.db.schema.inventory import Inventory, InventoryMap, InventoryType -from ttfrog.db.schema.item import ItemType from ttfrog.db.schema.modifiers import Modifier, ModifierMixin, Stat from ttfrog.db.schema.skill import Skill @@ -422,47 +421,6 @@ class Character(BaseObject, SlugMixin, ModifierMixin): mapping.attuned = False 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 prepare(self, mapping): - if mapping.item.item_type != ItemType.SPELL: - return False - if mapping.item.level > 0 and not self.spell_slots_by_level[mapping.item.level]: - return False - return self.equip(mapping) - - def unprepare(self, mapping): - if mapping.item.item_type != ItemType.SPELL: - return False - return self.unequip(mapping) - - def cast(self, mapping: InventoryMap, level=0): - if not mapping.prepared: - return False - if not level: - level = mapping.item.level - - # cantrips - if level == 0: - return True - - # expend the spell slot - avail = self.spell_slots_available[level] - if not avail: - return False - avail[0].expended = True - return True - def level_in_class(self, charclass): mapping = [mapping for mapping in self.class_map if mapping.character_class_id == charclass.id] if not mapping: diff --git a/src/ttfrog/db/schema/inventory.py b/src/ttfrog/db/schema/inventory.py index f50dbdb..f359890 100644 --- a/src/ttfrog/db/schema/inventory.py +++ b/src/ttfrog/db/schema/inventory.py @@ -41,6 +41,8 @@ class Inventory(BaseObject): uselist=True, cascade="all,delete,delete-orphan", lazy="immediate", default_factory=lambda: [] ) + character = relationship("Character", init=False, viewonly=True, lazy="immediate") + def get(self, item): return self.get_all(item)[0] @@ -101,6 +103,30 @@ class InventoryMap(BaseObject): if self.item.item_type == ItemType.SPELL: return self.equipped or self.always_prepared + def equip(self): + if self.equipped: + return False + self.equipped = True + return True + + def unequip(self): + if not self.equipped: + return False + self.equipped = False + return True + + def prepare(self): + if self.item.item_type != ItemType.SPELL: + return False + if self.item.level > 0 and not self.inventory.character.spell_slots_by_level[self.item.level]: + return False + return self.equip() + + def unprepare(self): + if self.item.item_type != ItemType.SPELL: + return False + return self.unequip() + def use(self, item_property: ItemProperty, charges=None): if item_property.charge_cost is None: return True @@ -126,6 +152,42 @@ class InventoryMap(BaseObject): return 0 return self.count + def cast(self, level=0): + if self.item.item_type != ItemType.SPELL: + return False + + if not self.prepared: + return False + if not level: + level = self.item.level + + # cantrips + if level == 0: + return True + + # expend the spell slot + avail = self.inventory.character.spell_slots_available[level] + if not avail: + return False + avail[0].expended = True + return True + + def attune(self): + if self.attuned: + return False + if not self.item.requires_attunement: + return False + if len(self.inventory.character.attuned_items) >= 3: + return False + self.attuned = True + return True + + def unattune(self): + if not self.attuned: + return False + self.attuned = False + return True + class Charge(BaseObject): __tablename__ = "charge" diff --git a/test/test_inventories.py b/test/test_inventories.py index aadf76c..6fd1134 100644 --- a/test/test_inventories.py +++ b/test/test_inventories.py @@ -33,21 +33,23 @@ def test_equipment_inventory(db, carl): assert fireball in carl.spells assert fireball not in carl.equipment + pole_one = all_carls_poles[0] + # equip one pole - assert carl.equip(all_carls_poles[0]) + assert pole_one.equip() # can't equip it twice - assert not carl.equip(all_carls_poles[0]) + assert not pole_one.equip() # unequip it - assert carl.unequip(all_carls_poles[0]) + assert pole_one.unequip() # can't unequip the unequipped ones - assert not carl.unequip(all_carls_poles[1]) - assert not carl.unequip(all_carls_poles[2]) + assert not all_carls_poles[1].unequip() + assert not all_carls_poles[2].unequip() # drop one pole - assert carl.equipment.remove(all_carls_poles[0]) + assert carl.equipment.remove(pole_one) assert ten_foot_pole in carl.equipment # drop the remaining poles @@ -56,7 +58,7 @@ def test_equipment_inventory(db, carl): assert ten_foot_pole not in carl.equipment # can't drop what you don't have - assert not carl.equipment.remove(all_carls_poles[0]) + assert not carl.equipment.remove(pole_one) def test_inventory_bundles(db, carl): @@ -108,15 +110,12 @@ def test_spell_slots(db, carl, wizard): # prepare the cantrip carls_prestidigitation = carl.spells.get(prestidigitation) - assert carl.prepare(carls_prestidigitation) - assert carl.cast(carls_prestidigitation) - - # prepare() and cast() require a spell from the spell inventory - carls_fireball = carl.spells.get(fireball) + assert carls_prestidigitation.prepare() + assert carls_prestidigitation.cast() # can't prepare a 3rd level spell if you don't have 3rd level slots assert carl.spellcaster_level == 1 - assert not carl.prepare(carls_fireball) + assert not carl.spells.get(fireball).prepare() # make carl a 5th level wizard so he gets a 3rd level spell slot carl.level_up(wizard, num_levels=4) @@ -124,10 +123,10 @@ def test_spell_slots(db, carl, wizard): assert carl.spellcaster_level == 3 # cast fireball until he's out of 3rd level slots - assert carl.prepare(carls_fireball) - assert carl.cast(carls_fireball) - assert carl.cast(carls_fireball) - assert not carl.cast(carls_fireball) + assert carl.spells.get(fireball).prepare() + assert carl.spells.get(fireball).cast() + assert carl.spells.get(fireball).cast() + assert not carl.spells.get(fireball).cast() # level up to 7th level, gaining 1 4th level slot and 1 more 3rd level slot carl.add_class(wizard) @@ -137,9 +136,9 @@ def test_spell_slots(db, carl, wizard): assert len(carl.spell_slots_available[3]) == 1 # cast at 4th level - assert carl.cast(carls_fireball, 4) - assert not carl.cast(carls_fireball, 4) + assert carl.spells.get(fireball).cast(level=4) + assert not carl.spells.get(fireball).cast(level=4) # use the last 3rd level slot - assert carl.cast(carls_fireball) - assert not carl.cast(carls_fireball) + assert carl.spells.get(fireball).cast() + assert not carl.spells.get(fireball).cast() diff --git a/test/test_items.py b/test/test_items.py index 47a8e2b..4efcb16 100644 --- a/test/test_items.py +++ b/test/test_items.py @@ -82,8 +82,8 @@ def test_charges(db, carl): db.add_or_update(carl) carls_dagger = carl.equipment.get(dagger_of_lulz) - assert carl.equip(carls_dagger) - assert carl.attune(carls_dagger) + assert carls_dagger.equip() + assert carls_dagger.attune() assert len(carls_dagger.charges) == dagger_of_lulz.charges == 6 assert len(carls_dagger.charges_available) == dagger_of_lulz.charges == 6 @@ -132,24 +132,24 @@ def test_attunement(db, carl): assert carl.armor_class == 10 assert len(carl.attuned_items) == 0 - carl.equip(carls_shield) + carls_shield.equip() assert plus_two_ac in carl.modifiers["armor_class"] assert ranged_resistance not in carl.modifiers[DamageType.ranged_weapon_attacks] assert carl.armor_class == 12 assert carls_shield not in carl.attuned_items - carl.attune(carls_shield) + carls_shield.attune() assert carl.armor_class == 12 assert plus_two_ac in carl.modifiers["armor_class"] assert ranged_resistance in carl.modifiers[DamageType.ranged_weapon_attacks] assert carls_shield in carl.attuned_items - assert carl.equip(carl.equipment.get(helm)) + assert carl.equipment.get(helm).equip() assert carl.armor_class == 13 - assert carl.unattune(carls_shield) + assert carls_shield.unattune() assert carl.armor_class == 13 assert ranged_resistance not in carl.modifiers[DamageType.ranged_weapon_attacks] - assert carl.unequip(carls_shield) + assert carls_shield.unequip() assert carl.armor_class == 11