test: cover tests.gameAwardKillTracksKillsAndBossKillsForMetaStats

This commit is contained in:
dualfroz committed 2026-09-10 16:01:11 +02:00
1 parent 24d2a4288f
commit 3fe669ab4b
1 file changed
+173
+173
View File
@@ -5319,6 +5319,179 @@ function tests.metaSaveLoadRoundTrip()
local path = os.tmpname()
Meta.save(meta, path)
local restored = Meta.load(path)
os.remove(path)
eq(restored.renown, meta.renown, "renown survives a save/load round trip")
eq(restored.totalRuns, meta.totalRuns, "run tally survives a save/load round trip")
eq(restored.bestDepth, meta.bestDepth, "best depth survives a save/load round trip")
eq(restored.achievements.boss_slayer, true, "achievements survive a save/load round trip")
eq(restored.perks.focus, true, "owned perks survive a save/load round trip")
end
function tests.gameAwardKillTracksKillsAndBossKillsForMetaStats()
local game = stagedGame(1, arena(21, 11), 3, 6)
local rat = Monsters.create(Monsters.byKey.rat, 1, 4, 6, 1)
rat.hp = 1
game.level.entities = { rat }
game:playerAttack(rat)
eq(game.stats.kills, 1, "felling a plain monster counts toward the kill tally")
eq(game.stats.bossKills, 0, "a plain monster does not count as a boss kill")
local lich = Bosses.create(Bosses.byKey.lich, 2, 4, 6)
lich.hp = 1
game.level.entities = { lich }
game:playerAttack(lich)
eq(game.stats.kills, 2, "felling a boss also counts toward the plain kill tally")
eq(game.stats.bossKills, 1, "felling a boss counts toward the boss-kill tally")
end
function tests.gameResolveQuestTracksQuestsCompletedForMetaStats()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.player.quest = { kind = Quests.CULL, species = "rat", need = 1, have = 0, rewardGold = 30, rewardXp = 15 }
game:resolveQuest()
eq(game.stats.questsCompleted, 0, "an unmet bounty does not count toward quests completed")
game.player.quest = { kind = Quests.CULL, species = "rat", need = 1, have = 1, rewardGold = 30, rewardXp = 15 }
game:resolveQuest()
eq(game.stats.questsCompleted, 1, "resolving a bounty counts toward quests completed")
end
function tests.gamePickupTracksArtifactsFoundForMetaStats()
local game = stagedGame(1, arena(21, 11), 5, 5)
game.level.floorItems = { { x = 5, y = 5, item = Artifacts.create("bloodfang") } }
game:pickupHere()
eq(game.stats.artifactsFound, 1, "picking up an artifact counts toward artifacts found")
ok(game.player.weapon == nil, "picking up an artifact only stashes it in inventory, it is not auto-equipped")
end
function tests.gameDescendTracksDeepestDepthEvenAfterAscending()
local game = Game.new(9, { maxDepth = 4 })
eq(game.deepestDepth, 1, "a fresh game's deepest depth is its starting depth")
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
game.player.x, game.player.y = game.level.downX, game.level.downY
game:descend()
eq(game.deepestDepth, 3, "descending twice raises the deepest depth to the floor just reached")
game.player.x, game.player.y = game.level.upX, game.level.upY
game:ascend()
eq(game.depth, 2, "ascending drops the live depth")
eq(game.deepestDepth, 3, "ascending back up does not erase the deepest depth already reached")
end
function tests.gameMetaStatsFieldsSurviveSerialization()
local game = stagedGame(1, arena(21, 11), 3, 6)
local rat = Monsters.create(Monsters.byKey.rat, 1, 4, 6, 1)
rat.hp = 1
game.level.entities = { rat }
game:playerAttack(rat)
game.deepestDepth = 3
local restored = Game.fromData(game:toData())
eq(restored.stats.kills, game.stats.kills, "the kill tally survives a save/load round trip")
eq(restored.deepestDepth, game.deepestDepth, "the deepest depth survives a save/load round trip")
local snapshot = restored:metaStats()
eq(snapshot.kills, 1, "metaStats reads the restored kill tally")
eq(snapshot.deepestDepth, 3, "metaStats reads the restored deepest depth")
end
-- Elemental resistance (resistances.lua): a damage-type layer that sits
-- underneath every existing damage pipeline. Default behaviour (no resist
-- table anywhere) must stay perfectly transparent - that is what every test
-- above this point already relies on - so these tests build entities and
-- gear with resist tables explicitly rather than reaching for bestiary data.
function tests.resistancesDefaultTierPassesDamageThroughUnchanged()
local plain = Entity.new({ x = 1, y = 1, glyph = "r", name = "rat", kind = "monster", hp = 10, maxHp = 10, attack = 2, defense = 0 })
eq(Resistances.tierOf(plain, Resistances.FIRE), Resistances.NORMAL, "an entity with no resist table is normal to every type")
eq(Resistances.apply(plain, 7, Resistances.FIRE), 7, "normal tier leaves the amount untouched")
eq(Resistances.apply(plain, 0, Resistances.FIRE), 0, "zero damage stays zero regardless of tier")
end
function tests.resistancesResistHalvesAndFloors()
local warded = Entity.new({ x = 1, y = 1, glyph = "@", name = "warded one", kind = "monster", hp = 20, maxHp = 20, attack = 2, defense = 0 })
warded.resist = { fire = Resistances.RESIST }
eq(Resistances.apply(warded, 9, Resistances.FIRE), 4, "resist halves and floors an odd amount")
eq(Resistances.apply(warded, 1, Resistances.FIRE), 0, "resist can floor a small hit all the way to zero")
eq(Resistances.apply(warded, 9, Resistances.COLD), 9, "a resist entry for one type does not bleed into another")
end
function tests.resistancesWeakAmplifies()
local frail = Entity.new({ x = 1, y = 1, glyph = "@", name = "frail one", kind = "monster", hp = 20, maxHp = 20, attack = 2, defense = 0 })
frail.resist = { cold = Resistances.WEAK }
eq(Resistances.apply(frail, 4, Resistances.COLD), 6, "weak deals 1.5x, floored")
eq(Resistances.apply(frail, 4, Resistances.FIRE), 4, "an unrelated type is unaffected by a cold weakness")
end
function tests.resistancesImmuneZeroesEvenAHugeHit()
local warded = Entity.new({ x = 1, y = 1, glyph = "@", name = "warded one", kind = "monster", hp = 20, maxHp = 20, attack = 2, defense = 0 })
warded.resist = { poison = Resistances.IMMUNE }
eq(Resistances.apply(warded, 500, Resistances.POISON), 0, "immune drops even a massive hit to nothing")
end
function tests.resistancesEquipmentGrantsATierOnTopOfABaseProfile()
local wearer = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, maxHp = 20, attack = 5, defense = 1 })
eq(Resistances.tierOf(wearer, Resistances.COLD), Resistances.NORMAL, "an unequipped entity has no resist from thin air")
wearer.ring = Items.ring("frost band", 0, 0)
wearer.ring.resist = { cold = Resistances.RESIST }
eq(Resistances.tierOf(wearer, Resistances.COLD), Resistances.RESIST, "a worn ring grants its resist tier")
eq(Resistances.apply(wearer, 10, Resistances.COLD), 5, "the granted tier scales incoming damage of that type")
wearer.armor = Items.armor("plate", 2)
wearer.armor.resist = { cold = Resistances.IMMUNE }
eq(Resistances.tierOf(wearer, Resistances.COLD), Resistances.IMMUNE, "the best tier across every equipped slot wins")
end
function tests.resistancesInnateWeaknessIsMaskedByAResistantItem()
local dragon = Entity.new({ x = 1, y = 1, glyph = "D", name = "young dragon", kind = "monster", hp = 30, maxHp = 30, attack = 8, defense = 3 })
dragon.resist = { cold = Resistances.WEAK }
eq(Resistances.tierOf(dragon, Resistances.COLD), Resistances.WEAK, "the innate weakness applies with nothing worn")
dragon.ring = Items.ring("frost band", 0, 0)
dragon.ring.resist = { cold = Resistances.RESIST }
eq(Resistances.tierOf(dragon, Resistances.COLD), Resistances.RESIST, "a resistant ring outranks and masks an innate weakness")
end
function tests.combatDefenderResistanceHalvesTheFinalBlow()
local attacker = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, maxHp = 20, attack = 10, defense = 1 })
local defender = Entity.new({ x = 2, y = 1, glyph = "o", name = "orc", kind = "monster", hp = 30, maxHp = 30, attack = 4, defense = 2 })
local baseline = Combat.damage(attacker, defender)
defender.resist = { physical = Resistances.RESIST }
local resisted = Combat.damage(attacker, defender)
eq(resisted, math.floor(baseline / 2), "a physical resist halves combat.damage's own output for the same matchup")
end
function tests.combatWeaponElementOverridesPhysicalAndCanBeFullyImmune()
local attacker = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, maxHp = 20, attack = 10, defense = 1 })
attacker.weapon = Affixes.decorate(Items.weapon("dagger", 2), Affixes.weapon[3])
eq(attacker.weapon.damageType, "fire", "a flaming affix retags the weapon's own damage type")
local mundane = Entity.new({ x = 2, y = 1, glyph = "o", name = "orc", kind = "monster", hp = 30, maxHp = 30, attack = 4, defense = 2 })
ok(Combat.damage(attacker, mundane) > 0, "a flaming weapon still hurts an ordinary target")
local salamander = Entity.new({ x = 2, y = 1, glyph = "s", name = "salamander", kind = "monster", hp = 30, maxHp = 30, attack = 4, defense = 2 })
salamander.resist = { fire = Resistances.IMMUNE }
eq(Combat.damage(attacker, salamander), 0, "the same flaming weapon does nothing at all against a fire-immune target")
end
function tests.statusPoisonAndBurningTicksRespectResistance()
local immunePoison = Entity.new({ x = 1, y = 1, glyph = "z", name = "zombie", kind = "monster", hp = 10, maxHp = 10, attack = 2, defense = 0 })
immunePoison.resist = { poison = Resistances.IMMUNE }
Status.apply(immunePoison, Status.POISON, 3, 4)
local lines = Status.tick(immunePoison)
eq(immunePoison.hp, 10, "a poison-immune entity takes no damage from a poison tick")
ok(contains(lines[1], "shrugs off"), "the immune tick logs a distinct shrug-off line")
local frailToFire = Entity.new({ x = 1, y = 1, glyph = "T", name = "troll", kind = "monster", hp = 20, maxHp = 20, attack = 6, defense = 2 })
frailToFire.resist = { fire = Resistances.WEAK }
Status.apply(frailToFire, Status.BURNING, 3, 2)
Status.tick(frailToFire)
eq(frailToFire.hp, 17, "a fire-weak entity takes 1.5x the tick's potency, floored")
end
function tests.hazardsAfflictAppliesTargetResistance()
local level = arena(21, 11)
Hazards.ignite(level, 5, 5, 3, 4)
local salamander = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
salamander.resist = { fire = Resistances.RESIST }
local hpBefore = salamander.hp
Hazards.afflict(level, salamander)
eq(salamander.hp, hpBefore - 2, "standing in fire as fire-resistant halves the usual potency")
end
function tests.spellMissileAndFireballRespectTargetResistance()
end
local order = {