test: cover tests.gameAwardKillTracksKillsAndBossKillsForMetaStats
This commit is contained in:
1 parent
24d2a4288f
commit
3fe669ab4b
1 file changed
+173
+173
@@ -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 = {
|
||||
|
||||
Reference in new issue
Block a user