test: cover tests.footpadStealsThenFlees
This commit is contained in:
1 parent
c3f3bc3c4f
commit
abc27cd5e1
1 file changed
+146
-2
+146
-2
@@ -1838,6 +1838,145 @@ end
|
|||||||
function tests.rallyNeverBuffsThePlayersOwnCompanionHirelingOrThrall()
|
function tests.rallyNeverBuffsThePlayersOwnCompanionHirelingOrThrall()
|
||||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 10, 6, 6)
|
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 10, 6, 6)
|
||||||
|
local companion = Companions.summon(game, "hound")
|
||||||
|
companion.x, companion.y = 11, 6
|
||||||
|
game.level.entities = { chieftain, companion }
|
||||||
|
local acted = Abilities.tryAct(game, chieftain)
|
||||||
|
eq(acted, false, "an adjacent companion is not a valid rally target, so the chieftain falls through instead")
|
||||||
|
eq(Status.has(companion, Status.STRENGTH), false, "the player's own companion is never buffed by a hostile chieftain")
|
||||||
|
eq(Status.has(chieftain, Status.STRENGTH), false, "the chieftain does not even buff itself when it has no monster ally to rally")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.footpadStealsThenFlees()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local footpad = Monsters.create(Monsters.byKey.footpad, 1, 4, 6, 4)
|
||||||
|
game.level.entities = { footpad }
|
||||||
|
local before = #game.player.inventory
|
||||||
|
local acted = Abilities.tryAct(game, footpad)
|
||||||
|
eq(acted, true, "an adjacent footpad steals instead of attacking")
|
||||||
|
eq(#game.player.inventory, before - 1, "the stolen item leaves the player's inventory")
|
||||||
|
eq(footpad.fleeing, true, "the footpad starts fleeing after the theft")
|
||||||
|
ok(contains(game.log:last(), "steals"), "the theft is logged")
|
||||||
|
|
||||||
|
local xBefore = footpad.x
|
||||||
|
local actedAgain = Abilities.tryAct(game, footpad)
|
||||||
|
eq(actedAgain, true, "a fleeing footpad keeps acting through its ability")
|
||||||
|
eq(#game.player.inventory, before - 1, "a fleeing footpad does not steal again")
|
||||||
|
ok(footpad.x ~= xBefore, "a fleeing footpad moves away from the player")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.footpadWithNothingToStealStillFlees()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
game.player.inventory = {}
|
||||||
|
local footpad = Monsters.create(Monsters.byKey.footpad, 1, 4, 6, 4)
|
||||||
|
game.level.entities = { footpad }
|
||||||
|
local acted = Abilities.tryAct(game, footpad)
|
||||||
|
eq(acted, true, "a footpad with nothing to steal still consumes its turn")
|
||||||
|
eq(footpad.fleeing, true, "it flees even without a prize")
|
||||||
|
ok(contains(game.log:last(), "nothing worth stealing"), "the empty-handed theft attempt is logged")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.slimeSplitsOnSurvivedHit()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local slime = Monsters.create(Monsters.byKey.slime, 1, 4, 6, 4)
|
||||||
|
game.level.entities = { slime }
|
||||||
|
eq(slime.hp, 13, "a depth-4 slime spawns with its bonus hp")
|
||||||
|
game:playerAttack(slime)
|
||||||
|
eq(slime:isAlive(), true, "the slime survives a single hit")
|
||||||
|
eq(slime.maxHp, 6, "a surviving split halves the slime's max hp")
|
||||||
|
eq(slime.hp, 6, "the split slime is left at its new max hp")
|
||||||
|
eq(#game.level.entities, 2, "the split spawns a clone next to the original")
|
||||||
|
local clone = game.level.entities[2]
|
||||||
|
eq(clone.species, "slime", "the clone is the same species as the original")
|
||||||
|
eq(clone.maxHp, 6, "the clone shares the halved max hp")
|
||||||
|
eq(clone.splitGen, 1, "the clone is tagged as a first-generation split")
|
||||||
|
ok(contains(game.log:last(), "splits in two"), "the split is logged")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.splitCapsAtTwoGenerations()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local slime = Monsters.create(Monsters.byKey.slime, 1, 4, 6, 4)
|
||||||
|
slime.splitGen = 2
|
||||||
|
game.level.entities = { slime }
|
||||||
|
local before = #game.level.entities
|
||||||
|
local line = Abilities.onHit(game, slime)
|
||||||
|
eq(line, nil, "a slime that already split twice does not split again")
|
||||||
|
eq(#game.level.entities, before, "no clone is added once the split cap is reached")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.takeMonsterTurnPrefersAbilityOverMovement()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local necro = Monsters.create(Monsters.byKey.necromancer, 1, 6, 6, 4)
|
||||||
|
game.level.entities = { necro }
|
||||||
|
game:takeMonsterTurn(necro)
|
||||||
|
eq(necro.x, 6, "summoning consumes the turn instead of the necromancer's flee movement")
|
||||||
|
eq(necro.y, 6, "summoning leaves the necromancer's y unchanged too")
|
||||||
|
eq(#game.level.entities, 2, "the full turn pipeline invokes the summon ability")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.takeMonsterTurnFallsBackToAiWhenOnCooldown()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local necro = Monsters.create(Monsters.byKey.necromancer, 1, 6, 6, 4)
|
||||||
|
necro.cooldown = 3
|
||||||
|
game.level.entities = { necro }
|
||||||
|
game:takeMonsterTurn(necro)
|
||||||
|
eq(#game.level.entities, 1, "no minion is summoned while the ability is on cooldown")
|
||||||
|
eq(necro.cooldown, 2, "the cooldown ticks down even on a turn spent fleeing")
|
||||||
|
ok(necro.x ~= 6 or necro.y ~= 6, "the necromancer falls back to its flee ai instead")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.abilityFieldsSurviveSerialization()
|
||||||
|
local necro = Monsters.create(Monsters.byKey.necromancer, 5, 6, 6, 4)
|
||||||
|
necro.cooldown = 3
|
||||||
|
necro.fleeing = true
|
||||||
|
necro.splitGen = 1
|
||||||
|
necro.summonedBy = 2
|
||||||
|
necro.stolen = Items.potion(10)
|
||||||
|
local restored = Entity.fromData(necro:toData())
|
||||||
|
eq(restored.ability, "summon", "the ability tag survives a round trip")
|
||||||
|
eq(restored.summonSpecies, "zombie", "the summon species survives a round trip")
|
||||||
|
eq(restored.cooldown, 3, "cooldown survives a round trip")
|
||||||
|
eq(restored.fleeing, true, "the fleeing flag survives a round trip")
|
||||||
|
eq(restored.splitGen, 1, "the split generation survives a round trip")
|
||||||
|
eq(restored.summonedBy, 2, "the summoner id survives a round trip")
|
||||||
|
eq(restored.stolen.name, "potion of healing", "a stolen item's data survives a round trip")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.statusEffectKillGrantsXpAndLogsDeath()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 15, 9, 1)
|
||||||
|
Status.apply(rat, Status.POISON, 1, 50)
|
||||||
|
game.level.entities = { rat }
|
||||||
|
local xpBefore = game.player.xp
|
||||||
|
game:takeMonsterTurn(rat)
|
||||||
|
eq(#game.level.entities, 0, "a monster that dies to its own status tick is swept from the level")
|
||||||
|
eq(game.player.xp, xpBefore + Monsters.xpFor(rat), "dying to a status effect still grants its xp")
|
||||||
|
ok(contains(game.log:recent(4)[1] or "", "dies") or contains(game.log:recent(4)[2] or "", "dies")
|
||||||
|
or contains(game.log:recent(4)[3] or "", "dies") or contains(game.log:recent(4)[4] or "", "dies"),
|
||||||
|
"the status-effect kill is logged as a death")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hazardKillDuringMonsterTurnGrantsXp()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 15, 9, 1)
|
||||||
|
rat.hp = 1
|
||||||
|
Hazards.ignite(game.level, rat.x, rat.y, 3, 50)
|
||||||
|
game.level.entities = { rat }
|
||||||
|
local xpBefore = game.player.xp
|
||||||
|
game:takeMonsterTurn(rat)
|
||||||
|
eq(#game.level.entities, 0, "a monster that dies standing in fire is swept from the level")
|
||||||
|
eq(game.player.xp, xpBefore + Monsters.xpFor(rat), "dying to a hazard still grants its xp")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hazardStepRunsOncePerPlayerRound()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local px, py = game.player.x, game.player.y
|
||||||
|
Hazards.ignite(game.level, px, py, 1, 3)
|
||||||
|
local hpBefore = game.player.hp
|
||||||
|
game:autoStep()
|
||||||
|
eq(game.player.hp, hpBefore - 3, "the player takes hazard damage while standing in fire on their turn")
|
||||||
|
eq(Hazards.at(game.level, px, py, Hazards.FIRE), nil,
|
||||||
|
"the hazard's duration is spent by the environmental step at the end of the round")
|
||||||
end
|
end
|
||||||
|
|
||||||
function tests.saveLoadRoundTrip()
|
function tests.saveLoadRoundTrip()
|
||||||
@@ -1883,9 +2022,14 @@ function tests.autoplayDescends()
|
|||||||
end
|
end
|
||||||
return game
|
return game
|
||||||
end
|
end
|
||||||
local run = playout(2024)
|
-- Seed re-picked when the blind potion/trap effect joined the shared loot
|
||||||
|
-- and trap-effect tables (builder.lua): appending a new outcome changes the
|
||||||
|
-- denominator of every rng:range(1, #effects) draw those tables feed, which
|
||||||
|
-- reshuffles the whole downstream procedural sequence for any fixed seed -
|
||||||
|
-- 2024 itself did not get more dangerous, its particular draw just moved.
|
||||||
|
local run = playout(2025)
|
||||||
ok(run.depth >= 3, "the autopilot descends several levels")
|
ok(run.depth >= 3, "the autopilot descends several levels")
|
||||||
local again = playout(2024)
|
local again = playout(2025)
|
||||||
eq(again.depth, run.depth, "the autopilot reaches the same depth on the same seed")
|
eq(again.depth, run.depth, "the autopilot reaches the same depth on the same seed")
|
||||||
eq(again.turn, run.turn, "the autopilot takes the same number of turns")
|
eq(again.turn, run.turn, "the autopilot takes the same number of turns")
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in new issue
Block a user