add spell inventory ui, refined level up ui, fixed bugs

This commit is contained in:
evilchili
2024-07-30 23:17:20 -07:00
parent 26bb645d22
commit 708d6fe9e9
9 changed files with 260 additions and 75 deletions
+34 -12
View File
@@ -91,28 +91,50 @@ def bootstrap(db):
assert acrobatics in fighter.skills
assert athletics in fighter.skills
wizard = schema.CharacterClass(
"wizard",
hit_die_name="1d6",
hit_die_stat_name="_intelligence",
)
db.add_or_update(wizard)
wizard.spell_slots = [
schema.ClassSpellSlotMap(wizard.id, class_level=1, spell_level=1),
schema.ClassSpellSlotMap(wizard.id, class_level=1, spell_level=1),
schema.ClassSpellSlotMap(wizard.id, class_level=2, spell_level=1),
schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=1),
schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=2),
schema.ClassSpellSlotMap(wizard.id, class_level=3, spell_level=2),
schema.ClassSpellSlotMap(wizard.id, class_level=4, spell_level=2),
schema.ClassSpellSlotMap(wizard.id, class_level=5, spell_level=3),
schema.ClassSpellSlotMap(wizard.id, class_level=5, spell_level=3),
schema.ClassSpellSlotMap(wizard.id, class_level=6, spell_level=3),
schema.ClassSpellSlotMap(wizard.id, class_level=7, spell_level=4),
]
rogue = schema.CharacterClass("rogue", hit_die_name="1d8", hit_die_stat_name="_dexterity")
db.add_or_update([rogue, fighter])
db.add_or_update([rogue, fighter, wizard])
# characters
foo = schema.Character("Foo", ancestry=tiefling, _intelligence=14)
db.add_or_update(foo)
foo.add_class(fighter, level=2)
foo.add_class(rogue, level=3)
# create a character: Carl the Wizard
carl = schema.Character("Carl", ancestry=tiefling, _intelligence=14)
carl.add_class(wizard)
db.add_or_update(carl)
bar = schema.Character("Bar", ancestry=human)
# persist all the records we've created
db.add_or_update([foo, bar])
@pytest.fixture
def carl(db, bootstrap, tiefling):
return schema.Character(name="Carl", ancestry=tiefling)
def carl(db, bootstrap):
return db.Character.filter_by(name="Carl").one()
@pytest.fixture
def wizard(db, bootstrap):
return db.CharacterClass.filter_by(name="wizard").one()
@pytest.fixture
def tiefling(db, bootstrap):
return db.Ancestry.filter_by(name="tiefling").one()
@pytest.fixture
def human(db, bootstrap):
return db.Ancestry.filter_by(name="human").one()
+63 -1
View File
@@ -1,4 +1,4 @@
from ttfrog.db.schema.item import Item, Spell, ItemType
from ttfrog.db.schema.item import Item, ItemType, Spell
def test_equipment_inventory(db, carl):
@@ -83,3 +83,65 @@ def test_inventory_bundles(db, carl):
# consume all remaining arrows
assert quiver.use(19) == 0
assert arrows not in carl.equipment
def test_spell_slots(db, carl, wizard):
with db.transaction():
prestidigitation = Spell(name="Prestidigitation", level=0, concentration=False)
fireball = Spell(name="Fireball", level=3, concentration=False)
db.add_or_update([carl, prestidigitation, fireball])
carl.spells.add(prestidigitation)
carl.spells.add(fireball)
db.add_or_update(carl)
# verify carl has the spell slots granted by wizard at 1st level
print(carl.levels)
print(carl.spell_slots)
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.prepared_spells
assert fireball not in carl.prepared_spells
# prepare the cantrip
carls_prestidigitation = carl.spells.get(prestidigitation)[0]
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)[0]
# 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)
# 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 carl.prepare(carls_fireball)
assert carl.cast(carls_fireball)
assert carl.cast(carls_fireball)
assert not carl.cast(carls_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.cast(carls_fireball, 4)
assert not carl.cast(carls_fireball, 4)
# use the last 3rd level slot
assert carl.cast(carls_fireball)
assert not carl.cast(carls_fireball)
+12 -13
View File
@@ -12,7 +12,7 @@ def test_manage_character(db, bootstrap):
# create a human character (the default)
char = schema.Character(name="Test Character", ancestry=human)
db.add_or_update(char)
assert char.id == 3
assert char.id
assert char.name == "Test Character"
assert char.ancestry.name == "human"
assert char.armor_class == 10
@@ -67,7 +67,7 @@ def test_manage_character(db, bootstrap):
rogue = db.CharacterClass.filter_by(name="rogue").one()
# assign a class and level
char.add_class(fighter, level=1)
assert char.add_class(fighter)
db.add_or_update(char)
assert char.levels == {"fighter": 1}
assert char.level == 1
@@ -81,7 +81,7 @@ def test_manage_character(db, bootstrap):
assert char.class_features == {}
# level up
char.add_class(fighter, level=7)
char.level_up(fighter, num_levels=6)
db.add_or_update(char)
assert char.levels == {"fighter": 7}
assert char.level == 7
@@ -109,7 +109,7 @@ def test_manage_character(db, bootstrap):
char._dexterity = 10
# multiclass
char.add_class(rogue, level=1)
char.add_class(rogue)
db.add_or_update(char)
assert char.level == 8
assert char.levels == {"fighter": 7, "rogue": 1}
@@ -132,8 +132,7 @@ def test_manage_character(db, bootstrap):
assert char.hit_dice["fighter"][0].name == fighter.hit_die_name == "1d10"
assert char.hit_dice["fighter"][0].stat == fighter.hit_die_stat_name == "_constitution"
# remove remaining class by setting level to zero
char.add_class(fighter, level=0)
char.remove_class(fighter)
db.add_or_update(char)
assert char.levels == {}
assert char.class_features == {}
@@ -205,9 +204,8 @@ def test_ancestries(db, bootstrap):
assert grognak.check_modifier(strength, save=True) == 1
def test_modifiers(db, bootstrap, carl, tiefling, human):
def test_modifiers(db, bootstrap, carl, tiefling, human, wizard):
with db.transaction():
# no modifiers; speed is ancestry speed
marx = schema.Character(name="Marx", ancestry=human)
db.add_or_update([carl, marx])
@@ -301,32 +299,33 @@ def test_modifiers(db, bootstrap, carl, tiefling, human):
assert carl.add_modifier(temp_proficiency)
assert carl.check_modifier(athletics) == carl.expertise_bonus + carl.strength.bonus == 2
assert carl.remove_modifier(temp_proficiency)
assert carl.check_modifier(athletics) == 0
# fighters get proficiency in athletics by default
fighter = db.CharacterClass.filter_by(name="fighter").one()
carl.add_class(fighter)
db.add_or_update(carl)
assert carl.check_modifier(athletics) == 1
assert carl.check_modifier(athletics) == carl.proficiency_bonus
# add the skill directly, which will grant proficiency but will not stack with proficiency from the class
carl.add_skill(athletics, proficient=True)
db.add_or_update(carl)
assert len([s for s in carl.skills if s == athletics]) == 2
assert carl.check_modifier(athletics) == 1
assert carl.check_modifier(athletics) == 2
# manually override proficiency with expertise
carl.add_skill(athletics, expert=True)
assert carl.check_modifier(athletics) == 2
assert carl.check_modifier(athletics) == 2 * carl.proficiency_bonus
assert len([s for s in carl.skills if s == athletics]) == 2
# remove expertise
carl.add_skill(athletics, proficient=True, expert=False)
assert carl.check_modifier(athletics) == 1
assert carl.check_modifier(athletics) == carl.proficiency_bonus
# remove the extra skill entirely, but the fighter proficiency remains
carl.remove_skill(athletics, proficient=True, expert=False, character_class=None)
assert len([s for s in carl.skills if s == athletics]) == 1
assert carl.check_modifier(athletics) == 1
assert carl.check_modifier(athletics) == carl.proficiency_bonus
# ensure you can't remove a skill already removed
assert not carl.remove_skill(athletics, proficient=True, expert=False, character_class=None)