test: cover tests.throwingAndRangedDamageRespectResistance

This commit is contained in:
dualfroz committed 2026-09-10 16:08:32 +02:00
1 parent 3fe669ab4b
commit 90bf3238ac
1 file changed
+173
+173
View File
@@ -5492,6 +5492,179 @@ function tests.hazardsAfflictAppliesTargetResistance()
end
function tests.spellMissileAndFireballRespectTargetResistance()
local game = stagedGame(1, arena(21, 11), 3, 6)
local wisp = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
wisp.hp, wisp.maxHp = 40, 40
wisp.resist = { arcane = Resistances.IMMUNE }
game.level.entities = { wisp }
game:castSpell("missile", wisp)
eq(wisp.hp, 40, "an arcane-immune target takes no damage from magic missile")
local blastGame = stagedGame(2, arena(21, 11), 5, 6)
local orc = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1)
orc.hp, orc.maxHp = 20, 20
orc.resist = { fire = Resistances.RESIST }
blastGame.level.entities = { orc }
Effects.apply("fireball", blastGame, blastGame.player, {})
eq(orc.hp, 16, "a fire-resistant target only takes half of fireball's flat burn")
end
function tests.throwingAndRangedDamageRespectResistance()
local game = stagedGame(1, arena(21, 11), 3, 6)
local flamingDagger = Affixes.decorate(Items.weapon("dagger", 3), Affixes.weapon[3])
game.player.inventory[#game.player.inventory + 1] = flamingDagger
local salamander = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1)
salamander.hp, salamander.maxHp = 40, 40
salamander.resist = { fire = Resistances.IMMUNE }
game.level.entities = { salamander }
local success = game:throwAt(#game.player.inventory, salamander)
ok(success, "the throw itself still leaves the thrower's hand")
eq(salamander.hp, 40, "a fire-immune target takes no damage from a thrown flaming weapon")
local rangedGame = stagedGame(1, arena(21, 11), 3, 6)
rangedGame.player.bow = Items.bow("longbow", 3, 6)
rangedGame.player.inventory[#rangedGame.player.inventory + 1] = Items.ammo("arrow", 5)
local armored = Monsters.create(Monsters.byKey.orc, 1, 6, 6, 1)
armored.hp, armored.maxHp = 40, 40
armored.resist = { physical = Resistances.RESIST }
rangedGame.level.entities = { armored }
rangedGame:fireAt(armored)
eq(armored.hp, 37, "physical resistance halves the arrow's already-defense-reduced damage")
end
function tests.equippingCinderplateAndFrostbandGrantsTheirResist()
local wearer = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, maxHp = 20, attack = 5, defense = 1 })
Items.equipArmor(Artifacts.create("cinderplate"), wearer)
eq(Resistances.tierOf(wearer, Resistances.FIRE), Resistances.RESIST, "cinderplate grants fire resist while worn")
Items.equipRing(Artifacts.create("frostband"), wearer)
eq(Resistances.tierOf(wearer, Resistances.COLD), Resistances.RESIST, "frostband grants cold resist while worn")
eq(Resistances.tierOf(wearer, Resistances.POISON), Resistances.NORMAL, "neither artifact grants a resist it was not built for")
end
function tests.disenchantingAnElementalAffixClearsItsDamageType()
local game = stagedGame(1, arena(21, 11), 3, 6)
local weapon = Affixes.decorate(Items.weapon("dagger", 2), Affixes.weapon[4])
Items.equipWeapon(weapon, game.player)
eq(weapon.damageType, "poison", "venomed tags the weapon as poison-typed")
Crafting.disenchant(game.player, weapon)
eq(weapon.damageType, nil, "disenchanting clears the elemental typing along with the affix")
end
function tests.bossBreathDamageTypeMatchesTheMonsterAndCanBeShruggedOff()
local game = stagedGame(1, arena(21, 11), 3, 6)
local dragon = Monsters.create(Monsters.byKey.dragon, 1, 6, 6, 1)
game.level.entities = { dragon }
eq(Resistances.tierOf(dragon, Resistances.FIRE), Resistances.IMMUNE, "the bestiary dragon is immune to its own breath element")
eq(Resistances.tierOf(dragon, Resistances.COLD), Resistances.WEAK, "the bestiary dragon is innately weak to cold")
game.player.resist = { fire = Resistances.IMMUNE }
local hpBefore = game.player.hp
game:monsterShoot(dragon)
eq(game.player.hp, hpBefore, "a fire-immune player takes no damage from dragon breath")
eq(Status.has(game.player, Status.BURNING), false, "a fire-immune player never catches alight from the breath")
end
function tests.resistFieldsSurviveEntitySerialization()
local dragon = Monsters.create(Monsters.byKey.dragon, 1, 5, 5, 6)
local restored = Entity.fromData(dragon:toData())
eq(Resistances.tierOf(restored, Resistances.FIRE), Resistances.IMMUNE, "a restored entity keeps its innate resist profile")
end
function tests.factionsTierBoundariesReflectReputationScore()
eq(Factions.tier(0), "neutral", "a fresh reputation reads neutral")
eq(Factions.tier(-10), "unfriendly", "the unfriendly ceiling is inclusive")
eq(Factions.tier(-9), "neutral", "one point above the unfriendly ceiling is still neutral")
eq(Factions.tier(-30), "hostile", "the hostile ceiling is inclusive")
eq(Factions.tier(25), "friendly", "the friendly floor is inclusive")
eq(Factions.tier(60), "honored", "the honored floor is inclusive")
end
function tests.factionsAdjustAccumulatesAndClampsToTheHundredBounds()
local player = {}
Factions.adjust(player, Factions.GOBLINOID, -40)
eq(Factions.reputationFor(player, Factions.GOBLINOID), -40, "a first adjustment lands from the implicit zero baseline")
Factions.adjust(player, Factions.GOBLINOID, -80)
eq(Factions.reputationFor(player, Factions.GOBLINOID), -100, "reputation floors at -100")
Factions.adjust(player, Factions.GOBLINOID, 500)
eq(Factions.reputationFor(player, Factions.GOBLINOID), 100, "reputation caps at 100")
eq(Factions.reputationFor(player, Factions.MERCHANT), 0, "an untouched faction still reads neutral zero")
end
function tests.factionsRegisterKillSoursOnlyTheFallensFaction()
local player = {}
local goblin = Monsters.create(Monsters.byKey.goblin, 1, 1, 1, 2)
Factions.registerKill(player, goblin)
eq(Factions.reputationFor(player, Factions.GOBLINOID), -5, "felling a goblin sours goblinoid standing")
eq(Factions.reputationFor(player, Factions.VERMIN), 0, "an unrelated faction is left untouched")
end
function tests.factionsRegisterKillPenalizesABossFarMoreThanARegularKill()
local player = {}
local dragon = Monsters.create(Monsters.byKey.dragon, 1, 1, 1, 6)
dragon.isBoss = true
Factions.registerKill(player, dragon)
eq(Factions.reputationFor(player, Factions.DRAKE), -15, "a boss kill costs three times a rank-and-file kill")
end
function tests.factionsRegisterKillIgnoresAFactionlessTarget()
local player = {}
Factions.registerKill(player, { faction = nil })
eq(player.reputation, nil, "a factionless kill never even creates a reputation table")
end
function tests.factionsPriceMultiplierScalesFromHostileToHonored()
local player = {}
eq(Factions.priceMultiplier(player, Factions.MERCHANT), 1.0, "a fresh, neutral standing charges catalog price")
Factions.adjust(player, Factions.MERCHANT, -40)
eq(Factions.priceMultiplier(player, Factions.MERCHANT), 1.5, "a hostile guild marks its prices up")
Factions.adjust(player, Factions.MERCHANT, 100)
eq(Factions.priceMultiplier(player, Factions.MERCHANT), 0.65, "an honored guild discounts its prices")
end
function tests.factionsIsWaryTrueForAFreshGoblinoidAndFalseOnceProvoked()
local player = {}
local kobold = Monsters.create(Monsters.byKey.kobold, 1, 1, 1, 1)
eq(Factions.isWary(player, kobold), true, "an unprovoked goblinoid starts wary of a stranger")
kobold.provoked = true
eq(Factions.isWary(player, kobold), false, "a provoked goblinoid is no longer wary")
end
function tests.factionsIsWaryIsFalseForNonTribalFactionsAndOnceTheTribeGoesHostile()
local player = {}
local rat = Monsters.create(Monsters.byKey.rat, 1, 1, 1, 1)
eq(Factions.isWary(player, rat), false, "vermin were never a wary tribe to begin with")
local kobold = Monsters.create(Monsters.byKey.kobold, 2, 1, 1, 1)
Factions.adjust(player, Factions.GOBLINOID, -40)
eq(Factions.isWary(player, kobold), false, "a tribe hated into hostility stops holding back even for a fresh member")
end
function tests.factionsAppeaseSpendsGoldAndRaisesReputationOrFailsWithoutFunds()
local player = { gold = 10 }
local success, cost = Factions.appease(player, Factions.GOBLINOID)
eq(success, false, "an empty purse cannot buy any goodwill")
eq(cost, 20, "the failure reports the base tribute cost at neutral standing")
eq(player.gold, 10, "a failed tribute spends nothing")
player.gold = 100
local success2, cost2 = Factions.appease(player, Factions.GOBLINOID)
eq(success2, true, "a well-stocked purse can pay the tribute")
eq(cost2, 20, "the neutral tribute costs its base amount")
eq(player.gold, 80, "the tribute's cost is deducted")
eq(Factions.reputationFor(player, Factions.GOBLINOID), 15, "the tribute raises standing by its fixed amount")
end
function tests.factionsAppeaseCostsMoreTheDeeperTheGrudge()
local player = { gold = 1000 }
Factions.adjust(player, Factions.BANDIT, -30)
local _, cost = Factions.appease(player, Factions.BANDIT)
eq(cost, 80, "the tribute cost climbs with how negative the standing already is")
end
function tests.factionsAppeaseFailsOnceAFactionIsAlreadyMaxedOut()
local player = { gold = 1000, reputation = { [Factions.GOBLINOID] = 100 } }
local success, cost = Factions.appease(player, Factions.GOBLINOID)
eq(success, false, "an already-maxed relationship has nothing left to buy")
eq(cost, 0, "the failure reports there was no cost, not an unaffordable one")
eq(player.gold, 1000, "nothing is spent when there is nothing to buy")
end
local order = {