From 6dc9d144d223a0309f61b1cdb075de7cea1ef3e8 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 12:15:41 +0200 Subject: [PATCH] test: cover tests.setsDoNotCrossTalkBetweenDifferentSets --- tests/run.lua | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) diff --git a/tests/run.lua b/tests/run.lua index c834025..02bb1d2 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -7049,6 +7049,179 @@ end function tests.setsBonusDropsBackWhenAPieceIsUnequipped() local player = { weapon = Sets.create("ironclad", "weapon"), + armor = Sets.create("ironclad", "armor"), + ring = Sets.create("ironclad", "ring"), + } + eq(Sets.attackBonus(player), 2, "the full set starts active") + player.ring = Items.ring("plain band", 0, 0) + eq(Sets.attackBonus(player), 1, "losing a piece drops the bonus back to the 2-piece tier") + player.armor = Items.armor("plain mail", 2) + eq(Sets.attackBonus(player), 0, "a single remaining piece grants no bonus at all") +end + +function tests.setsDoNotCrossTalkBetweenDifferentSets() + local player = { weapon = Sets.create("ironclad", "weapon"), bow = Sets.create("duskrunner", "bow") } + eq(Sets.attackBonus(player), 0, "one piece from each of two different sets completes neither") +end + +function tests.gameEquippingASetPieceAppliesItsBonusInActualCombat() + local game = stagedGame(1, arena(21, 11), 3, 6) + Items.equipWeapon(Items.weapon("sword", 4), game.player) + local orc = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1) + orc.defense, orc.baseDefense, orc.hp, orc.maxHp = 0, 0, 40, 40 + game.level.entities = { orc } + local before = orc.hp + game:playerAttack(orc) + local plainDamage = before - orc.hp + + local game2 = stagedGame(1, arena(21, 11), 3, 6) + Items.equipWeapon(Sets.create("ironclad", "weapon"), game2.player) + Items.equipArmor(Sets.create("ironclad", "armor"), game2.player) + local orc2 = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1) + orc2.defense, orc2.baseDefense, orc2.hp, orc2.maxHp = 0, 0, 40, 40 + game2.level.entities = { orc2 } + local before2 = orc2.hp + game2:playerAttack(orc2) + local setDamage = before2 - orc2.hp + -- The ironclad weapon's own power (3) is one below the plain sword's (4), + -- but the completed 2-piece bonus adds back exactly +1 attack, landing on + -- the same total damage - proof the bonus reads live in actual combat. + eq(setDamage, plainDamage, "a 2-piece Ironclad bonus makes up for the set piece's own weaker power") +end + +function tests.gameEquippingTheSecondSetPieceLogsTheSetAwakening() + local game = stagedGame(1, arena(21, 11), 3, 6) + game.player.inventory = { Sets.create("ironclad", "weapon"), Sets.create("ironclad", "armor") } + ok(game:useItem(1), "equipping the first ironclad piece succeeds") + ok(not contains(game.log:last(), "awakens"), "a lone set piece does not announce a bonus") + ok(game:useItem(1), "equipping the second ironclad piece succeeds") + ok(contains(game.log:last(), "Ironclad") and contains(game.log:last(), "awakens"), + "completing the 2-piece threshold announces the set by name") +end + +function tests.setsRegenerationAppliesEachTurnAtTheFullIroncladSet() + local game = Game.new(1) + Items.equipWeapon(Sets.create("ironclad", "weapon"), game.player) + Items.equipArmor(Sets.create("ironclad", "armor"), game.player) + Items.equipRing(Sets.create("ironclad", "ring"), game.player) + game.player.hp = game.player.maxHp - 10 + local before = game.player.hp + game:hungerTorchTick() + eq(game.player.hp, before + 1, "the full ironclad set regenerates its fixed amount each turn") +end + +function tests.builderOccasionallyPlacesASetPieceFromDepthThree() + local found + for seed = 1, 20 do + local level = Builder.build(3, Rng.new(seed), {}) + for _, ground in ipairs(level.floorItems) do + if ground.item.set then found = ground.item end + end + if found then break end + end + ok(found ~= nil, "a depth-3 floor eventually places a set piece across a handful of seeds") + ok(Sets.byKey[found.set] ~= nil, "the placed piece names a real catalog set") +end + +function tests.setPiecesStayOffTheShallowestTwoFloors() + for seed = 1, 6 do + for depth = 1, 2 do + local level = Builder.build(depth, Rng.new(seed), {}) + for _, ground in ipairs(level.floorItems) do + ok(not ground.item.set, "no set piece appears on depth 1 or 2") + end + end + end +end + +function tests.setFieldsSurviveSerialization() + local game = Game.new(3) + Items.equipWeapon(Sets.create("ironclad", "weapon"), game.player) + local path = os.tmpname() + game:save(path) + local restored = Game.load(path) + os.remove(path) + eq(restored.player.weapon.set, "ironclad", "an equipped set piece's set key survives a save/load round trip") +end + +function tests.gamePickupTracksSetPiecesFoundForMetaStats() + local game = stagedGame(1, arena(21, 11), 5, 5) + game.level.floorItems = { { x = 5, y = 5, item = Sets.create("ironclad", "ring") } } + game:pickupHere() + eq(game.stats.setPiecesFound, 1, "picking up a set piece counts toward set pieces found") + ok(game.player.ring == nil, "picking up a set piece only stashes it in inventory, it is not auto-equipped") +end + +function tests.metaRenownForScalesWithSetPiecesFound() + local base = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, setPiecesFound = 0, won = false }) + local withPieces = Meta.renownFor({ deepestDepth = 3, bossKills = 0, questsCompleted = 0, setPiecesFound = 2, won = false }) + eq(withPieces, base + 6, "each set piece found adds a fixed renown bonus") +end + +function tests.metaRecordRunUnlocksSetCollectorOnceThreePiecesFound() + local meta = Meta.new() + local stats = { + deepestDepth = 1, kills = 0, bossKills = 0, questsCompleted = 0, + artifactsFound = 0, vaultsOpened = 0, setPiecesFound = 3, won = false, + } + local unlocked = Meta.recordRun(meta, stats) + eq(unlocked[1], "Set Collector", "finding three set pieces in a run unlocks Set Collector") + eq(meta.achievements.set_collector, true, "set_collector is marked unlocked") + local again = Meta.recordRun(meta, stats) + eq(#again, 0, "an already-unlocked Set Collector is not re-announced on a later run") +end + +function tests.renderPanelShowsTheActiveSetBonusWhenOneApplies() + local game = stagedGame(1, arena(21, 11), 3, 6) + ok(not contains(Render.panel(game), "set "), "the panel omits the set line without an active bonus") + Items.equipWeapon(Sets.create("ironclad", "weapon"), game.player) + Items.equipArmor(Sets.create("ironclad", "armor"), game.player) + ok(contains(Render.panel(game), "Ironclad 2pc"), "the panel names the active set and its piece count") +end + +function tests.mimicsFloorItemBuildsADisguisedLootShape() + local item = Mimics.floorItem(Rng.new(1)) + eq(item.kind, "mimic", "a mimic's floor item is its own kind, not a real item kind") + eq(item.mimic, true, "a mimic's floor item is tagged so pickupHere knows to unmask it") + local matched + for _, disguise in ipairs(Mimics.DISGUISES) do + if disguise.name == item.name and disguise.glyph == item.glyph then matched = true end + end + ok(matched, "the rolled disguise's name and glyph both come from the catalog") +end + +function tests.mimicsRevealBuildsAHostileMonsterScaledByDepth() + local shallow = Mimics.reveal(1, 4, 6, 1) + eq(shallow.kind, "monster", "a revealed mimic is a genuine monster entity") + eq(shallow.glyph, "M", "a revealed mimic shows its own glyph, not a disguise") + eq(shallow.mimic, true, "a revealed mimic stays tagged as a mimic") + eq(shallow.hp, 12, "a depth-1 mimic has its base hp with no bonus") + local deep = Mimics.reveal(2, 4, 6, 5) + eq(deep.hp, 16, "a mimic's hp scales with depth the same way Monsters.create scales a wild species") +end + +function tests.gamePickupHereRevealsAMimicAndAmbushesThePlayer() + local game = stagedGame(1, arena(21, 11), 5, 5) + game.player.defense, game.player.baseDefense = 0, 0 + game.level.floorItems = { { x = 5, y = 5, item = Mimics.floorItem(Rng.new(1)) } } + local hpBefore = game.player.hp + game:pickupHere() + eq(#game.level.floorItems, 0, "the disguised item leaves the floor the instant it is disturbed") + eq(#game.level.entities, 1, "a revealed mimic joins the level as a real hostile monster") + eq(game.level.entities[1].mimic, true, "the spawned monster is tagged as a mimic") + eq(game.player.hp, hpBefore - 5, "the mimic lands one free ambush hit for its full attack") + ok(contains(game.log:last(), "damage"), "the ambush hit is logged") +end + +function tests.gameKillingARevealedMimicGrantsItsFixedEssenceReward() + local game = stagedGame(1, arena(21, 11), 3, 6) + local mimic = Mimics.reveal(1, 4, 6, 1) + mimic.hp = 1 + game.level.entities = { mimic } + local essenceBefore = game.player.essence + game:playerAttack(mimic) + eq(game.player.essence, essenceBefore + Crafting.essenceFor(Mimics.xpFor()), + "felling a mimic grants essence scaled to its own fixed xp reward, not a plain monster's") end local order = {