test: cover tests.spellsLevitateAppliesTheLevitateStatusToTheCasterAn...

This commit is contained in:
dualfroz committed 2026-09-12 13:27:04 +02:00
1 parent 5e5103cecb
commit aaa221f6bf
1 file changed
+173
+173
View File
@@ -11028,6 +11028,179 @@ function tests.builderCanRollATomeOfTheCleanseSpellSomewhereInARangeOfSeeds()
local found = false local found = false
for seed = 1, 200 do for seed = 1, 200 do
local level = Builder.build(6, Rng.new(seed), {}) 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 == "cleanse" then found = true end
end
if found then break end
end
ok(found, "the cleanse tome is reachable dungeon loot within a reasonable seed range")
end
function tests.spellsLevitateAppliesTheLevitateStatusToTheCasterAndSpendsItsListedManaCost()
local game = stagedGame(1, arena(21, 11), 3, 6)
Spells.learn(game.player, "levitate")
local manaBefore = game.player.mana
local success = game:castSpell("levitate")
ok(success, "casting a learned, self-targeted levitate succeeds")
ok(Status.has(game.player, Status.LEVITATE), "the caster now carries the levitate status")
eq(game.player.mana, manaBefore - Spells.catalog.levitate.cost, "casting levitate spends exactly its listed mana cost")
end
function tests.builderCanRollATomeOfTheLevitateSpellSomewhereInARangeOfSeeds()
-- Range widened from 100 to 200: adding "weaken" to Builder's `learnable`
-- list shifted rng:range(1, #learnable)'s result for effectively every
-- seed (a longer list moves the modulo, not just adds a new bucket at the
-- end), and the nearest levitate tome under the new list happens to sit
-- at seed 173 - still well within "a reasonable seed range", just a
-- different one than before this list grew by one entry.
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 == "levitate" then found = true end
end
if found then break end
end
ok(found, "the levitate tome is reachable dungeon loot within a reasonable seed range")
end
function tests.polymorphPoolExcludesTheShopkeeperAndRespectsDepth()
local shallow = Polymorph.pool(1)
local shallowKeys = {}
for _, spec in ipairs(shallow) do shallowKeys[spec.key] = true end
ok(shallowKeys.rat and shallowKeys.bat and shallowKeys.kobold, "a depth-one pool offers every depth-one species")
ok(not shallowKeys.orc, "a depth-one pool excludes a species that first appears deeper")
local deep = Polymorph.pool(4)
local deepKeys = {}
for _, spec in ipairs(deep) do deepKeys[spec.key] = true end
ok(deepKeys.zombie and deepKeys.necromancer, "a depth-four pool offers species that first appear at depth four")
ok(not deepKeys.shopkeeper, "the shopkeeper is never offered by a polymorph, at any depth")
end
function tests.polymorphTransformPicksADifferentSpeciesAndPreservesTheHpFraction()
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
rat.hp = 2
local pool = Polymorph.pool(1)
local spec = Polymorph.transform(rat, Rng.new(7), pool)
ok(spec ~= nil, "a pool with other species on offer always yields a transform")
ok(rat.species ~= "rat", "the entity is no longer its original species")
eq(rat.species, spec.key, "the entity's species now matches whatever transform returned")
eq(rat.maxHp, spec.hp, "max hp is replaced wholesale from the new species")
local expectedHp = math.max(1, math.floor(spec.hp * 0.5 + 0.5))
eq(rat.hp, expectedHp, "current hp is rescaled to the same fraction the old body carried")
eq(rat.attack, spec.attack, "attack is replaced wholesale from the new species")
eq(rat.ai, spec.ai, "ai behaviour is replaced wholesale from the new species")
end
function tests.polymorphTransformReturnsNilWhenThePoolOffersNothingButTheCurrentSpecies()
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
local spec = Polymorph.transform(rat, Rng.new(3), { Monsters.byKey.rat })
ok(spec == nil, "a pool with nothing but the entity's own species transforms nothing")
eq(rat.species, "rat", "the entity is left untouched when there is nowhere else to go")
end
function tests.spellsPolymorphTransformsTheTargetAndSpendsItsListedManaCost()
local game = stagedGame(1, arena(21, 11), 3, 6)
Spells.learn(game.player, "polymorph")
local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1)
game.level.entities = { orc }
local manaBefore = game.player.mana
local success = game:castSpell("polymorph", orc)
ok(success, "casting a learned, in-range polymorph succeeds")
ok(orc.species ~= "orc", "the target's species actually changed")
eq(game.player.mana, manaBefore - Spells.catalog.polymorph.cost, "casting polymorph spends exactly its listed mana cost")
end
function tests.spellsPolymorphFailsOutOfRangeAndSpendsNothing()
local game = stagedGame(1, arena(21, 11), 3, 6)
Spells.learn(game.player, "polymorph")
local farOrc = Monsters.create(Monsters.byKey.orc, 1, 19, 6, 1)
game.level.entities = { farOrc }
local manaBefore = game.player.mana
local success = game:castSpell("polymorph", farOrc)
ok(not success, "polymorph cast beyond its range fails")
eq(farOrc.species, "orc", "a failed cast never transforms the target")
eq(game.player.mana, manaBefore, "a failed cast spends no mana")
end
function tests.builderCanRollATomeOfThePolymorphSpellSomewhereInARangeOfSeeds()
-- 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 polymorph tome under the new list happens to sit
-- at seed 183 - 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 == "polymorph" then found = true end
end
if found then break end
end
ok(found, "the polymorph tome is reachable dungeon loot within a reasonable seed range")
end
function tests.blocksCanReceiveRefusesWallsDoorsVaultsGatesBlocksAndEntities()
local level = arena(9, 9)
level.map:set(4, 4, Tiles.WALL)
eq(Blocks.canReceive(level, 4, 4), false, "a wall cannot receive a shoved block")
level.map:set(4, 5, Tiles.DOOR_CLOSED)
eq(Blocks.canReceive(level, 4, 5), false, "a closed door cannot receive a shoved block")
level.map:set(4, 6, Tiles.VAULT_DOOR)
eq(Blocks.canReceive(level, 4, 6), false, "a sealed vault door cannot receive a shoved block")
level.map:set(4, 7, Tiles.GATE_CLOSED)
eq(Blocks.canReceive(level, 4, 7), false, "a closed gate cannot receive a shoved block")
level.blocks = { { x = 3, y = 3 } }
eq(Blocks.canReceive(level, 3, 3), false, "a cell already holding another block cannot receive a shove")
level.entities = { Entity.new({ x = 3, y = 4, glyph = "r", name = "rat", kind = "monster", hp = 5, attack = 1, defense = 0 }) }
eq(Blocks.canReceive(level, 3, 4), false, "a cell occupied by a living entity cannot receive a shove")
eq(Blocks.canReceive(level, 5, 5), true, "plain open floor can receive a shoved block")
eq(Blocks.canReceive(level, 20, 20), false, "an out-of-bounds cell cannot receive a shove")
end
function tests.blocksPushRelocatesABlockOntoPlainFloor()
local level = arena(9, 9)
local block = { x = 3, y = 3 }
level.blocks = { block }
local pushed, line = Blocks.push(level, block, 1, 0)
eq(pushed, true, "a shove onto open floor succeeds")
eq(block.x, 4, "the block's own x relocates by the shove")
eq(block.y, 3, "the block's own y is unchanged by a purely horizontal shove")
ok(contains(line, "shove"), "a plain shove is logged as a shove")
end
function tests.blocksPushIntoLavaSolidifiesTheCellAndConsumesTheBlock()
local level = arena(9, 9)
level.map:set(4, 3, Tiles.LAVA)
local block = { x = 3, y = 3 }
level.blocks = { block }
local pushed, line = Blocks.push(level, block, 1, 0)
eq(pushed, true, "a shove into lava still succeeds")
eq(level.map:get(4, 3), Tiles.RUBBLE, "the lava cell solidifies into rubble")
eq(#level.blocks, 0, "the block is consumed by the lava it just quenched")
ok(contains(line, "lava"), "quenching lava is logged")
end
function tests.blocksPushIntoAChasmFillsTheGapAndConsumesTheBlock()
local level = arena(9, 9)
level.map:set(4, 3, Tiles.CHASM)
local block = { x = 3, y = 3 }
level.blocks = { block }
local pushed, line = Blocks.push(level, block, 1, 0)
eq(pushed, true, "a shove into a chasm still succeeds")
eq(level.map:get(4, 3), Tiles.FLOOR, "the chasm cell fills into plain floor")
eq(#level.blocks, 0, "the block is consumed filling in the chasm")
ok(contains(line, "chasm"), "filling a chasm is logged")
end
function tests.blocksPushFailsAgainstAWallOrAnotherBlockOrAnEntity()
local level = arena(9, 9)
level.map:set(4, 3, Tiles.WALL)
local block = { x = 3, y = 3 }
level.blocks = { block }
eq(Blocks.push(level, block, 1, 0), false, "a shove into a wall fails outright")
eq(block.x, 3, "a failed shove leaves the block exactly where it was")
end end
local order = { local order = {