from ttfrog.db.schema.constants import DamageType, Defenses from ttfrog.db.schema.item import Armor, Item, ItemProperty, Rarity, RechargeTime, Shield, Weapon from ttfrog.db.schema.modifiers import Modifier def test_weapons(db): with db.transaction(): longbow = Weapon( name="longbow", damage_die="1d6", damage_type=DamageType.piercing, martial=True, two_handed=True, ammunition=True, targets=1, attack_range=150, attack_range_long=600, ) dagger = Weapon( name="Dagger", damage_die="1d4", damage_type=DamageType.slashing, melee=True, finesse=True, light=True, thrown=True, attack_range=20, attack_range_long=60, ) db.add_or_update([longbow, dagger]) assert longbow.martial assert longbow.ranged assert not longbow.melee assert not dagger.martial assert dagger.ranged assert dagger.melee def test_charges(db, carl): with db.transaction(): for_the_lulz = ItemProperty( name="For the Lulz", description=""" On a hit against a creature with a mouth, spend one charge to force the target to roll a DC 13 Wisdom saving throw. On a failure, the target is forced to grin for one minute. While grinning, the target cannot speak. The target can repeat the saving throw at the start of their turn." """, charge_cost=2, ) # from sqlalchemy.orm import relationship # help(relationship) dagger_of_lulz = Weapon( name="Dagger of Lulz", description="This magical dagger has 6 charges. It regains 1d6 charges after a short rest.", damage_die="1d4", damage_type=DamageType.slashing, melee=True, finesse=True, light=True, thrown=True, attack_range=20, attack_range_long=60, magical=True, charges=6, recharge_time=RechargeTime.SHORT_REST, recharge_amount="1d6", rarity=Rarity["Very Rare"], requires_attunement=True, properties=[for_the_lulz], ) db.add_or_update([carl, dagger_of_lulz]) assert for_the_lulz in dagger_of_lulz.properties assert carl.equipment.add(dagger_of_lulz) db.add_or_update(carl) carls_dagger = carl.equipment.get(dagger_of_lulz) 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 assert carls_dagger.use(for_the_lulz) assert len(carls_dagger.charges_available) == 4 # use the remaining charges assert carls_dagger.use(for_the_lulz) assert carls_dagger.use(for_the_lulz) # all out of charges assert len(carls_dagger.charges_available) == 0 assert not carls_dagger.use(for_the_lulz) def test_nocharges(db, carl): smiles = ItemProperty(name="Smile!", description="The target grins for one minute.", charge_cost=None) wand_of_unlimited_smiles = Item(name="Wand of Unlimited Smiles", description="description", properties=[smiles]) db.add_or_update(wand_of_unlimited_smiles) carl.equipment.add(wand_of_unlimited_smiles) db.add_or_update(carl) # no charges means you can use it at will assert carl.equipment.get(wand_of_unlimited_smiles).use(smiles) assert carl.equipment.get(wand_of_unlimited_smiles).use(smiles) assert carl.equipment.get(wand_of_unlimited_smiles).use(smiles) def test_attunement(db, carl): with db.transaction(): helm = Armor( name="Iron Helm", rarity=Rarity.Common, ) helm.add_modifier(Modifier("+1 AC (helmet)", target="armor_class", relative_value=1, stacks=True)) shield = Shield( name="Shield of Missile Attraction", description=""" While holding this shield, you have resistance to damage from ranged weapon attacks. **Curse.** This shield is cursed. Attuning to it curses you until you are targeted by the remove curse spell or similar magic. Removing the shield fails to end the curse on you. Whenever a ranged weapon attack is made against a target within 10 feet of you, the curse causes you to become the target instead. """, rarity=Rarity.Rare, requires_attunement=True, ) plus_two_ac = Modifier("+2 AC (Shield", target="armor_class", relative_value=2, stacks=True) ranged_resistance = Modifier( "Resistance to Damage From Ranged Weapon Attacks (Shield of Missle Attraction)", target=DamageType.ranged_weapon_attacks, new_value=Defenses.resistant, requires_attunement=True, ) shield.add_modifier(plus_two_ac) shield.add_modifier(ranged_resistance) db.add_or_update([carl, helm, shield]) assert carl.equipment.add(shield) assert carl.equipment.add(helm) db.add_or_update(carl) carls_shield = carl.equipment.get(shield) assert carl.armor_class == 10 assert len(carl.attuned_items) == 0 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 assert carls_shield.attune() assert not 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.equipment.get(helm).equip() assert carl.armor_class == 13 assert carls_shield.unattune() assert not carls_shield.unattune() assert carl.armor_class == 13 assert ranged_resistance not in carl.modifiers[DamageType.ranged_weapon_attacks] assert carls_shield.unequip() assert carl.armor_class == 11 # can only attune 3 items assert carl.equipment.add(shield) assert carl.equipment.add(shield) assert carl.equipment.add(shield) db.add_or_update(carl) assert carl.equipment.get_all(shield)[0].attune() assert carl.equipment.get_all(shield)[1].attune() assert carl.equipment.get_all(shield)[2].attune() assert len(carl.attuned_items) == 3 assert not carl.equipment.get_all(shield)[3].attune()