test: cover tests.questBoardRendersItsGlyphAndFlipsOnceClaimed
This commit is contained in:
1 parent
61296a4dd3
commit
24d2a4288f
1 file changed
+173
+173
@@ -5146,6 +5146,179 @@ function tests.autopilotDoesNotStackASecondBountyWhileOneIsActive()
|
|||||||
local held = Quests.generate(Rng.new(1), 4, 10)
|
local held = Quests.generate(Rng.new(1), 4, 10)
|
||||||
game.player.quest = held
|
game.player.quest = held
|
||||||
game.level.questBoards = { { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
game.level.questBoards = { { x = 5, y = 5, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
||||||
|
game:choosePlayerAction()
|
||||||
|
eq(game.player.quest, held, "the autopilot keeps its held bounty rather than swapping to a new one")
|
||||||
|
eq(game.level.questBoards[1].claimed, false, "a board it cannot bind to stays unclaimed for later")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questBoardRendersItsGlyphAndFlipsOnceClaimed()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local board = { x = 5, y = 6, claimed = false, quest = Quests.generate(Rng.new(2), 4, 10) }
|
||||||
|
game.level.questBoards = { board }
|
||||||
|
game:computeVisible()
|
||||||
|
ok(game:render():find("&", 1, true) ~= nil, "an unclaimed bounty board renders its glyph")
|
||||||
|
board.claimed = true
|
||||||
|
ok(game:render():find("=", 1, true) ~= nil, "a claimed bounty board renders a different glyph")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questFieldsSurviveSerialization()
|
||||||
|
local level = arena(21, 11)
|
||||||
|
level.questBoards = { { x = 4, y = 4, claimed = true, quest = Quests.generate(Rng.new(2), 4, 10) } }
|
||||||
|
local restored = Level.fromData(level:toData())
|
||||||
|
eq(#restored.questBoards, 1, "the quest board list survives a level round trip")
|
||||||
|
eq(restored.questBoards[1].claimed, true, "a board's claimed flag survives a round trip")
|
||||||
|
eq(restored.questBoards[1].quest.kind, Quests.CULL, "a board's bounty kind survives a round trip")
|
||||||
|
eq(restored.questBoards[1].quest.need, 4, "a board's bounty tally survives a round trip")
|
||||||
|
|
||||||
|
local game = Game.new(6)
|
||||||
|
game.player.quest = { kind = Quests.HOARD, need = 200, rewardGold = 50, rewardXp = 20 }
|
||||||
|
local path = os.tmpname()
|
||||||
|
game:save(path)
|
||||||
|
local restoredGame = Game.load(path)
|
||||||
|
os.remove(path)
|
||||||
|
eq(restoredGame.player.quest.kind, Quests.HOARD, "the player's own bound bounty survives a save/load round trip")
|
||||||
|
eq(restoredGame.player.quest.need, 200, "the bound bounty's target survives a save/load round trip")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.builderOccasionallyPlacesAQuestBoardFromDepthTwo()
|
||||||
|
local level = Builder.build(2, Rng.new(11), {})
|
||||||
|
eq(#level.questBoards, 1, "this seed's depth-2 floor places a bounty board")
|
||||||
|
eq(level.questBoards[1].claimed, false, "a freshly built board starts unclaimed")
|
||||||
|
ok(level.questBoards[1].quest ~= nil, "a freshly built board already carries its one bounty")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.questBoardsStayOffTheEntranceFloor()
|
||||||
|
for seed = 1, 5 do
|
||||||
|
local level = Builder.build(1, Rng.new(seed), {})
|
||||||
|
eq(#level.questBoards, 0, "no bounty board appears on the entrance floor")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaFreshStateHasNoRenownRunsOrAchievements()
|
||||||
|
local meta = Meta.new()
|
||||||
|
eq(meta.renown, 0, "a fresh meta state banks no renown")
|
||||||
|
eq(meta.totalRuns, 0, "a fresh meta state has recorded no runs")
|
||||||
|
eq(meta.wins, 0, "a fresh meta state has recorded no wins")
|
||||||
|
eq(meta.bestDepth, 0, "a fresh meta state has no best depth yet")
|
||||||
|
eq(next(meta.achievements), nil, "a fresh meta state has unlocked nothing")
|
||||||
|
eq(next(meta.perks), nil, "a fresh meta state owns no perks")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaRenownForScalesWithDepthBossesAndQuestsAndAddsAWinBonus()
|
||||||
|
local base = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, won = false })
|
||||||
|
eq(base, 12, "renown scales with deepest depth alone")
|
||||||
|
local withBosses = Meta.renownFor({ deepestDepth = 3, bossKills = 2, questsCompleted = 0, won = false })
|
||||||
|
eq(withBosses, base + 30, "each felled boss adds a fixed renown bonus")
|
||||||
|
local withQuests = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 4, won = false })
|
||||||
|
eq(withQuests, base + 32, "each resolved bounty adds a fixed renown bonus")
|
||||||
|
local won = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, won = true })
|
||||||
|
eq(won, base + 100, "escaping with the amulet adds a large flat bonus")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaRecordRunBanksRenownAndAdvancesTallies()
|
||||||
|
local meta = Meta.new()
|
||||||
|
Meta.recordRun(meta, { deepestDepth = 4, kills = 2, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = false })
|
||||||
|
eq(meta.totalRuns, 1, "recordRun advances the run tally")
|
||||||
|
eq(meta.wins, 0, "an unwon run does not advance the win tally")
|
||||||
|
eq(meta.bestDepth, 4, "recordRun raises the best depth to this run's own")
|
||||||
|
eq(meta.renown, Meta.renownFor({ deepestDepth = 4, bossKills = 0, questsCompleted = 0, won = false }),
|
||||||
|
"recordRun banks exactly the renown this run's tally is worth")
|
||||||
|
Meta.recordRun(meta, { deepestDepth = 2, kills = 0, bossKills = 0, questsCompleted = 0, artifactsFound = 0, won = true })
|
||||||
|
eq(meta.totalRuns, 2, "a second run advances the run tally again")
|
||||||
|
eq(meta.wins, 1, "a won run advances the win tally")
|
||||||
|
eq(meta.bestDepth, 4, "a shallower later run does not lower the best depth")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaRecordRunUnlocksMatchingAchievementsOnlyOnce()
|
||||||
|
local meta = Meta.new()
|
||||||
|
local stats = { deepestDepth = 5, kills = 1, bossKills = 1, questsCompleted = 3, artifactsFound = 1, won = true }
|
||||||
|
local unlocked = Meta.recordRun(meta, stats)
|
||||||
|
table.sort(unlocked)
|
||||||
|
local expected = { "Amulet Bearer", "Artificer", "Boss Slayer", "Bounty Hunter", "Deep Delver", "First Blood" }
|
||||||
|
table.sort(expected)
|
||||||
|
eq(table.concat(unlocked, ","), table.concat(expected, ","),
|
||||||
|
"a run meeting every threshold unlocks every achievement at once")
|
||||||
|
for _, key in ipairs({ "first_blood", "boss_slayer", "deep_delver", "bounty_hunter", "artificer", "amulet_bearer" }) do
|
||||||
|
eq(meta.achievements[key], true, key .. " is marked unlocked")
|
||||||
|
end
|
||||||
|
local again = Meta.recordRun(meta, stats)
|
||||||
|
eq(#again, 0, "an already-unlocked achievement is not re-announced on a later run")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaPurchaseSpendsRenownAndMarksThePerkOwned()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 30
|
||||||
|
local success, message = Meta.purchase(meta, "vigor")
|
||||||
|
eq(success, true, "purchase succeeds with enough banked renown")
|
||||||
|
eq(message, "unlocked Vigor", "purchase reports the perk it unlocked")
|
||||||
|
eq(meta.renown, 0, "purchase spends exactly the perk's cost")
|
||||||
|
eq(Meta.isOwned(meta, "vigor"), true, "the purchased perk is marked owned")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaPurchaseFailsWithoutEnoughRenown()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 5
|
||||||
|
local success, message = Meta.purchase(meta, "vigor")
|
||||||
|
eq(success, false, "purchase fails without enough banked renown")
|
||||||
|
eq(message, "not enough renown", "purchase explains why it failed")
|
||||||
|
eq(meta.renown, 5, "a failed purchase spends nothing")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaPurchaseFailsForAnAlreadyOwnedPerk()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 100
|
||||||
|
Meta.purchase(meta, "vigor")
|
||||||
|
local renownAfterFirst = meta.renown
|
||||||
|
local success, message = Meta.purchase(meta, "vigor")
|
||||||
|
eq(success, false, "purchase refuses to sell the same perk twice")
|
||||||
|
eq(message, "already owned", "purchase explains why it failed")
|
||||||
|
eq(meta.renown, renownAfterFirst, "a refused repurchase spends nothing further")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaPurchaseFailsForAnUnknownPerk()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 100
|
||||||
|
local success, message = Meta.purchase(meta, "not_a_real_perk")
|
||||||
|
eq(success, false, "purchase fails for a key outside the catalog")
|
||||||
|
eq(message, "no such perk", "purchase explains why it failed")
|
||||||
|
eq(meta.renown, 100, "a failed purchase spends nothing")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaApplyAllAppliesEveryOwnedPerkToAFreshPlayer()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 200
|
||||||
|
Meta.purchase(meta, "vigor")
|
||||||
|
Meta.purchase(meta, "purse")
|
||||||
|
local game = Game.new(1, { meta = meta })
|
||||||
|
eq(game.player.maxHp, 30 + 5, "the owned vigor perk raises max hp before class deltas")
|
||||||
|
eq(game.player.hp, 30 + 5, "the owned vigor perk raises current hp to match")
|
||||||
|
eq(game.player.gold, 20 + 25, "the owned deep pockets perk raises starting gold")
|
||||||
|
local plain = Game.new(1)
|
||||||
|
eq(plain.player.maxHp, 30, "a game started without a meta table gets no perk bonus")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaAppliedPerksLandBeneathAClassDelta()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 200
|
||||||
|
Meta.purchase(meta, "vigor")
|
||||||
|
local game = Game.new(1, { meta = meta, class = "fighter" })
|
||||||
|
eq(game.player.maxHp, 30 + 5 + 8, "a fighter's class bonus stacks on top of an owned perk's bonus")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaLoadReturnsFreshStateForAMissingFile()
|
||||||
|
local meta = Meta.load("/tmp/warren_meta_does_not_exist_12345.sav")
|
||||||
|
eq(meta.renown, 0, "loading a missing meta file yields a fresh state")
|
||||||
|
eq(meta.totalRuns, 0, "a freshly defaulted meta state has recorded no runs")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.metaSaveLoadRoundTrip()
|
||||||
|
local meta = Meta.new()
|
||||||
|
meta.renown = 42
|
||||||
|
Meta.recordRun(meta, { deepestDepth = 6, kills = 5, bossKills = 1, questsCompleted = 2, artifactsFound = 1, won = false })
|
||||||
|
Meta.purchase(meta, "focus")
|
||||||
|
local path = os.tmpname()
|
||||||
|
Meta.save(meta, path)
|
||||||
|
local restored = Meta.load(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
local order = {
|
local order = {
|
||||||
|
|||||||
Reference in new issue
Block a user