test: cover tests.commandsApplyHelpMentionsTheOptionalTargetNumberOnE...

This commit is contained in:
dualfroz committed 2026-09-13 15:21:10 +02:00
1 parent 851e57ec97
commit 0129c399f5
1 file changed
+173
+173
View File
@@ -13969,6 +13969,179 @@ function tests.commandsApplyExploreDrivesGameAutoExploreAndReturnsItsResult()
end end
function tests.commandsApplyHelpMentionsExplore() function tests.commandsApplyHelpMentionsExplore()
local game = stagedGame(1, arena(21, 11), 5, 5)
local _, message = Commands.apply(game, "help")
ok(contains(message, "explore"), "help mentions explore")
end
function tests.commandsApplyHelpMentionsTheOptionalTargetNumberOnEveryTargetedVerb()
local game = stagedGame(1, arena(21, 11), 5, 5)
local _, message = Commands.apply(game, "help")
ok(contains(message, "throw <n> [target]"), "help spells out throw's optional target number")
ok(contains(message, "fire [target]"), "help spells out fire's optional target number")
ok(contains(message, "capture <n> [target]"), "help spells out capture's optional target number")
ok(contains(message, "cast <spell> [target]"), "help spells out cast's optional target number")
ok(contains(message, "useskill <skill> [target]"), "help spells out useskill's optional target number")
end
function tests.gameRestUntilHealedRefusesWithoutSpendingATurnWhenAlreadyAtFullHealth()
local game = stagedGame(1, arena(11, 9), 5, 5)
local turnBefore = game.turn
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, false, "resting at full health spends no turn")
eq(message, "you are already at full health", "the refusal explains why")
eq(game.turn, turnBefore, "the turn counter never advances")
end
function tests.gameRestUntilHealedRefusesWithoutSpendingATurnWhileAHostileIsAlreadyInSight()
local level = arena(21, 11)
local game = stagedGame(1, level, 5, 5)
game.player.hp = game.player.maxHp - 5
local orc = Monsters.create(Monsters.byKey.orc, 1, 7, 5, 1)
orc.awareness = Stealth.DORMANT
level.entities = { orc }
game:computeVisible()
local turnBefore = game.turn
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, false, "a hostile already visible refuses to even start")
eq(message, "a monster is already in sight", "the refusal names the reason")
eq(game.turn, turnBefore, "the turn counter never advances")
end
function tests.gameRestUntilHealedHealsTurnByTurnUntilFullThenStops()
local game = stagedGame(1, arena(11, 9), 5, 5)
game.player.hp = game.player.maxHp - 6
Status.apply(game.player, Status.REGEN, 20, 2)
local turnBefore = game.turn
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, true, "a rest that actually heals spends turns")
eq(message, "you rest until your wounds close", "a completed rest reports it finished")
eq(game.player.hp, game.player.maxHp, "hp is capped exactly at maxHp, never over it")
eq(game.turn, turnBefore + 3, "exactly ceil(6/2) turns of the regen tick were needed")
end
-- Deliberately the same "nothing left to explore" shape as autoExplore's own
-- steps==0 refusal, but rest's equivalent needs a *second* unchanged turn
-- before it gives up rather than judging the first one alone - see
-- Game:restUntilHealed's own comment for why: a real player's typed "rest"
-- always runs through Game:manualStep, which already primes one turn of
-- ticking before this function's loop ever gets to take its own first
-- hpBefore snapshot, so that first reading alone cannot be trusted.
function tests.gameRestUntilHealedStopsAfterTwoUnchangedTurnsWhenNothingIsHealingThePlayer()
local game = stagedGame(1, arena(11, 9), 5, 5)
game.player.hp = game.player.maxHp - 5
local turnBefore = game.turn
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, true, "the turns spent checking still count as spent")
eq(message, "you stop resting - nothing here is healing you", "the interrupt explains itself")
eq(game.turn, turnBefore + 2, "exactly two turns were spent before giving up")
eq(game.player.hp, game.player.maxHp - 5, "hp is unchanged - nothing was healing it in the first place")
end
function tests.gameRestUntilHealedStopsTheInstantThePlayerTakesDamage()
local game = stagedGame(1, arena(11, 9), 5, 5)
game.player.hp = game.player.maxHp - 5
Status.apply(game.player, Status.POISON, 5, 4)
local turnBefore = game.turn
local hpBefore = game.player.hp
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, true, "the interrupted turn itself still spends")
eq(message, "you stop resting - you have taken damage", "the interrupt explains itself")
eq(game.turn, turnBefore + 1, "exactly the one turn that took the damage was spent")
eq(game.player.hp, hpBefore - 4, "the poison tick's own damage lands normally before the interrupt fires")
end
-- Same row placement trick as autoExplore's own sight-interrupt test, but
-- mirrored: here the *player* stands still (resting) and the orc is the one
-- that has to close the distance on its own, so it starts HUNTING (already
-- aware, not DORMANT) rather than relying on stealth.lua's wake roll - the
-- point of this test is Game:canSeePlayer's own chase range (10) reaching
-- further than the player's own vision radius (8), not the wake mechanic.
-- Status.REGEN is applied so the run has real healing to interrupt (see
-- Game:restUntilHealed's own comment - two turns are needed before "nothing
-- is healing you" could even fire, and this scenario resolves before that).
function tests.gameRestUntilHealedStopsTheMomentAHostileComesIntoSightWithoutEngagingIt()
local level = arena(30, 11)
local game = stagedGame(2, level, 20, 6)
game.player.hp = game.player.maxHp - 20
Status.apply(game.player, Status.REGEN, 50, 1)
local orc = Monsters.create(Monsters.byKey.orc, 1, 11, 6, 1)
orc.awareness = Stealth.HUNTING
level.entities = { orc }
game:computeVisible()
ok(not game.visible[level.map:index(orc.x, orc.y)], "the orc starts out of sight")
local turnBefore = game.turn
local hpBefore = game.player.hp
local orcHpBefore = orc.hp
local consumesTurn, message = game:restUntilHealed()
eq(consumesTurn, true, "real turns were spent healing before the interrupt fired")
eq(message, "you stop resting - a monster is in sight", "the interrupt names a sighted hostile")
eq(game.turn, turnBefore + 2, "exactly the steps needed to bring the orc within vision range 8 were taken")
ok(game.player.hp > hpBefore, "the turns spent before the interrupt actually healed the player")
eq(orc.hp, orcHpBefore, "the orc is only spotted, never engaged")
ok(game.visible[level.map:index(orc.x, orc.y)], "the orc really is the thing that got seen")
eq(orc.awareness, Stealth.HUNTING, "the orc's own awareness is unaffected by simply being spotted")
end
function tests.gameManualStepDrivenRestSpendsExactlyAsManyTurnsAsCallingGameRestUntilHealedDirectly()
local direct = stagedGame(9, arena(11, 9), 5, 5)
direct.player.hp = direct.player.maxHp - 6
Status.apply(direct.player, Status.REGEN, 20, 2)
direct:restUntilHealed()
local viaManualStep = stagedGame(9, arena(11, 9), 5, 5)
viaManualStep.player.hp = viaManualStep.player.maxHp - 6
Status.apply(viaManualStep.player, Status.REGEN, 20, 2)
viaManualStep:manualStep("rest")
eq(viaManualStep.turn, direct.turn,
"routing rest through Game:manualStep must not end the turn a second time on top of what " ..
"restUntilHealed already closed itself, and must credit the turn manualStep primes before " ..
"Commands.apply ever runs to the same run rather than losing it")
eq(viaManualStep.player.hp, direct.player.hp, "both paths heal the player by the same total amount")
end
function tests.commandsApplyRestDrivesGameRestUntilHealedAndReturnsItsResult()
local game = stagedGame(1, arena(11, 9), 5, 5)
local consumesTurn, message = Commands.apply(game, "rest")
eq(consumesTurn, false, "a player already at full health spends no turn")
eq(message, "you are already at full health", "the command surfaces Game:restUntilHealed's own message verbatim")
end
function tests.commandsApplyHelpMentionsRest()
local game = stagedGame(1, arena(21, 11), 5, 5)
local _, message = Commands.apply(game, "help")
ok(contains(message, "rest"), "help mentions rest")
end
-- Splits an otherwise-open arena into two floor regions with a solid wall
-- column at x == gapX between them - used only to build a destination that is
-- provably unreachable from the player's own side, for Game:travelTo's "no
-- route exists" refusal below.
local function splitArena(w, h, gapX)
local map = Map.new(w, h)
for y = 2, h - 1 do
for x = 2, gapX - 1 do map:set(x, y, Map.FLOOR) end
for x = gapX + 1, w - 1 do map:set(x, y, Map.FLOOR) end
end
return Level.new({
depth = 1, generator = "arena", map = map,
entities = {}, floorItems = {}, traps = {}, lights = {}, dark = false, explored = {},
upX = 2, upY = 2, downX = w - 1, downY = h - 1,
})
end
function tests.gameTravelToWalksTheShortestPathAndArrivesAtTheDestination()
local level = arena(21, 11)
local game = stagedGame(1, level, 3, 6)
local expectedSteps = #game:playerPathTo(level.downX, level.downY) - 1
local turnBefore = game.turn
local consumesTurn, message = game:travelTo(level.downX, level.downY)
eq(consumesTurn, true, "a real trip spends turns")
eq(message, "you arrive", "arriving reports so")
eq(game.player.x, level.downX, "the player actually reaches the destination x")
eq(game.player.y, level.downY, "the player actually reaches the destination y")
eq(game.turn, turnBefore + expectedSteps, "exactly the shortest path's own number of steps were spent")
end
end end
local order = { local order = {