test: cover tests.bossPhaseFieldsSurviveALevelSerializationRoundTrip
This commit is contained in:
1 parent
94f102b1e2
commit
61296a4dd3
1 file changed
+173
+173
@@ -4973,6 +4973,179 @@ function tests.takeMonsterTurnSkipsAbilityAndAiWhileABossBracesOrUnleashesItsCat
|
|||||||
warlord.enraged = true
|
warlord.enraged = true
|
||||||
warlord.hp = math.floor(warlord.maxHp * spec.desperateAt)
|
warlord.hp = math.floor(warlord.maxHp * spec.desperateAt)
|
||||||
game.level.entities = { warlord }
|
game.level.entities = { warlord }
|
||||||
|
|
||||||
|
game:takeMonsterTurn(warlord)
|
||||||
|
eq(warlord.x, 4, "a bracing boss never steps toward the player through its ordinary ai")
|
||||||
|
local hpAfterBrace = game.player.hp
|
||||||
|
eq(hpAfterBrace, game.player.maxHp, "bracing alone never damages the player")
|
||||||
|
|
||||||
|
game:takeMonsterTurn(warlord)
|
||||||
|
ok(game.player.hp < hpAfterBrace, "unleashing the cataclysm damages the adjacent player")
|
||||||
|
|
||||||
|
local hpBeforeThird = game.player.hp
|
||||||
|
game:takeMonsterTurn(warlord)
|
||||||
|
ok(warlord.ability == nil or true, "a spent boss falls through to its plain rally ability or ai on its next turn")
|
||||||
|
ok(game.player.hp <= hpBeforeThird, "the boss keeps acting normally once its cataclysm is spent")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.bossPhaseFieldsSurviveALevelSerializationRoundTrip()
|
||||||
|
local game = stagedGame(3, arena(21, 11), 5, 5)
|
||||||
|
local spec = Bosses.byKey.warlord
|
||||||
|
local warlord = Bosses.create(spec, 1, 6, 5)
|
||||||
|
warlord.enraged = true
|
||||||
|
warlord.hp = math.floor(warlord.maxHp * spec.desperateAt)
|
||||||
|
Phases.tick(game, warlord) -- brace: desperate + channeling
|
||||||
|
game.level.entities = { warlord }
|
||||||
|
|
||||||
|
local restoredLevel = Level.fromData(game.level:toData())
|
||||||
|
local restored = restoredLevel.entities[1]
|
||||||
|
eq(restored.desperate, true, "the boss's desperate flag survives a round trip")
|
||||||
|
eq(restored.channeling, true, "the boss's channeling flag survives a round trip")
|
||||||
|
eq(restored.defense, warlord.defense, "the boss's braced defense value survives a round trip")
|
||||||
|
|
||||||
|
Phases.tick(game, restored) -- unleash on the restored copy
|
||||||
|
eq(restored.novaSpent, true, "a restored boss can still unleash and latch novaSpent")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questGenerateProducesACullBounty()
|
||||||
|
local bounty = Quests.generate(Rng.new(2), 4, 10)
|
||||||
|
eq(bounty.kind, Quests.CULL, "this seed at depth 4 rolls a cull bounty")
|
||||||
|
eq(bounty.species, "necromancer", "the cull bounty's target species is fixed by the seed")
|
||||||
|
eq(bounty.need, 4, "the cull bounty's tally is fixed by the seed")
|
||||||
|
eq(bounty.have, 0, "a freshly generated cull bounty starts at zero progress")
|
||||||
|
eq(bounty.rewardGold, 47, "reward gold scales with the generating depth")
|
||||||
|
eq(bounty.rewardXp, 26, "reward xp scales with the generating depth")
|
||||||
|
eq(Quests.describe(bounty), "cull 4 necromancer (0/4)", "describe renders the cull bounty's live progress")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questGenerateProducesAHoardBounty()
|
||||||
|
local bounty = Quests.generate(Rng.new(1), 4, 10)
|
||||||
|
eq(bounty.kind, Quests.HOARD, "this seed at depth 4 rolls a hoard bounty")
|
||||||
|
eq(bounty.need, 120, "the hoard bounty's absolute gold target scales with depth")
|
||||||
|
eq(Quests.describe(bounty), "amass 120 gold", "describe renders the hoard bounty's target")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questGenerateProducesADepthBountyClampedToMaxDepth()
|
||||||
|
local shallow = Quests.generate(Rng.new(5), 4, 10)
|
||||||
|
eq(shallow.kind, Quests.DEPTH, "this seed at depth 4 rolls a depth bounty")
|
||||||
|
eq(shallow.need, 6, "an unclamped depth bounty targets a few floors further down")
|
||||||
|
local nearBottom = Quests.generate(Rng.new(5), 9, 10)
|
||||||
|
eq(nearBottom.kind, Quests.DEPTH, "the same seed at depth 9 also rolls a depth bounty")
|
||||||
|
eq(nearBottom.need, 10, "a depth bounty that would exceed the dungeon's floor is clamped to it")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questAcceptBindsThePlayerAndClaimsTheBoard()
|
||||||
|
local player = { quest = nil }
|
||||||
|
local board = { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) }
|
||||||
|
local success, line = Quests.accept(player, board)
|
||||||
|
eq(success, true, "accepting an unclaimed bounty while unbound succeeds")
|
||||||
|
eq(player.quest, board.quest, "the player is bound to the board's exact bounty")
|
||||||
|
eq(board.claimed, true, "the board is claimed the instant its bounty is taken")
|
||||||
|
ok(line:find("necromancer") ~= nil, "the acceptance line names the bounty struck")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questAcceptFailsWhenAlreadyHoldingOne()
|
||||||
|
local existing = Quests.generate(Rng.new(1), 4, 10)
|
||||||
|
local player = { quest = existing }
|
||||||
|
local board = { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) }
|
||||||
|
local success = Quests.accept(player, board)
|
||||||
|
eq(success, false, "a player already bound to a bounty cannot bind to a second")
|
||||||
|
eq(player.quest, existing, "the player's original bounty is untouched")
|
||||||
|
eq(board.claimed, false, "the untaken board stays unclaimed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questAcceptFailsOnAnAlreadyClaimedBoard()
|
||||||
|
local player = { quest = nil }
|
||||||
|
local board = { x = 5, y = 5, claimed = true, quest = Quests.generate(Rng.new(2), 4, 10) }
|
||||||
|
local success = Quests.accept(player, board)
|
||||||
|
eq(success, false, "a board already claimed grants no second bounty")
|
||||||
|
eq(player.quest, nil, "the player stays unbound")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questRegisterKillIncrementsOnlyMatchingSpeciesWhileActive()
|
||||||
|
local player = { quest = { kind = Quests.CULL, species = "rat", need = 3, have = 0 } }
|
||||||
|
Quests.registerKill(player, "goblin")
|
||||||
|
eq(player.quest.have, 0, "a kill of an unrelated species does not advance the tally")
|
||||||
|
Quests.registerKill(player, "rat")
|
||||||
|
Quests.registerKill(player, "rat")
|
||||||
|
eq(player.quest.have, 2, "kills of the target species advance the tally")
|
||||||
|
Quests.registerKill(player, "rat")
|
||||||
|
Quests.registerKill(player, "rat")
|
||||||
|
eq(player.quest.have, 3, "the tally never climbs past the bounty's required count")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questResolvedDetectsEachKindsCompletion()
|
||||||
|
local culler = { quest = { kind = Quests.CULL, need = 3, have = 2 }, gold = 0 }
|
||||||
|
eq(Quests.resolved(culler, 1), false, "a cull short of its tally is not resolved")
|
||||||
|
culler.quest.have = 3
|
||||||
|
eq(Quests.resolved(culler, 1), true, "a cull at its tally is resolved")
|
||||||
|
|
||||||
|
local hoarder = { quest = { kind = Quests.HOARD, need = 100 }, gold = 40 }
|
||||||
|
eq(Quests.resolved(hoarder, 1), false, "a hoard short of its gold target is not resolved")
|
||||||
|
hoarder.gold = 100
|
||||||
|
eq(Quests.resolved(hoarder, 1), true, "a hoard at its gold target is resolved")
|
||||||
|
|
||||||
|
local pactbound = { quest = { kind = Quests.DEPTH, need = 5 }, gold = 0 }
|
||||||
|
eq(Quests.resolved(pactbound, 4), false, "a depth pact short of its floor is not resolved")
|
||||||
|
eq(Quests.resolved(pactbound, 5), true, "a depth pact met at its floor is resolved")
|
||||||
|
|
||||||
|
eq(Quests.resolved({ quest = nil, gold = 0 }, 5), false, "a player with no bounty is never resolved")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameResolveQuestPaysOutAndClearsAFulfilledBounty()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
game.player.quest = { kind = Quests.CULL, species = "rat", need = 1, have = 1, rewardGold = 30, rewardXp = 15 }
|
||||||
|
local goldBefore, essenceBefore = game.player.gold, game.player.essence
|
||||||
|
local resolved = game:resolveQuest()
|
||||||
|
eq(resolved, true, "resolveQuest reports the payout it made")
|
||||||
|
eq(game.player.gold, goldBefore + 30, "the bounty's gold reward is paid")
|
||||||
|
eq(game.player.essence, essenceBefore + Crafting.essenceFor(15), "the bounty's xp reward also converts to essence")
|
||||||
|
eq(game.player.quest, nil, "a resolved bounty frees the player to bind another")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameResolveQuestIsANoOpOnAnUnmetBounty()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
game.player.quest = { kind = Quests.CULL, species = "rat", need = 3, have = 1, rewardGold = 30, rewardXp = 15 }
|
||||||
|
local goldBefore = game.player.gold
|
||||||
|
local resolved = game:resolveQuest()
|
||||||
|
eq(resolved, false, "resolveQuest reports nothing was paid out")
|
||||||
|
eq(game.player.gold, goldBefore, "no gold changes hands on an unmet bounty")
|
||||||
|
ok(game.player.quest ~= nil, "the unmet bounty stays bound to the player")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameTryAcceptQuestBindsAndLogsWhenStandingOnAnUnclaimedBoard()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local board = { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) }
|
||||||
|
local success = game:tryAcceptQuest(board)
|
||||||
|
eq(success, true, "tryAcceptQuest binds the player to the board it stands on")
|
||||||
|
eq(game.player.quest.kind, Quests.CULL, "the bound bounty matches the board's own")
|
||||||
|
eq(board.claimed, true, "the board is spent on acceptance")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.autopilotAcceptsAnUnclaimedBountyForFreeWithoutSkippingItsTurn()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
game.level.questBoards = { { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
||||||
|
local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 5, 1)
|
||||||
|
game.level.entities = { orc }
|
||||||
|
local hpBefore = orc.hp
|
||||||
|
game:choosePlayerAction()
|
||||||
|
ok(game.player.quest ~= nil, "the autopilot binds itself to a bounty it stands on")
|
||||||
|
eq(game.level.questBoards[1].claimed, true, "the board is claimed once the autopilot steps onto it")
|
||||||
|
ok(orc.hp < hpBefore, "accepting the bounty costs no action, so the same turn still lands an attack")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.autopilotSkipsAnAlreadyClaimedBoard()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
game.level.questBoards = { { x = 5, y = 5, claimed = true, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
||||||
|
game:choosePlayerAction()
|
||||||
|
eq(game.player.quest, nil, "the autopilot never binds to an already claimed board")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.autopilotDoesNotStackASecondBountyWhileOneIsActive()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local held = Quests.generate(Rng.new(1), 4, 10)
|
||||||
|
game.player.quest = held
|
||||||
|
game.level.questBoards = { { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
||||||
end
|
end
|
||||||
|
|
||||||
local order = {
|
local order = {
|
||||||
|
|||||||
Reference in new issue
Block a user