test: cover tests.companionGainXpNeverReappliesAnAlreadyCrossedStage
This commit is contained in:
1 parent
9b44abbbfd
commit
cf5e984712
1 file changed
+173
+173
@@ -4108,6 +4108,179 @@ function tests.companionGainXpCrossingBothThresholdsAtOnceAppliesBothStagesCumul
|
||||
eq(hound.defense, 2, "both stages' defense bonuses stack (0 base + 1 + 1)")
|
||||
end
|
||||
|
||||
function tests.companionGainXpNeverReappliesAnAlreadyCrossedStage()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
Companions.gainXp(game, hound, 12)
|
||||
Companions.gainXp(game, hound, 1)
|
||||
eq(hound.companionXp, 13, "further xp below the next threshold keeps accumulating")
|
||||
eq(hound.companionLevel, 2, "a companion does not advance again before its next threshold")
|
||||
eq(hound.attack, 6, "an already-crossed stage's bonus is never applied a second time")
|
||||
end
|
||||
|
||||
function tests.companionGainXpAtMaxLevelJustBanksFurtherXpWithNoFurtherEvolution()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
Companions.gainXp(game, hound, 40)
|
||||
Companions.gainXp(game, hound, 100)
|
||||
eq(hound.companionXp, 140, "xp keeps accumulating past the last stage")
|
||||
eq(hound.companionLevel, 3, "a fully evolved companion caps out at its last stage")
|
||||
eq(hound.attack, 9, "stats hold steady once every stage has already been crossed")
|
||||
end
|
||||
|
||||
function tests.companionGainXpIgnoresAZeroOrNegativeAmount()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
Companions.gainXp(game, hound, 0)
|
||||
Companions.gainXp(game, hound, -5)
|
||||
eq(hound.companionXp, 0, "a zero or negative grant leaves companion xp untouched")
|
||||
end
|
||||
|
||||
function tests.companionActCreditsItsOwnKillWithXpOnTopOfThePlayers()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local hound = Companions.summon(game, "hound")
|
||||
local rat = Monsters.create(Monsters.byKey.rat, 50, hound.x + 1, hound.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = rat
|
||||
local xpBefore = game.player.xp
|
||||
Companions.act(game, hound)
|
||||
eq(rat.hp, 0, "the hound's hit still kills the rat")
|
||||
eq(hound.companionXp, Monsters.xpFor(rat), "the companion banks the felled monster's own xp value")
|
||||
ok(game.player.xp > xpBefore, "the player is still separately credited xp for the companion's kill")
|
||||
end
|
||||
|
||||
function tests.companionEvolutionBoostsAHearthSpriteHealingOnTopOfItsStaticRate()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
game.player.hp = game.player.maxHp - 10
|
||||
local sprite = Companions.summon(game, "sprite")
|
||||
Companions.gainXp(game, sprite, 10)
|
||||
eq(sprite.name, "kindled sprite", "the sprite's first evolution renames it")
|
||||
local before = game.player.hp
|
||||
Companions.act(game, sprite)
|
||||
eq(game.player.hp, before + Companions.byKey.sprite.heal + 1,
|
||||
"an evolved sprite heals its species rate plus its own evolution bonus")
|
||||
end
|
||||
|
||||
function tests.companionLevelXpAndHealBonusSurviveSerialization()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sprite = Companions.summon(game, "sprite")
|
||||
Companions.gainXp(game, sprite, 10)
|
||||
local restoredLevel = Level.fromData(game.level:toData())
|
||||
local restored = restoredLevel.entities[1]
|
||||
eq(restored.companionLevel, 2, "the companion's level survives a round trip")
|
||||
eq(restored.companionXp, 10, "the companion's banked xp survives a round trip")
|
||||
eq(restored.companionHealBonus, 1, "the companion's evolved heal bonus survives a round trip")
|
||||
eq(restored.name, "kindled sprite", "the companion's evolved name survives a round trip")
|
||||
end
|
||||
|
||||
function tests.mercenaryHireTakesOnAKnownRosterEntryAdjacentToThePlayer()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
ok(sellsword ~= nil, "hiring a valid roster key succeeds next to the player")
|
||||
eq(sellsword.kind, "hireling", "a hired mercenary is tagged with the hireling kind")
|
||||
eq(math.max(math.abs(sellsword.x - 5), math.abs(sellsword.y - 5)), 1, "the hireling lands adjacent to the player")
|
||||
eq(sellsword.loyalty, Mercenaries.START_LOYALTY, "a freshly hired mercenary starts at full loyalty")
|
||||
eq(sellsword.wage, Mercenaries.byKey.sellsword.wage, "the hireling carries its roster wage")
|
||||
end
|
||||
|
||||
function tests.mercenaryHireRejectsAnUnknownRosterKey()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
eq(Mercenaries.hire(game, "dragon"), nil, "an unknown roster key hires nobody")
|
||||
eq(#game.level.entities, 0, "no entity is added for an unknown roster key")
|
||||
end
|
||||
|
||||
function tests.mercenaryHireReplacesAnEarlierHireling()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local first = Mercenaries.hire(game, "sellsword")
|
||||
local second = Mercenaries.hire(game, "medic")
|
||||
ok(not first:isAlive(), "hiring a new mercenary retires the earlier one")
|
||||
ok(second:isAlive(), "the freshly hired mercenary is alive")
|
||||
local alive = 0
|
||||
for _, e in ipairs(game.level.entities) do
|
||||
if e.kind == "hireling" and e:isAlive() then alive = alive + 1 end
|
||||
end
|
||||
eq(alive, 1, "only one hireling is ever alive at a time")
|
||||
end
|
||||
|
||||
function tests.mercenaryAttacksAdjacentHostileMonsterAndCanKillIt()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
local rat = Monsters.create(Monsters.byKey.rat, 50, sellsword.x + 1, sellsword.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = rat
|
||||
local xpBefore = game.player.xp
|
||||
Mercenaries.act(game, sellsword)
|
||||
ok(rat.hp <= 0, "a sellsword's hit kills the weak rat standing next to it")
|
||||
ok(game.player.xp > xpBefore, "the player is credited xp for its hireling's kill")
|
||||
end
|
||||
|
||||
function tests.mercenaryAdvancesOnAVisibleMonsterItCannotYetReach()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
local orc = Monsters.create(Monsters.byKey.orc, 50, sellsword.x + 5, sellsword.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = orc
|
||||
local startX = sellsword.x
|
||||
Mercenaries.act(game, sellsword)
|
||||
ok(sellsword.x > startX, "the hireling closes the distance to a monster it can see but not reach")
|
||||
end
|
||||
|
||||
function tests.mercenaryIgnoresAMonsterBeyondItsSightRadius()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 3)
|
||||
local medic = Mercenaries.hire(game, "medic")
|
||||
local orc = Monsters.create(Monsters.byKey.orc, 50, 19, 9, 1)
|
||||
game.level.entities[#game.level.entities + 1] = orc
|
||||
local startX, startY = medic.x, medic.y
|
||||
Mercenaries.act(game, medic)
|
||||
eq(medic.x, startX, "a monster far outside sight range does not pull the hireling off course (x)")
|
||||
eq(medic.y, startY, "a monster far outside sight range does not pull the hireling off course (y)")
|
||||
end
|
||||
|
||||
-- Same gap as companionActBlindedCompanionOnlyDetectsAnAdjacentHostileNot
|
||||
-- OneWithinItsSightRadius, but for Mercenaries.act's own nearestHostile.
|
||||
function tests.mercenaryActBlindedHirelingOnlyDetectsAnAdjacentHostileNotOneWithinItsSightRadius()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
local farOrc = Monsters.create(Monsters.byKey.orc, 50, sellsword.x + 5, sellsword.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = farOrc
|
||||
Status.apply(sellsword, Status.BLIND, 6, 1)
|
||||
local startX = sellsword.x
|
||||
Mercenaries.act(game, sellsword)
|
||||
eq(sellsword.x, startX, "a blinded hireling loses a sighting well within its listed sight radius")
|
||||
|
||||
local rat = Monsters.create(Monsters.byKey.rat, 51, sellsword.x + 1, sellsword.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = rat
|
||||
local ratHpBefore = rat.hp
|
||||
Mercenaries.act(game, sellsword)
|
||||
ok(rat.hp < ratHpBefore, "a blinded hireling still fights a hostile once it stands adjacent")
|
||||
end
|
||||
|
||||
-- Same gap as companionActAttackingAnAdjacentMonsterEmitsNoise..., mirrored
|
||||
-- onto the hireling side: Mercenaries.act's own Combat.attack call never
|
||||
-- routed through Game:emitNoise either.
|
||||
function tests.mercenaryActAttackingAnAdjacentMonsterEmitsNoiseThatCanStirADormantBystander()
|
||||
local stirred = false
|
||||
for seed = 1, 200 do
|
||||
local game = stagedGame(seed, arena(21, 11), 3, 6)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
local target = Monsters.create(Monsters.byKey.rat, 50, sellsword.x + 1, sellsword.y, 1)
|
||||
local bystander = Monsters.create(Monsters.byKey.orc, 51, sellsword.x + 4, sellsword.y, 1)
|
||||
bystander.awareness = Stealth.DORMANT
|
||||
game.level.entities[#game.level.entities + 1] = target
|
||||
game.level.entities[#game.level.entities + 1] = bystander
|
||||
Mercenaries.act(game, sellsword)
|
||||
if bystander.awareness ~= Stealth.DORMANT then stirred = true; break end
|
||||
end
|
||||
ok(stirred, "a hireling's own melee is exactly as loud as a companion's or the player's")
|
||||
end
|
||||
|
||||
function tests.mercenaryFollowsThePlayerWhenNoMonsterIsVisible()
|
||||
local game = stagedGame(1, arena(21, 11), 10, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.x, sellsword.y = 3, 5
|
||||
local startDist = math.abs(sellsword.x - game.player.x)
|
||||
Mercenaries.act(game, sellsword)
|
||||
ok(math.abs(sellsword.x - game.player.x) < startDist, "a hireling with no monster in sight closes in on the player")
|
||||
end
|
||||
|
||||
-- Same wiring proof as tests.companionActConfusedCompanionSometimesStumbles
|
||||
end
|
||||
|
||||
local order = {
|
||||
|
||||
Reference in new issue
Block a user