test: cover tests.reanimationActAttackingAnAdjacentMonsterEmitsNoiseT...

This commit is contained in:
dualfroz committed 2026-09-09 17:53:47 +02:00
1 parent b35beaeee7
commit 73de8d4a93
1 file changed
+173
+173
View File
@@ -3589,6 +3589,179 @@ function tests.reanimationActBlindedThrallOnlyDetectsAnAdjacentHostileNotOneWith
game.level.entities[#game.level.entities + 1] = rat
local ratHpBefore = rat.hp
Reanimation.act(game, thrall)
ok(rat.hp < ratHpBefore, "a blinded thrall still fights a hostile once it stands adjacent")
end
-- Same gap as companionActAttackingAnAdjacentMonsterEmitsNoise..., mirrored
-- onto the thrall side. Fresh game per seed rather than a shared one both to
-- match that sibling test's shape and because Reanimation.act decrements
-- thrall.decay on every call, so a single thrall could not absorb 200 calls
-- regardless.
function tests.reanimationActAttackingAnAdjacentMonsterEmitsNoiseThatCanStirADormantBystander()
local stirred = false
for seed = 1, 200 do
local game = stagedGame(seed, arena(21, 11), 3, 6)
local corpseSource = Monsters.create(Monsters.byKey.rat, 40, game.player.x + 1, game.player.y, 1)
Corpses.spawn(game.level, corpseSource)
local thrall = Reanimation.raise(game, game.level.corpses[1])
local target = Monsters.create(Monsters.byKey.rat, 50, thrall.x + 1, thrall.y, 1)
local bystander = Monsters.create(Monsters.byKey.orc, 51, thrall.x + 4, thrall.y, 1)
bystander.awareness = Stealth.DORMANT
game.level.entities[#game.level.entities + 1] = target
game.level.entities[#game.level.entities + 1] = bystander
Reanimation.act(game, thrall)
if bystander.awareness ~= Stealth.DORMANT then stirred = true; break end
end
ok(stirred, "a thrall's own melee is exactly as loud as any other ally's or the player's")
end
function tests.reanimationActFollowsThePlayerWhenNoMonsterIsVisible()
local game = stagedGame(1, arena(21, 11), 10, 5)
local rat0 = Monsters.create(Monsters.byKey.rat, 1, 10, 5, 1)
Corpses.spawn(game.level, rat0)
local thrall = Reanimation.raise(game, game.level.corpses[1])
thrall.x, thrall.y = 3, 5
local startDist = math.abs(thrall.x - game.player.x)
Reanimation.act(game, thrall)
ok(math.abs(thrall.x - game.player.x) < startDist, "a thrall with no monster in sight closes in on the player")
end
-- Same wiring proof as the companion/hireling versions above, but for
-- Reanimation.act - placed after its own decay tick/death check (upkeep
-- the confusion roll must never skip), just before the behaviour it can
-- override.
function tests.reanimationActConfusedThrallSometimesStumblesInsteadOfFightingAnAdjacentHostile()
local sawAttack, sawStumble = false, false
for seed = 1, 200 do
local game = stagedGame(seed, arena(21, 11), 3, 6)
local rat0 = Monsters.create(Monsters.byKey.rat, 1, 3, 6, 1)
Corpses.spawn(game.level, rat0)
local thrall = Reanimation.raise(game, game.level.corpses[1])
local rat = Monsters.create(Monsters.byKey.rat, 50, thrall.x + 1, thrall.y, 1)
game.level.entities[#game.level.entities + 1] = rat
Status.apply(thrall, Status.CONFUSE, 5, 1)
local hpBefore = rat.hp
Reanimation.act(game, thrall)
if rat.hp < hpBefore then
sawAttack = true
else
sawStumble = true
end
if sawAttack and sawStumble then break end
end
ok(sawAttack, "a confused thrall's roll sometimes lets it fight an adjacent hostile as normal")
ok(sawStumble, "a confused thrall's roll sometimes overrides even a point-blank attack")
end
function tests.reanimationActCollapsesOnceItsBorrowedAnimationRunsOut()
local game = stagedGame(1, arena(21, 11), 5, 5)
local rat0 = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
Corpses.spawn(game.level, rat0)
local thrall = Reanimation.raise(game, game.level.corpses[1])
thrall.decay = 1
game:takeMonsterTurn(thrall)
eq(#game.level.entities, 0, "a thrall whose animation runs out is swept from the level")
ok(contains(game.log:last(), "crumbles"), "a collapsing thrall's fate is logged")
end
function tests.takeMonsterTurnRoutesAThrallThroughReanimationAct()
local game = stagedGame(1, arena(21, 11), 5, 5)
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 5, 5, 6)
Corpses.spawn(game.level, chieftain)
local thrall = Reanimation.raise(game, game.level.corpses[1])
local rat = Monsters.create(Monsters.byKey.rat, 50, thrall.x + 1, thrall.y, 1)
game.level.entities[#game.level.entities + 1] = rat
game:takeMonsterTurn(thrall)
eq(rat.hp, 0, "the full monster-turn pipeline dispatches a thrall's turn to reanimation.act")
end
function tests.gameRaiseCorpseSucceedsAndLogsTheCallAnswered()
local game = stagedGame(1, arena(21, 11), 5, 5)
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
Corpses.spawn(game.level, rat)
ok(game:raiseCorpse(), "raising the corpse underfoot succeeds")
eq(#game.level.corpses, 0, "the corpse is gone after raising")
local hasThrall = false
for _, e in ipairs(game.level.entities) do
if e.kind == "thrall" then hasThrall = true end
end
ok(hasThrall, "a thrall joins the level once raised through the game action")
ok(contains(game.log:last(), "answers the call"), "a successful raise is logged")
end
function tests.gameRaiseCorpseFailsWithoutACorpseUnderfoot()
local game = stagedGame(1, arena(21, 11), 5, 5)
ok(not game:raiseCorpse(), "raising empty ground fails")
ok(contains(game.log:last(), "nothing here"), "the failure is logged")
end
function tests.scavengingHerbForMapsEachWildFactionToItsOwnFixedHerb()
eq(Scavenging.herbFor({ species = "rat" }), Alchemy.NIGHTCAP, "vermin always yields nightcap")
eq(Scavenging.herbFor({ species = "goblin" }), Alchemy.BLOODROOT, "goblinoids always yield bloodroot")
eq(Scavenging.herbFor({ species = "troll" }), Alchemy.EMBERLEAF, "beasts always yield emberleaf")
eq(Scavenging.herbFor({ species = "dragon" }), Alchemy.EMBERLEAF, "drakes yield the same herb as any other beast-aspected foe")
end
function tests.scavengingHerbForReturnsNilForACorpseWithNoBestiaryEntry()
eq(Scavenging.herbFor({ species = "hound" }), nil,
"a bonded companion's own remains carry no bestiary-mapped herb")
end
function tests.gameScavengeCorpseCarvesTheMatchingHerbAndClearsTheCorpse()
local game = stagedGame(1, arena(21, 11), 5, 5)
local goblin = Monsters.create(Monsters.byKey.goblin, 1, 5, 5, 2)
Corpses.spawn(game.level, goblin)
local before = #game.player.inventory
ok(game:scavengeCorpse(), "scavenging the corpse underfoot succeeds")
eq(#game.level.corpses, 0, "the corpse is gone after scavenging")
eq(#game.player.inventory, before + 1, "the player's inventory gains the carved herb")
local herb = game.player.inventory[#game.player.inventory]
eq(herb.herb, Alchemy.BLOODROOT, "the carved herb matches the goblin's own goblinoid aspect")
ok(contains(game.log:last(), "carve"), "a successful scavenge is logged")
end
function tests.gameScavengeCorpseFailsWithoutACorpseUnderfoot()
local game = stagedGame(1, arena(21, 11), 5, 5)
ok(not game:scavengeCorpse(), "scavenging empty ground fails")
ok(contains(game.log:last(), "nothing here"), "the failure is logged")
end
function tests.gameScavengeCorpseFailsOnARottenCorpse()
local game = stagedGame(1, arena(21, 11), 5, 5)
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
Corpses.spawn(game.level, rat)
game.level.corpses[1].decay = Corpses.ROT_TURNS
local before = #game.player.inventory
ok(not game:scavengeCorpse(), "a rotten corpse yields no herb")
eq(#game.level.corpses, 1, "a failed scavenge leaves the corpse in place rather than spending it")
eq(#game.player.inventory, before, "no herb is added on a failed scavenge")
end
function tests.gameScavengeCorpseFailsForASpeciesWithNoKnownHerb()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.corpses = { { x = 5, y = 5, species = "hound", name = "hound", nutrition = 40, decay = Corpses.DECAY_TURNS } }
ok(not game:scavengeCorpse(), "a companion's own remains carry nothing worth carving")
eq(#game.level.corpses, 1, "a failed scavenge leaves the corpse in place")
end
function tests.reanimationFieldsSurviveSerialization()
local game = stagedGame(1, arena(21, 11), 5, 5)
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
Corpses.spawn(game.level, rat)
local thrall = Reanimation.raise(game, game.level.corpses[1])
thrall.decay = 17
local restoredLevel = Level.fromData(game.level:toData())
eq(#restoredLevel.entities, 1, "the thrall survives a level round trip")
eq(restoredLevel.entities[1].kind, "thrall", "the thrall's kind survives a round trip")
eq(restoredLevel.entities[1].decay, 17, "the thrall's remaining animation survives a round trip")
end
function tests.herbItemsSurviveSerialization()
local game = Game.new(5)
game.player.inventory[#game.player.inventory + 1] = Alchemy.herb(Alchemy.NIGHTCAP)
local path = os.tmpname()
game:save(path)
end
local order = {