extends "res://addons/gut/test.gd" const CharacterSheet = preload("res://scripts/state/character_sheet.gd") func _sheet(race := "human", calling := "sellsword", attrs := {}) -> CharacterSheet: var s = CharacterSheet.new() s.race_id = race s.calling_id = calling s.attributes = {"str": 14, "dex": 12, "con": 14, "fth": 10, "mag": 8} for k in attrs: s.attributes[k] = attrs[k] return s func test_max_hp_is_hit_die_plus_con_mod(): var s = _sheet("human", "reaver") # d12, CON 14 -> +2 assert_eq(s.max_hp(), 14) var frail = _sheet("human", "hedge_mage", {"con": 8}) # d6, CON 8 -> -1 assert_eq(frail.max_hp(), 5) func test_ac_is_ten_plus_dex(): assert_eq(_sheet().ac(), 11) # DEX 12 -> +1. Armor is an item (M7). func test_initiative_is_dex(): assert_eq(_sheet().initiative(), 1) func test_save_picks_up_calling_proficiency(): var s = _sheet("elf", "sellsword") # saves STR, CON; prof +2 assert_eq(s.save("str"), 4) # STR 14 -> +2, +2 prof assert_eq(s.save("dex"), 1) # DEX 12 -> +1, not proficient func test_human_plus_one_lands_on_all_five_saves(): var human = _sheet("human", "sellsword") var elf = _sheet("elf", "sellsword") for stat in ["str", "dex", "con", "fth", "mag"]: assert_eq(human.save(stat), elf.save(stat) + 1, "human +1 on %s" % stat) func test_dwarf_poison_bonus_is_not_in_the_con_save(): var dwarf = _sheet("dwarf", "sellsword") var elf = _sheet("elf", "sellsword") assert_eq(dwarf.save("con"), elf.save("con"), "the +2 is situational, not a flat CON save") assert_eq(dwarf.poison_save_bonus(), 2) assert_eq(elf.poison_save_bonus(), 0) func test_skill_bonus_uses_the_governing_attribute(): var s = _sheet() # STR 14 -> +2 assert_eq(s.skill_bonus("athletics"), 2, "not proficient: attribute only") s.skills = ["athletics"] assert_eq(s.skill_bonus("athletics"), 4, "proficient: +2 prof") func test_bonus_skill_confers_proficiency_via_the_skills_list(): var s = _sheet() s.skills = ["athletics", "endurance"] # Task 5 appends the human's pick here s.bonus_skill = "endurance" assert_true(s.is_proficient("endurance")) assert_false(s.is_proficient("stealth")) func test_martials_have_no_mp_and_no_spell_dc(): var s = _sheet("human", "sellsword") assert_eq(s.max_mp(), 0) assert_eq(s.spell_dc(), 0) func test_casters_have_mp_and_a_spell_dc(): var s = _sheet("human", "bonesetter", {"fth": 16}) # FTH 16 -> +3 assert_true(s.max_mp() > 0, "a caster has a pool") assert_eq(s.spell_dc(), 13) # 8 + 2 prof + 3 func test_race_flags_are_exposed(): assert_true(_sheet("elf").has_nightsight()) assert_false(_sheet("human").has_nightsight()) assert_true(_sheet("beastfolk").has_claws()) assert_true(_sheet("beastfolk").has_keen_scent()) assert_false(_sheet("dwarf").has_claws()) func test_luck_is_nowhere_on_the_sheet(): # ยง7: LCK has no save, no skill, no row, no accessor. It lives in GameState. var s = _sheet() assert_false("lck" in s.attributes, "LCK is never an attribute row") assert_false(s.has_method("luck"), "the sheet must not expose Luck")