2024-08-20 09:30:23 -07:00
|
|
|
from ttfrog.db.schema.constants import DamageType, Defenses
|
2024-09-02 12:39:59 -07:00
|
|
|
from ttfrog.db.schema.item import Armor, Item, ItemProperty, Rarity, RechargeTime, Shield, Weapon
|
2024-08-20 09:30:23 -07:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2024-08-29 15:14:47 -07:00
|
|
|
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."
|
|
|
|
""",
|
2024-09-02 12:39:59 -07:00
|
|
|
charge_cost=2,
|
2024-08-29 15:14:47 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
# 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)
|
2024-08-29 16:41:15 -07:00
|
|
|
assert carls_dagger.equip()
|
|
|
|
assert carls_dagger.attune()
|
2024-08-29 15:14:47 -07:00
|
|
|
|
|
|
|
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)
|
2024-09-02 12:39:59 -07:00
|
|
|
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)
|
2024-08-29 15:14:47 -07:00
|
|
|
|
|
|
|
|
2024-08-20 09:30:23 -07:00
|
|
|
def test_attunement(db, carl):
|
|
|
|
with db.transaction():
|
2024-08-21 14:14:37 -07:00
|
|
|
helm = Armor(
|
2024-08-20 09:30:23 -07:00
|
|
|
name="Iron Helm",
|
|
|
|
rarity=Rarity.Common,
|
|
|
|
)
|
|
|
|
helm.add_modifier(Modifier("+1 AC (helmet)", target="armor_class", relative_value=1, stacks=True))
|
|
|
|
|
2024-08-21 14:14:37 -07:00
|
|
|
shield = Shield(
|
2024-08-20 09:30:23 -07:00
|
|
|
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)
|
|
|
|
|
2024-08-29 15:14:47 -07:00
|
|
|
carls_shield = carl.equipment.get(shield)
|
2024-08-20 09:30:23 -07:00
|
|
|
|
|
|
|
assert carl.armor_class == 10
|
|
|
|
assert len(carl.attuned_items) == 0
|
|
|
|
|
2024-08-29 16:41:15 -07:00
|
|
|
carls_shield.equip()
|
2024-08-20 09:30:23 -07:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-09-02 12:39:59 -07:00
|
|
|
assert carls_shield.attune()
|
|
|
|
assert not carls_shield.attune()
|
2024-08-20 09:30:23 -07:00
|
|
|
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
|
|
|
|
|
2024-08-29 16:41:15 -07:00
|
|
|
assert carl.equipment.get(helm).equip()
|
2024-08-20 09:30:23 -07:00
|
|
|
assert carl.armor_class == 13
|
|
|
|
|
2024-08-29 16:41:15 -07:00
|
|
|
assert carls_shield.unattune()
|
2024-09-02 12:39:59 -07:00
|
|
|
assert not carls_shield.unattune()
|
2024-08-20 09:30:23 -07:00
|
|
|
assert carl.armor_class == 13
|
|
|
|
assert ranged_resistance not in carl.modifiers[DamageType.ranged_weapon_attacks]
|
2024-08-29 16:41:15 -07:00
|
|
|
assert carls_shield.unequip()
|
2024-08-20 09:30:23 -07:00
|
|
|
assert carl.armor_class == 11
|
2024-09-02 12:39:59 -07:00
|
|
|
|
|
|
|
# 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()
|