test: cover tests.nemesisSpawnScalesHpAndAttackWithGrudgeAndAppliesTh...

This commit is contained in:
dualfroz committed 2026-09-12 12:25:21 +02:00
1 parent 8e7c57dcbf
commit 2163658381
1 file changed
+173
+173
View File
@@ -10336,6 +10336,179 @@ end
function tests.nemesisEscalateRaisesGrudgeAndHealsTowardFullBothCapped() function tests.nemesisEscalateRaisesGrudgeAndHealsTowardFullBothCapped()
local pursuit = { species = "orc", grudge = 1, hpFraction = 0.3 } local pursuit = { species = "orc", grudge = 1, hpFraction = 0.3 }
Nemesis.escalate(pursuit)
eq(pursuit.grudge, 2, "escalating raises the grudge by one")
eq(pursuit.hpFraction, 0.5, "escalating heals the pursuit's carried hp fraction")
for _ = 1, 10 do Nemesis.escalate(pursuit) end
eq(pursuit.grudge, Nemesis.MAX_GRUDGE, "grudge never climbs past its cap")
eq(pursuit.hpFraction, 1, "hp fraction never heals past full")
end
function tests.nemesisSpawnScalesHpAndAttackWithGrudgeAndAppliesTheCarriedHpFraction()
local base = Monsters.create(Monsters.byKey.orc, 1, 5, 5, 1)
local weak = Nemesis.spawn({ species = "orc", grudge = 1, hpFraction = 0.5 }, 2, 5, 5, 1)
eq(weak.maxHp, base.maxHp + Nemesis.bonusHp(1), "a grudge-one nemesis carries a small hp bonus over the plain species")
eq(weak.hp, math.floor(weak.maxHp * 0.5), "the spawned nemesis's hp reflects its carried hp fraction")
eq(weak.attack, base.attack + Nemesis.bonusAttack(1), "a grudge-one nemesis carries a small attack bonus")
ok(weak.nemesis, "the spawned entity is flagged as a nemesis")
eq(weak.nemesisGrudge, 1, "the spawned entity remembers its own grudge tier")
eq(weak.species, "orc", "the spawned nemesis keeps the species key its xp/faction lookups depend on")
local fierce = Nemesis.spawn({ species = "orc", grudge = 4, hpFraction = 1 }, 3, 5, 5, 1)
ok(fierce.maxHp > weak.maxHp, "a higher grudge nemesis carries a larger hp bonus")
ok(fierce.attack > weak.attack, "a higher grudge nemesis carries a larger attack bonus")
local sliver = Nemesis.spawn({ species = "orc", grudge = 1, hpFraction = 0.001 }, 4, 5, 5, 1)
ok(sliver.hp >= 1, "a nemesis spawned from a nearly-dead fraction never rounds down to zero hp")
end
function tests.nemesisNameForEscalatesThroughEachEpithetAndClampsAtTheLast()
eq(Nemesis.nameFor("orc", 1), "orc, " .. Nemesis.EPITHETS[1], "grudge one uses the first epithet")
eq(Nemesis.nameFor("orc", #Nemesis.EPITHETS), "orc, " .. Nemesis.EPITHETS[#Nemesis.EPITHETS],
"grudge at the epithet count uses the last epithet")
eq(Nemesis.nameFor("orc", Nemesis.MAX_GRUDGE + 5), "orc, " .. Nemesis.EPITHETS[#Nemesis.EPITHETS],
"grudge beyond the epithet list clamps to the last epithet rather than indexing past it")
end
function tests.nemesisXpBonusAndBountyGoldScaleWithGrudge()
ok(Nemesis.xpBonus(2) > Nemesis.xpBonus(1), "a higher grudge pays a bigger xp bonus")
ok(Nemesis.bountyGold(2) > Nemesis.bountyGold(1), "a higher grudge pays a bigger bounty")
eq(Nemesis.bountyGold(1), 10, "grudge one's bounty matches the catalog base")
end
-- Seed 3, not 5: adding a tenth potion type (levitate) grew identify.lua's
-- potionLooks pool by one, which costs Identify.new one extra shuffle draw
-- at world creation - every seed's subsequent rng stream shifts by exactly
-- that much, including this test's nemesis-emergence chance roll. Reseeded
-- by actually running Game:descend() across a range and picking one where
-- the roll still succeeds, not guessed.
function tests.gameDescendCanRollANewNemesisFromAHuntingMonsterLeftOnTheDepartingFloor()
local game = stagedGame(3, arena(21, 11), 2, 2)
local orc = Monsters.create(Monsters.byKey.orc, 1, 10, 6, 1)
orc.awareness = Stealth.HUNTING
game.level.entities = { orc }
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
ok(game.nemesis ~= nil, "this seed's departure roll gives the hunting orc a chance to follow")
eq(game.nemesis.species, "orc", "the nemesis descriptor remembers which species followed")
local found
for _, entity in ipairs(game.level.entities) do
if entity.nemesis then found = entity end
end
ok(found ~= nil, "the nemesis is actually placed onto the floor just reached")
eq(found.nemesisGrudge, 1, "a freshly emerged nemesis starts at grudge one")
end
function tests.gameDescendNeverRollsANewNemesisFromAMonsterThatIsNotActuallyHunting()
for seed = 1, 30 do
local game = stagedGame(seed, arena(21, 11), 2, 2)
local orc = Monsters.create(Monsters.byKey.orc, 1, 10, 6, 1)
game.level.entities = { orc }
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
ok(game.nemesis == nil, "a dormant monster never gives chase no matter the seed")
end
end
function tests.gameNemesisFollowsAcrossMultipleDescentsEscalatingItsGrudgeEachTime()
local game = stagedGame(1, arena(21, 11), 2, 2)
game.nemesis = { species = "orc", grudge = 1, hpFraction = 1 }
local hunter = Nemesis.spawn(game.nemesis, game:nextMonsterId(), 10, 6, 1)
game.level.entities = { hunter }
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
eq(game.nemesis.grudge, 2, "following once past the first stairwell raises the grudge to two")
local first
for _, entity in ipairs(game.level.entities) do if entity.nemesis then first = entity end end
ok(first ~= nil, "the escalated nemesis is placed on the second floor")
eq(first.nemesisGrudge, 2, "the respawned nemesis carries the escalated grudge")
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
eq(game.nemesis.grudge, 3, "following a second time raises the grudge again")
local second
for _, entity in ipairs(game.level.entities) do if entity.nemesis then second = entity end end
ok(second ~= nil, "the nemesis follows onto the third floor too")
local nemesisCount = 0
for _, entity in ipairs(game.level.entities) do
if entity.nemesis then nemesisCount = nemesisCount + 1 end
end
eq(nemesisCount, 1, "exactly one nemesis is ever on a floor at a time, never a duplicate")
end
function tests.gameAscendCarriesAnActiveNemesisAlongButNeverRollsANewOne()
local game = stagedGame(1, arena(21, 11), 2, 2)
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
eq(game.depth, 2, "the game is now on the second floor")
eq(game.nemesis, nil, "no nemesis was following yet")
for seed = 1, 20 do
local ascendGame = stagedGame(seed, arena(21, 11), 2, 2)
ascendGame.player.x, ascendGame.player.y = ascendGame.level.downX, ascendGame.level.downY
ascendGame:descend()
local orc = Monsters.create(Monsters.byKey.orc, 99, 10, 6, ascendGame.depth)
orc.awareness = Stealth.HUNTING
ascendGame.level.entities = { orc }
ascendGame.player.x, ascendGame.player.y = ascendGame.level.upX, ascendGame.level.upY
ascendGame:ascend()
ok(ascendGame.nemesis == nil, "climbing back up never manufactures a brand new nemesis")
end
local followGame = stagedGame(1, arena(21, 11), 2, 2)
followGame.player.x, followGame.player.y = followGame.level.downX, followGame.level.downY
followGame:descend()
followGame.nemesis = { species = "orc", grudge = 2, hpFraction = 1 }
local hunter = Nemesis.spawn(followGame.nemesis, followGame:nextMonsterId(), 10, 6, followGame.depth)
followGame.level.entities = { hunter }
followGame.player.x, followGame.player.y = followGame.level.upX, followGame.level.upY
followGame:ascend()
eq(followGame.nemesis.grudge, 3, "an already-active nemesis still follows the player back up the stairs")
local found
for _, entity in ipairs(followGame.level.entities) do if entity.nemesis then found = entity end end
ok(found ~= nil, "the nemesis is placed on the floor just climbed back onto")
end
-- companions.lua/mercenaries.lua/reanimation.lua's own module comments each
-- describe a bonded ally as sticking with the player for the run (a
-- companion "persists across the whole run once bonded", a hireling demands
-- its wage "for as long as it lives") - before Game:detachCarriedAllies/
-- Game:plantCarriedAllies existed, Game:descend simply swapped self.level to
-- the next floor's own dungeon:get(depth) result, so every one of the three
-- was silently left behind, alive and well, on the departing floor forever.
function tests.gameDescendCarriesEveryLivingAllyOntoTheNextFloorInsteadOfAbandoningThem()
local game = stagedGame(1, arena(21, 11), 3, 6)
local oldLevel = game.level
local golem = Companions.summon(game, "golem")
local hireling = Mercenaries.hire(game, "sellsword")
local rat = Monsters.create(Monsters.byKey.rat, 99, 8, 6, 1)
Corpses.spawn(game.level, rat)
local thrall = Reanimation.raise(game, game.level.corpses[1])
ok(golem ~= nil and hireling ~= nil and thrall ~= nil, "all three allies bond before the descent")
game.player.x, game.player.y = game.level.downX, game.level.downY
ok(game:descend(), "descending succeeds")
eq(game.depth, 2, "the game is now on the second floor")
ok(game:hasCompanion(), "the companion is with the player on the new floor")
ok(game:hasHireling(), "the hireling is with the player on the new floor")
local thrallFound
for _, entity in ipairs(game.level.entities) do
if entity.kind == "thrall" then thrallFound = entity end
end
eq(thrallFound, thrall, "the very same thrall instance rises with the player, not a rebuilt one")
local leftBehind = 0
for _, entity in ipairs(oldLevel.entities) do
if entity.kind == "companion" or entity.kind == "hireling" or entity.kind == "thrall" then
leftBehind = leftBehind + 1
end
end
eq(leftBehind, 0, "none of the three allies are stranded on the departing floor")
end
function tests.gameAscendCarriesEveryLivingAllyBackOntoThePreviousFloorToo()
local game = stagedGame(1, arena(21, 11), 3, 6)
end end
local order = { local order = {