from ttfrog.db.schema import prototypes from ttfrog.db.schema.inventory import InventoryType def test_spell_inventory(db, carl): with db.transaction(): fireball = prototypes.BaseSpell(name="Fireball", level=3, concentration=False) db.add_or_update([fireball, carl]) assert fireball not in carl.spells assert carl.spells.learn(fireball) db.add_or_update(carl) assert fireball in carl.spells assert fireball in carl.spells.known assert fireball in carl.spells.available assert fireball not in carl.spells.prepared prestidigitation = prototypes.BaseSpell(name="Prestidigitation", level=0, concentration=False) wish = prototypes.BaseSpell(name="Wish", level=9, concentration=False) spellbook = prototypes.BaseItem(name="Spell Book", inventory_type=InventoryType.SPELL) db.add_or_update([wish, spellbook]) assert wish not in carl.spells grimoire = carl.equipment.add(spellbook) db.add_or_update(carl) grimoire.inventory.add(wish) grimoire.inventory.add(prestidigitation) db.add_or_update(carl) assert wish in carl.spells.available assert wish not in carl.spells.known assert prestidigitation in carl.spells.available assert prestidigitation not in carl.spells.known assert carl.spells.get(wish) assert carl.spells.get(prestidigitation) def test_equipment_inventory(db, carl): with db.transaction(): # trigger the creation of inventory mappings db.add_or_update(carl) # create some items ten_foot_pole = prototypes.BaseItem(name="10ft. Pole", item_type=prototypes.ItemType.ITEM, consumable=False) db.add_or_update(ten_foot_pole) # add the pole to carl's equipment, and the spell to his spell list. assert carl.equipment.add(ten_foot_pole) # can't mix and match inventory item types assert not carl.spells.learn(ten_foot_pole) # add two more 10 foot poles. You can never have too many. assert carl.equipment.add(ten_foot_pole) assert carl.equipment.add(ten_foot_pole) db.add_or_update(carl) all_carls_poles = carl.equipment.get_all(ten_foot_pole) assert len(all_carls_poles) == 3 # check the "contains" logic assert ten_foot_pole in carl.equipment assert ten_foot_pole not in carl.spells pole_one = all_carls_poles[0] # equip one pole assert pole_one.equip() # can't equip it twice assert not pole_one.equip() # not consumable or attunable assert not pole_one.consume() assert not pole_one.attune() assert pole_one.unequip() # can't unequip the unequipped ones assert not all_carls_poles[1].unequip() assert not all_carls_poles[2].unequip() # drop one pole assert carl.equipment.remove(pole_one) assert ten_foot_pole in carl.equipment return # drop the remaining poles assert carl.equipment.remove(all_carls_poles[1]) assert carl.equipment.remove(all_carls_poles[2]) assert ten_foot_pole not in carl.equipment # can't drop what you don't have assert not carl.equipment.remove(pole_one) def test_inventory_bundles(db, carl): with db.transaction(): arrows = prototypes.BaseItem(name="Arrows", item_type=prototypes.ItemType.ITEM, consumable=True, count=20) db.add_or_update([carl, arrows]) quiver = carl.equipment.add(arrows) db.add_or_update([carl, quiver]) assert quiver.container == carl.equipment # full quiver assert arrows in carl.equipment assert quiver.count == 20 # use one assert quiver.consume(1) == 19 assert quiver.count == 19 # cannot use more than you have assert not quiver.consume(20) # cannot use a negative amount assert not quiver.consume(-1) # consume all remaining arrows assert quiver.consume(19) == 0 assert arrows not in carl.equipment def test_spell_slots(db, carl, wizard): with db.transaction(): prestidigitation = prototypes.BaseSpell(name="Prestidigitation", level=0, concentration=False) fireball = prototypes.BaseSpell(name="Fireball", level=3, concentration=False) db.add_or_update([carl, prestidigitation, fireball]) carl.spells.learn(prestidigitation) carl.spells.learn(fireball) db.add_or_update(carl) # verify carl has the spell slots granted by wizard at 1st level assert len(carl.spell_slots) == 2 assert carl.spell_slots[0].spell_level == 1 assert carl.spell_slots[1].spell_level == 1 # carl knows the spells but hasn't prepared them assert prestidigitation in carl.spells assert fireball in carl.spells assert prestidigitation not in carl.spells.prepared assert fireball not in carl.spells.prepared # prepare the cantrip assert carl.spells.prepare(prestidigitation) assert carl.spells.cast(prestidigitation) # can't prepare a 3rd level spell if you don't have 3rd level slots assert carl.spellcaster_level == 1 assert not carl.spells.prepare(fireball) # make carl a 5th level wizard so he gets a 3rd level spell slot carl.level_up(wizard, num_levels=4) assert carl.level == 5 assert carl.spellcaster_level == 3 # cast fireball until he's out of 3rd level slots assert not carl.spells.cast(fireball) assert carl.spells.prepare(fireball) assert carl.spells.cast(fireball) assert carl.spells.cast(fireball) assert not carl.spells.cast(fireball) # level up to 7th level, gaining 1 4th level slot and 1 more 3rd level slot carl.add_class(wizard) carl.level_up(wizard, num_levels=2) assert carl.spellcaster_level == 4 assert len(carl.spell_slots_available[4]) == 1 assert len(carl.spell_slots_available[3]) == 1 # cast at 4th level assert carl.spells.cast(fireball, level=4) assert not carl.spells.cast(fireball, level=4) # use the last 3rd level slot assert carl.spells.cast(fireball) assert not carl.spells.cast(fireball) # unprepare it assert carl.spells.unprepare(fireball) assert not carl.spells.unprepare(fireball) def test_containers(db, carl): with db.transaction(): ten_foot_pole = prototypes.BaseItem(name="10ft. Pole") coil_of_rope = prototypes.BaseItem(name="50 ft. of Rope", consumable=True, count=50) bag_of_holding = prototypes.BaseItem(name="Bag of Holding", inventory_type=InventoryType.EQUIPMENT) db.add_or_update([ten_foot_pole, coil_of_rope, bag_of_holding]) pole = carl.equipment.add(ten_foot_pole) rope = carl.equipment.add(coil_of_rope) bag = carl.equipment.add(bag_of_holding) db.add_or_update(carl) # verify the bag of holding's inventory is created automatically. assert bag.inventory_type is not None assert bag.inventory is not None # the existing instances are found using the get() method assert carl.equipment.get(bag_of_holding) == bag assert carl.equipment.get(ten_foot_pole) == pole assert carl.equipment.get(coil_of_rope) == rope # backreferences are populated correctly assert pole.container == carl.equipment # add some items to the bag of holding assert pole.move_to(bag) assert rope.move_to(bag) assert pole.container.id == bag.inventory.id assert pole.container == bag.inventory assert pole in bag.inventory assert pole in bag assert pole not in carl.equipment.contents assert pole.container == bag.inventory pole_from_bag = bag.inventory.get(ten_foot_pole) rope_from_bag = bag.inventory.get(coil_of_rope) assert pole_from_bag.prototype == ten_foot_pole assert pole_from_bag in bag bag_inventory_size = 2 # one pole, one rope equipment_size = 1 # one bag # one bag, one pole, one rope total_inventory_size = bag_inventory_size + equipment_size assert len(list(bag.inventory.contents)) == bag_inventory_size assert len(list(carl.equipment.contents)) == equipment_size assert len(list(carl.equipment.all_contents)) == total_inventory_size # nested containers! assert pole_from_bag in carl.equipment assert rope_from_bag in carl.equipment # test equality of mappings carls_bag = carl.equipment.get(bag_of_holding) carls_pole = carl.equipment.get(ten_foot_pole) carls_rope = carl.equipment.get(coil_of_rope) assert carls_pole == pole_from_bag assert carls_rope == rope_from_bag # use some rope assert carls_rope.consume(10) assert carls_rope.count == 40 # move the rope out of the bag of holding, but not the pole assert carls_rope in carls_bag assert carls_rope.move_to(carl.equipment) assert carls_rope not in carls_bag assert carls_pole in carls_bag # get the db record anew, in case the in-memory representation isn't # what's recorded in the database. Then make sure we didn't break # anything by asserting we still only have 40ft of rope. carl = db.Character.filter_by(name="carl").one() assert carls_rope in carl.equipment assert carls_rope not in carl.equipment.get(bag_of_holding) assert carls_rope.count == 40 # old references are still valid assert rope_from_bag == carls_rope # use the rest of the rope assert carls_rope.consume(40) == 0 assert rope_from_bag not in carl.equipment assert rope_from_bag not in carl.equipment.get(bag_of_holding)