diff --git a/tests/run.lua b/tests/run.lua index d5fbcb4..26bfc25 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -10855,6 +10855,179 @@ end function tests.builderCanRollATomeOfTheConfuseSpellSomewhereInARangeOfSeeds() local found = false for seed = 1, 100 do + local level = Builder.build(6, Rng.new(seed), {}) + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.TOME and ground.item.spell == "confuse" then found = true end + end + if found then break end + end + ok(found, "the confuse tome is reachable dungeon loot within a reasonable seed range") +end + +function tests.builderCanRollAConfuseTrapOrTrapKitSomewhereInARangeOfSeeds() + local sawTrap, sawKit = false, false + for seed = 1, 100 do + local level = Builder.build(3, Rng.new(seed), {}) + for _, trap in ipairs(level.traps) do + if trap.effect == "confuse" then sawTrap = true end + end + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.KIT and ground.item.effect == "confuse" then sawKit = true end + end + if sawTrap and sawKit then break end + end + ok(sawTrap, "a builder-placed confuse trap is reachable within a reasonable seed range") + ok(sawKit, "a confuse trap kit is reachable dungeon loot within a reasonable seed range") +end + +function tests.builderCanRollABlindTrapOrTrapKitSomewhereInARangeOfSeeds() + local sawTrap, sawKit = false, false + for seed = 1, 100 do + local level = Builder.build(3, Rng.new(seed), {}) + for _, trap in ipairs(level.traps) do + if trap.effect == "blind" then sawTrap = true end + end + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.KIT and ground.item.effect == "blind" then sawKit = true end + end + if sawTrap and sawKit then break end + end + ok(sawTrap, "a builder-placed blind trap is reachable within a reasonable seed range") + ok(sawKit, "a blind trap kit is reachable dungeon loot within a reasonable seed range") +end + +function tests.builderCanRollAStunTrapOrTrapKitSomewhereInARangeOfSeeds() + local sawTrap, sawKit = false, false + for seed = 1, 100 do + local level = Builder.build(3, Rng.new(seed), {}) + for _, trap in ipairs(level.traps) do + if trap.effect == "stun" then sawTrap = true end + end + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.KIT and ground.item.effect == "stun" then sawKit = true end + end + if sawTrap and sawKit then break end + end + ok(sawTrap, "a builder-placed stun trap is reachable within a reasonable seed range") + ok(sawKit, "a stun trap kit is reachable dungeon loot within a reasonable seed range") +end + +function tests.spellsBlindAppliesTheBlindStatusAndSpendsItsListedManaCost() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "blind") + local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1) + game.level.entities = { orc } + local manaBefore = game.player.mana + local success = game:castSpell("blind", orc) + ok(success, "casting a learned, in-range blind succeeds") + ok(Status.has(orc, Status.BLIND), "the target now carries the blind status") + eq(game.player.mana, manaBefore - Spells.catalog.blind.cost, "casting blind spends exactly its listed mana cost") +end + +function tests.spellsBlindFailsOutOfRangeAndSpendsNothing() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "blind") + local farOrc = Monsters.create(Monsters.byKey.orc, 1, 19, 6, 1) + game.level.entities = { farOrc } + local manaBefore = game.player.mana + local success = game:castSpell("blind", farOrc) + ok(not success, "blind cast beyond its range fails") + ok(not Status.has(farOrc, Status.BLIND), "a failed cast never applies the status") + eq(game.player.mana, manaBefore, "a failed cast spends no mana") +end + +function tests.builderCanRollATomeOfTheBlindSpellSomewhereInARangeOfSeeds() + -- Range widened from 100 to 200: adding "cleanse" to Builder's `learnable` + -- list shifted rng:range(1, #learnable)'s result for effectively every + -- seed, and the nearest blind tome under the new list happens to sit at + -- seed 111 - still well within "a reasonable seed range". + local found = false + for seed = 1, 200 do + local level = Builder.build(6, Rng.new(seed), {}) + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.TOME and ground.item.spell == "blind" then found = true end + end + if found then break end + end + ok(found, "the blind tome is reachable dungeon loot within a reasonable seed range") +end + +-- WEAKEN previously only ever flowed toward the player (shaman's tryCurse, +-- a cursed shrine) - see spells.lua's weaken entry for the missing other +-- direction this closes. +function tests.spellsWeakenAppliesTheWeakenStatusAndReducesAttackAndSpendsItsListedManaCost() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "weaken") + local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1) + game.level.entities = { orc } + local effectiveAttackBefore = orc:effectiveAttack() + local manaBefore = game.player.mana + local success = game:castSpell("weaken", orc) + ok(success, "casting a learned, in-range weaken succeeds") + ok(Status.has(orc, Status.WEAKEN), "the target now carries the weaken status") + eq(orc:effectiveAttack() + 3, effectiveAttackBefore, "weaken's -3 attack modifier is folded into the target's effective attack") + eq(game.player.mana, manaBefore - Spells.catalog.weaken.cost, "casting weaken spends exactly its listed mana cost") +end + +function tests.spellsWeakenFailsOutOfRangeAndSpendsNothing() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "weaken") + local farOrc = Monsters.create(Monsters.byKey.orc, 1, 19, 6, 1) + game.level.entities = { farOrc } + local manaBefore = game.player.mana + local success = game:castSpell("weaken", farOrc) + ok(not success, "weaken cast beyond its range fails") + ok(not Status.has(farOrc, Status.WEAKEN), "a failed cast never applies the status") + eq(game.player.mana, manaBefore, "a failed cast spends no mana") +end + +function tests.builderCanRollATomeOfTheWeakenSpellSomewhereInARangeOfSeeds() + -- Range widened from 100 to 200: adding "cleanse" to Builder's `learnable` + -- list shifted rng:range(1, #learnable)'s result for effectively every + -- seed, and the nearest weaken tome under the new list happens to sit at + -- seed 127 - still well within "a reasonable seed range". + local found = false + for seed = 1, 200 do + local level = Builder.build(6, Rng.new(seed), {}) + for _, ground in ipairs(level.floorItems) do + if ground.item.kind == Items.TOME and ground.item.spell == "weaken" then found = true end + end + if found then break end + end + ok(found, "the weaken tome is reachable dungeon loot within a reasonable seed range") +end + +function tests.spellsCleanseLiftsHostileStatusesAndDiseaseOnTheCasterAndSpendsItsListedManaCost() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "cleanse") + Status.apply(game.player, Status.POISON, 5, 2) + Status.apply(game.player, Status.CONFUSE, 6, 1) + Disease.infect(game.player, Disease.PARASITES) + local manaBefore = game.player.mana + local success = game:castSpell("cleanse") + ok(success, "casting a learned, self-targeted cleanse succeeds") + ok(not Status.has(game.player, Status.POISON), "cleanse lifts poison from the caster") + ok(not Status.has(game.player, Status.CONFUSE), "cleanse lifts confusion from the caster") + ok(not Disease.isInfected(game.player), "cleanse also lifts an active infection, like the cure potion does") + eq(game.player.mana, manaBefore - Spells.catalog.cleanse.cost, "casting cleanse spends exactly its listed mana cost") + ok(contains(game.log:last(), "affliction lifts") and contains(game.log:last(), "fades"), + "the single logged line credits both the disease and the status effects it lifted") +end + +function tests.spellsCleanseWithNothingHostileStillSucceedsAndSpendsMana() + local game = stagedGame(1, arena(21, 11), 3, 6) + Spells.learn(game.player, "cleanse") + local manaBefore = game.player.mana + local success = game:castSpell("cleanse") + ok(success, "casting cleanse with nothing to lift still succeeds, exactly like heal or shield always do") + eq(game.player.mana, manaBefore - Spells.catalog.cleanse.cost, "mana is still spent even when there was nothing to cleanse") + ok(contains(game.log:last(), "nothing to cleanse"), "the logged line is honest that nothing was actually lifted") +end + +function tests.builderCanRollATomeOfTheCleanseSpellSomewhereInARangeOfSeeds() + local found = false + for seed = 1, 200 do + local level = Builder.build(6, Rng.new(seed), {}) end local order = {