test: cover tests.aMonsterSteppingOntoAPlayerSetTrapSpringsIt

This commit is contained in:
dualfroz committed 2026-09-10 13:23:03 +02:00
1 parent 4d07d9597f
commit a408c1ebfc
1 file changed
+173
+173
View File
@@ -4627,6 +4627,179 @@ function tests.plantingATrapWhereOneAlreadyExistsFails()
game.level.traps = { { x = 5, y = 5, effect = "poison", revealed = false } }
game.player.inventory = { Items.kit("dart") }
ok(not game:useItem(1), "planting a trap on an occupied tile fails")
eq(#game.player.inventory, 1, "a failed planting leaves the kit in the inventory")
end
function tests.aMonsterSteppingOntoAPlayerSetTrapSpringsIt()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.traps = { { x = 6, y = 6, effect = "dart", revealed = false, placedByPlayer = true } }
local orc = Monsters.create(Monsters.byKey.orc, 1, 7, 6, 1)
game.level.entities = { orc }
local hpBefore = orc.hp
game:stepMonsterToward(orc, game.player.x, game.player.y)
eq(orc.x, 6, "the orc steps onto the trapped cell while closing on the player")
ok(orc.hp < hpBefore, "stepping onto a player-set dart trap wounds the monster")
eq(game.level.traps[1].revealed, true, "the sprung trap becomes revealed")
end
-- Status.LEVITATE (see status.lua) makes Game:checkTrapFor and movePlayer's
-- own inline trap check both bail out before springTrap ever runs - and,
-- unlike the lava/chasm skip in checkTerrainFor, deliberately leaves the
-- trap unrevealed: floating clear of a pressure plate buys safety, not free
-- detection of what was under it.
function tests.checkTrapForSkipsAndLeavesUndetectedForALevitatingMonster()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.traps = { { x = 6, y = 6, effect = "dart", revealed = false } }
local orc = Monsters.create(Monsters.byKey.orc, 1, 7, 6, 1)
Status.apply(orc, Status.LEVITATE, 20, 1)
game.level.entities = { orc }
local hpBefore = orc.hp
game:stepMonsterToward(orc, game.player.x, game.player.y)
eq(orc.x, 6, "the levitating orc still steps onto the trapped cell while closing on the player")
eq(orc.hp, hpBefore, "a levitating monster drifting over a trap takes no damage from it")
eq(game.level.traps[1].revealed, false, "the trap it floated over is never revealed")
end
function tests.movePlayerSkipsTheInlineTrapTriggerForALevitatingPlayer()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.traps = { { x = 6, y = 5, effect = "dart", revealed = false } }
Status.apply(game.player, Status.LEVITATE, 20, 1)
local hpBefore = game.player.hp
game:movePlayer(1, 0)
eq(game.player.x, 6, "the levitating player still moves onto the trapped cell")
eq(game.player.hp, hpBefore, "a levitating player takes no damage from the trap")
eq(game.level.traps[1].revealed, false, "the trap the player floated over stays hidden")
end
function tests.aLethalTrapKillsAWeakMonsterAndCreditsThePlayer()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.traps = { { x = 6, y = 6, effect = "dart", revealed = false } }
local rat = Monsters.create(Monsters.byKey.rat, 1, 7, 6, 1)
rat.hp, rat.maxHp = 1, 1
game.level.entities = { rat }
local xpBefore = game.player.xp
game:stepMonsterToward(rat, game.player.x, game.player.y)
ok(not rat:isAlive(), "a dart trap can finish off a weak monster")
ok(game.player.xp > xpBefore, "the player is credited xp for a trap kill")
eq(#game.level.entities, 0, "the fallen monster is swept from the level")
end
function tests.aTeleportTrapRelocatesTheMonsterThatSpringsIt()
local game = stagedGame(1, arena(21, 11), 3, 6)
game.level.traps = { { x = 6, y = 6, effect = "teleport", revealed = false } }
local orc = Monsters.create(Monsters.byKey.orc, 1, 7, 6, 1)
game.level.entities = { orc }
game:stepMonsterToward(orc, game.player.x, game.player.y)
ok(not (orc.x == 6 and orc.y == 6), "a teleport trap relocates the monster off the trapped cell")
end
function tests.builderOccasionallyPlacesATrapKitFromDepthTwo()
local level = Builder.build(3, Rng.new(3), {})
local found
for _, ground in ipairs(level.floorItems) do
if ground.item.kind == Items.KIT then found = ground.item end
end
ok(found ~= nil, "this seed's depth-3 floor places a trap kit")
local isKnownEffect = false
for _, effect in ipairs(Builder.TRAP_EFFECTS) do
if found.effect == effect then isKnownEffect = true end
end
ok(isKnownEffect, "the kit's effect is one of Builder.TRAP_EFFECTS")
end
function tests.kitsStayOffTheEntranceFloor()
for seed = 1, 5 do
local level = Builder.build(1, Rng.new(seed), {})
for _, ground in ipairs(level.floorItems) do
ok(ground.item.kind ~= Items.KIT, "no trap kit appears on the entrance floor")
end
end
end
function tests.plantedTrapFieldsSurviveSerialization()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.player.inventory = { Items.kit("poison") }
game:useItem(1)
local restoredLevel = Level.fromData(game.level:toData())
eq(#restoredLevel.traps, 1, "the planted trap survives a level round trip")
eq(restoredLevel.traps[1].effect, "poison", "the planted trap's effect survives a round trip")
eq(restoredLevel.traps[1].placedByPlayer, true, "the planted trap's origin flag survives a round trip")
end
function tests.bossCreateBuildsAFreshUnenragedBoss()
local warlord = Bosses.create(Bosses.byKey.warlord, 9, 4, 6)
eq(warlord.isBoss, true, "a created boss is flagged isBoss")
eq(warlord.bossKey, "warlord", "a created boss remembers its catalog key")
eq(warlord.enraged, false, "a freshly created boss has not yet enraged")
eq(warlord.hp, warlord.maxHp, "a created boss starts at full hp")
eq(warlord.species, "boss_warlord", "a created boss's species is namespaced away from Monsters.byKey")
end
function tests.bossAtFullHealthDoesNotEnrage()
local warlord = Bosses.create(Bosses.byKey.warlord, 1, 1, 1)
local before = warlord.attack
local line = Bosses.tryEnrage(warlord)
eq(line, nil, "a boss above its enrage threshold does not enrage")
eq(warlord.enraged, false, "the boss is still marked as not enraged")
eq(warlord.attack, before, "an unenraged boss keeps its base attack")
end
function tests.bossEnragesOncePastItsHalfHpThresholdAndOnlyOnce()
local warlord = Bosses.create(Bosses.byKey.warlord, 1, 1, 1)
local spec = Bosses.byKey.warlord
warlord.hp = math.floor(warlord.maxHp * spec.enrageAt)
local attackBefore, speedBefore = warlord.attack, warlord.speed
local line = Bosses.tryEnrage(warlord)
ok(line ~= nil, "a bloodied boss enrages and logs a line")
eq(warlord.enraged, true, "the boss is now flagged enraged")
eq(warlord.attack, attackBefore + spec.enrageAttack, "enraging adds the catalog's flat attack bonus")
eq(warlord.baseAttack, attackBefore + spec.enrageAttack, "enraging also lifts the boss's base attack")
eq(warlord.speed, speedBefore + spec.enrageSpeed, "enraging adds the catalog's flat speed bonus")
local hpAfterFirst = warlord.hp
local second = Bosses.tryEnrage(warlord)
eq(second, nil, "an already-enraged boss does not enrage a second time")
eq(warlord.attack, attackBefore + spec.enrageAttack, "a second call never stacks the attack bonus again")
eq(warlord.hp, hpAfterFirst, "a second call grants no further heal")
end
function tests.bossLootMatchesItsCatalogEntry()
local lich = Bosses.create(Bosses.byKey.lich, 1, 1, 1)
local loot = Bosses.lootFor(lich)
ok(loot ~= nil, "a boss with a valid bossKey drops its catalog loot")
eq(loot.artifact, "lich_phylactery", "the dropped item is the boss's exact catalog artifact")
eq(loot.unique, Artifacts.REGEN, "the boss loot carries its unique mechanic tag")
local unknown = Bosses.create({ key = "ghost" }, 1, 1, 1)
unknown.bossKey = "ghost"
eq(Bosses.lootFor(unknown), nil, "a boss with no catalog entry drops nothing")
end
function tests.bossOnlyArtifactsAreExcludedFromTheRandomBuilderRoll()
local bossOnlyKeys = { "warlords_cleaver", "lich_phylactery", "bone_queen_carapace" }
for _, key in ipairs(bossOnlyKeys) do
ok(Artifacts.byKey[key] ~= nil, key .. " is a real catalog entry")
local inOrder = false
for _, orderKey in ipairs(Artifacts.order) do
if orderKey == key then inOrder = true end
end
ok(not inOrder, key .. " never appears in the builder's random artifact roll")
end
end
function tests.builderOccasionallyPlacesABossFromDepthSix()
local level = Builder.build(6, Rng.new(8), {})
local found
for _, entity in ipairs(level.entities) do
if entity.isBoss then found = entity end
end
ok(found ~= nil, "this seed's depth-6 floor places a named boss")
eq(found.bossKey, "warlord", "the only boss eligible at depth 6 is the warlord")
end
function tests.bossesStayOffFloorsAboveDepthSix()
for seed = 1, 5 do
for depth = 1, 5 do
local level = Builder.build(depth, Rng.new(seed), {})
for _, entity in ipairs(level.entities) do
end
local order = {