test: cover tests.curseAtRangeHurtsAndWeakens

This commit is contained in:
dualfroz committed 2026-09-08 18:55:28 +02:00
1 parent 40369e5a09
commit c3f3bc3c4f
1 file changed
+173
+173
View File
@@ -1665,6 +1665,179 @@ function tests.summonRespectsPopulationCap()
local minionB = Monsters.create(Monsters.byKey.zombie, 3, 6, 7, 4) local minionB = Monsters.create(Monsters.byKey.zombie, 3, 6, 7, 4)
minionA.summonedBy = necro.id minionA.summonedBy = necro.id
minionB.summonedBy = necro.id minionB.summonedBy = necro.id
game.level.entities = { necro, minionA, minionB }
local acted = Abilities.tryAct(game, necro)
eq(acted, false, "a necromancer already at its summon cap does not summon a third")
eq(#game.level.entities, 3, "the level still has exactly the two existing minions")
end
function tests.curseAtRangeHurtsAndWeakens()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 7, 6, 4)
game.level.entities = { shaman }
local hpBefore = game.player.hp
local acted = Abilities.tryAct(game, shaman)
eq(acted, true, "a shaman with a clear shot curses instead of moving")
eq(game.player.hp, hpBefore - 1, "the curse bolt runs through Combat.damage (shaman attack 2 minus the player's base defense 1)")
eq(Status.has(game.player, Status.WEAKEN), true, "the curse also weakens the player")
eq(shaman.cooldown, 4, "casting the curse starts its cooldown")
ok(contains(game.log:last(), "curses you for 1"), "the logged amount matches the mitigated damage, not a flat number")
end
-- The curse bolt used to bypass Combat.damage entirely (a flat, unmitigated
-- subtraction from hp), so a shaman hit for the same 3 no matter how armored
-- the player was, never broke a melee combo, and never wore armor - every
-- other monster-on-player attack (monsterMelee, monsterShoot, mimic/grave
-- ambush) already ran through Combat.damage + Momentum.onDamaged +
-- Durability.wearArmor. These three lock in the fix.
function tests.curseDamageIsMitigatedByThePlayersDefenseLikeAnyOtherAttack()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 7, 6, 4)
game.level.entities = { shaman }
game.player.defense = game.player.defense + 10
local hpBefore = game.player.hp
Abilities.tryAct(game, shaman)
eq(game.player.hp, hpBefore - 1, "a shaman's attack (2) minus a heavily-defended player floors at Combat.damage's minimum of 1, not a flat 3")
end
function tests.curseBreaksThePlayersMeleeMomentumStack()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 7, 6, 4)
game.level.entities = { shaman }
game.player.combo = 4
Abilities.tryAct(game, shaman)
eq(Momentum.stacks(game.player), 0, "a landed curse bolt snaps momentum to zero exactly like a melee hit or a breath attack")
end
function tests.curseWearsThePlayersEquippedArmor()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 7, 6, 4)
game.level.entities = { shaman }
game.player.armor = { name = "chainmail", kind = Items.ARMOR, power = 2, defense = 2 }
Durability.ensure(game.player.armor)
local durabilityBefore = game.player.armor.durability
Abilities.tryAct(game, shaman)
eq(game.player.armor.durability, durabilityBefore - 1, "the curse wears equipped armor exactly like a landed melee or breath hit does")
end
function tests.curseWithheldWhenAdjacent()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 4, 6, 4)
game.level.entities = { shaman }
local acted = Abilities.tryAct(game, shaman)
eq(acted, false, "a shaman standing next to the player withholds its curse")
end
function tests.shriekAtRangeBlindsThePlayer()
local game = stagedGame(1, arena(21, 11), 3, 6)
local banshee = Monsters.create(Monsters.byKey.banshee, 1, 7, 6, 5)
game.level.entities = { banshee }
eq(Status.has(game.player, Status.BLIND), false, "the player starts able to see")
local acted = Abilities.tryAct(game, banshee)
eq(acted, true, "a banshee with a clear shot shrieks instead of moving")
eq(Status.has(game.player, Status.BLIND), true, "the shriek blinds the player")
eq(banshee.cooldown, 6, "shrieking starts its cooldown")
ok(contains(game.log:last(), "blinds you"), "the shriek is logged")
end
function tests.shriekWithheldWhenAdjacent()
local game = stagedGame(1, arena(21, 11), 3, 6)
local banshee = Monsters.create(Monsters.byKey.banshee, 1, 4, 6, 5)
game.level.entities = { banshee }
local acted = Abilities.tryAct(game, banshee)
eq(acted, false, "a banshee standing next to the player withholds its shriek")
eq(Status.has(game.player, Status.BLIND), false, "no shriek means no blindness")
end
function tests.shriekIsUnavailableOutOfRange()
local game = stagedGame(1, arena(21, 11), 3, 6)
local banshee = Monsters.create(Monsters.byKey.banshee, 1, 20, 6, 5)
game.level.entities = { banshee }
local acted = Abilities.tryAct(game, banshee)
eq(acted, false, "a banshee out of shriek range falls through to its base ai")
eq(Status.has(game.player, Status.BLIND), false, "no shriek means no blindness")
end
function tests.shriekBlindnessReducesTheVictimsEffectiveSightRadius()
local game = stagedGame(1, arena(21, 11), 3, 6)
local banshee = Monsters.create(Monsters.byKey.banshee, 1, 7, 6, 5)
game.level.entities = { banshee }
Abilities.tryAct(game, banshee)
eq(game:effectiveRadius(), 1, "blind (also inflicted by a banshee) still narrows sight to game.lua's own radius")
end
function tests.abilityCooldownBlocksImmediateReuse()
local game = stagedGame(1, arena(21, 11), 3, 6)
local shaman = Monsters.create(Monsters.byKey.shaman, 1, 7, 6, 4)
game.level.entities = { shaman }
Abilities.tryAct(game, shaman)
local hpAfterFirst = game.player.hp
local acted = Abilities.tryAct(game, shaman)
eq(acted, false, "an ability on cooldown does not fire again")
eq(game.player.hp, hpAfterFirst, "no further damage is dealt while on cooldown")
for _ = 1, 4 do Abilities.tickCooldown(shaman) end
eq(shaman.cooldown, 0, "four ticks fully drains a four-turn cooldown")
local actedAgain = Abilities.tryAct(game, shaman)
eq(actedAgain, true, "the ability is usable again once its cooldown has ticked down")
end
function tests.healerMendsNeediestAlly()
local game = stagedGame(1, arena(21, 11), 3, 6)
local cleric = Monsters.create(Monsters.byKey.cleric, 1, 6, 6, 5)
local hurt = Monsters.create(Monsters.byKey.rat, 2, 8, 6, 5)
hurt.hp = 1
game.level.entities = { cleric, hurt }
local acted = Abilities.tryAct(game, cleric)
eq(acted, true, "a cleric with a hurt ally in sight heals instead of moving")
eq(hurt.hp, 7, "healing restores the fixed amount, capped at max hp")
eq(cleric.cooldown, 5, "healing starts its cooldown")
end
function tests.healerSkipsWhenAlliesAreHealthy()
local game = stagedGame(1, arena(21, 11), 3, 6)
local cleric = Monsters.create(Monsters.byKey.cleric, 1, 6, 6, 5)
local ally = Monsters.create(Monsters.byKey.rat, 2, 8, 6, 5)
game.level.entities = { cleric, ally }
local acted = Abilities.tryAct(game, cleric)
eq(acted, false, "a cleric with no hurt allies falls through to its base ai")
end
function tests.rallyBuffsAdjacentAllies()
local game = stagedGame(1, arena(21, 11), 3, 6)
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 10, 6, 6)
local grunt = Monsters.create(Monsters.byKey.goblin, 2, 11, 6, 6)
game.level.entities = { chieftain, grunt }
eq(Status.has(grunt, Status.STRENGTH), false, "the grunt starts unbuffed")
local acted = Abilities.tryAct(game, chieftain)
eq(acted, true, "a chieftain next to an unbuffed ally rallies instead of moving")
eq(Status.has(grunt, Status.STRENGTH), true, "rallying buffs the adjacent ally")
eq(Status.has(chieftain, Status.STRENGTH), true, "rallying also buffs the chieftain itself")
eq(chieftain.cooldown, 8, "rallying starts its cooldown")
end
function tests.rallySkipsWhenNoUnbuffedAlliesAdjacent()
local game = stagedGame(1, arena(21, 11), 3, 6)
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 10, 6, 6)
game.level.entities = { chieftain }
local acted = Abilities.tryAct(game, chieftain)
eq(acted, false, "a chieftain with no adjacent allies falls through to its base ai")
end
function tests.healerNeverMendsThePlayersOwnCompanionHirelingOrThrall()
local game = stagedGame(1, arena(21, 11), 3, 6)
local cleric = Monsters.create(Monsters.byKey.cleric, 1, 6, 6, 5)
local companion = Companions.summon(game, "hound")
companion.x, companion.y = 8, 6
companion.hp = 1
game.level.entities = { cleric, companion }
local acted = Abilities.tryAct(game, cleric)
eq(acted, false, "a hurt companion is not a valid heal target, so the cleric falls through instead")
eq(companion.hp, 1, "the player's own companion is left exactly as hurt as it was")
end
function tests.rallyNeverBuffsThePlayersOwnCompanionHirelingOrThrall()
local game = stagedGame(1, arena(21, 11), 3, 6)
local chieftain = Monsters.create(Monsters.byKey.chieftain, 1, 10, 6, 6)
end end
function tests.saveLoadRoundTrip() function tests.saveLoadRoundTrip()