test: cover tests.mercenaryActConfusedHirelingSometimesStumblesInstea...
This commit is contained in:
1 parent
cf5e984712
commit
277f28a49e
1 file changed
+173
+173
@@ -4281,6 +4281,179 @@ function tests.mercenaryFollowsThePlayerWhenNoMonsterIsVisible()
|
||||
end
|
||||
|
||||
-- Same wiring proof as tests.companionActConfusedCompanionSometimesStumbles
|
||||
-- InsteadOfFightingAnAdjacentHostile, but for Mercenaries.act - a distinct
|
||||
-- call site that could independently have been left off Game:allyActsConfused.
|
||||
function tests.mercenaryActConfusedHirelingSometimesStumblesInsteadOfFightingAnAdjacentHostile()
|
||||
local sawAttack, sawStumble = false, false
|
||||
for seed = 1, 200 do
|
||||
local game = stagedGame(seed, arena(21, 11), 3, 6)
|
||||
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
|
||||
Status.apply(sellsword, Status.CONFUSE, 5, 1)
|
||||
local hpBefore = rat.hp
|
||||
Mercenaries.act(game, sellsword)
|
||||
if rat.hp < hpBefore then
|
||||
sawAttack = true
|
||||
else
|
||||
sawStumble = true
|
||||
end
|
||||
if sawAttack and sawStumble then break end
|
||||
end
|
||||
ok(sawAttack, "a confused hireling's roll sometimes lets it fight an adjacent hostile as normal")
|
||||
ok(sawStumble, "a confused hireling's roll sometimes overrides even a point-blank attack")
|
||||
end
|
||||
|
||||
function tests.fieldMedicMendsThePlayerWhenIdle()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
game.player.hp = game.player.maxHp - 5
|
||||
local medic = Mercenaries.hire(game, "medic")
|
||||
local before = game.player.hp
|
||||
Mercenaries.act(game, medic)
|
||||
eq(game.player.hp, before + Mercenaries.byKey.medic.heal, "a field medic mends its employer each idle turn")
|
||||
end
|
||||
|
||||
function tests.mercenaryStopsChasingAndFollowingOnceSulking()
|
||||
local game = stagedGame(1, arena(21, 11), 10, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.loyalty = Mercenaries.SULKING_THRESHOLD
|
||||
sellsword.x, sellsword.y = 3, 5
|
||||
local startX, startY = sellsword.x, sellsword.y
|
||||
Mercenaries.act(game, sellsword)
|
||||
eq(sellsword.x, startX, "a sulking hireling does not close in on the player it cannot reach (x)")
|
||||
eq(sellsword.y, startY, "a sulking hireling does not close in on the player it cannot reach (y)")
|
||||
end
|
||||
|
||||
function tests.mercenaryStillFightsAnAdjacentHostileEvenWhileSulking()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.loyalty = 0
|
||||
local rat = Monsters.create(Monsters.byKey.rat, 50, sellsword.x + 1, sellsword.y, 1)
|
||||
game.level.entities[#game.level.entities + 1] = rat
|
||||
Mercenaries.act(game, sellsword)
|
||||
ok(rat.hp <= 0, "a sulking hireling still fights whatever already stands next to it")
|
||||
end
|
||||
|
||||
function tests.mercenaryPayWagePaysWhenAffordableAndRestoresLoyalty()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.loyalty = 50
|
||||
game.player.gold = 100
|
||||
local line = Mercenaries.payWage(game)
|
||||
eq(game.player.gold, 100 - sellsword.wage, "an affordable wage is deducted from the wallet")
|
||||
eq(sellsword.loyalty, 50 + Mercenaries.LOYALTY_GAIN_PAID, "paying the wage restores some loyalty")
|
||||
ok(contains(line, "pay"), "a paid wage is logged as a payment")
|
||||
end
|
||||
|
||||
function tests.mercenaryPayWageShortsAndDropsLoyaltyWhenGoldIsShort()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
game.player.gold = 0
|
||||
local line = Mercenaries.payWage(game)
|
||||
eq(game.player.gold, 0, "an unaffordable wage never touches the wallet")
|
||||
eq(sellsword.loyalty, Mercenaries.START_LOYALTY - Mercenaries.LOYALTY_LOSS_UNPAID,
|
||||
"missing a wage knocks the hireling's loyalty down")
|
||||
ok(contains(line, "grumbles"), "a missed wage that survives is logged as a grumble")
|
||||
end
|
||||
|
||||
function tests.mercenaryPayWageDesertsOnceLoyaltyBottomsOut()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.loyalty = 20
|
||||
game.player.gold = 0
|
||||
local line = Mercenaries.payWage(game)
|
||||
ok(not sellsword:isAlive(), "a hireling whose loyalty bottoms out deserts")
|
||||
eq(#game.level.entities, 0, "a deserted hireling is swept from the level")
|
||||
ok(contains(line, "deserts"), "desertion is logged distinctly from a mere grumble")
|
||||
end
|
||||
|
||||
function tests.mercenaryPayWageIsANoOpWithNoHirelingOnTheLevel()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
eq(Mercenaries.payWage(game), nil, "paying a wage with no hireling present does nothing and reports nothing")
|
||||
end
|
||||
|
||||
function tests.mercenaryDismissReleasesTheHirelingWithNoPenalty()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
Mercenaries.hire(game, "medic")
|
||||
local name = Mercenaries.dismiss(game)
|
||||
eq(name, "field medic", "dismissing returns the released hireling's name")
|
||||
eq(#game.level.entities, 0, "the dismissed hireling leaves the level")
|
||||
end
|
||||
|
||||
function tests.mercenaryDismissFailsWithNoOneUnderContract()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
eq(Mercenaries.dismiss(game), nil, "dismissing with nobody hired reports nothing")
|
||||
end
|
||||
|
||||
function tests.useItemOnAContractHiresTheNamedMercenaryAndConsumesIt()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
game.player.inventory = { Items.contract("skirmisher") }
|
||||
ok(game:useItem(1), "using a contract succeeds")
|
||||
eq(#game.player.inventory, 0, "the contract leaves the inventory once the hireling joins")
|
||||
eq(#game.level.entities, 1, "the hired mercenary is added to the level")
|
||||
eq(game.level.entities[1].species, "skirmisher", "the hired mercenary matches the contract's roster key")
|
||||
end
|
||||
|
||||
function tests.useItemOnAContractWithAnUnknownRosterKeyFailsWithoutConsumingIt()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
game.player.inventory = { Items.contract("dragon") }
|
||||
eq(game:useItem(1), false, "using a contract for an unknown roster key fails")
|
||||
eq(#game.player.inventory, 1, "a failed hire never consumes the contract")
|
||||
end
|
||||
|
||||
function tests.takeMonsterTurnRoutesAHirelingThroughMercenariesAct()
|
||||
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
|
||||
game:takeMonsterTurn(sellsword)
|
||||
ok(rat.hp <= 0, "the full monster-turn pipeline dispatches a hireling's turn to mercenaries.act")
|
||||
end
|
||||
|
||||
function tests.aFallenHirelingIsLoggedAndSweptWithoutAwardingXp()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
sellsword.hp = 1
|
||||
Status.apply(sellsword, Status.POISON, 1, 5)
|
||||
local xpBefore = game.player.xp
|
||||
game:takeMonsterTurn(sellsword)
|
||||
eq(#game.level.entities, 0, "a hireling that dies to its own status tick is swept from the level")
|
||||
eq(game.player.xp, xpBefore, "a fallen hireling grants the player no xp")
|
||||
ok(contains(game.log:last(), "falls"), "a fallen hireling's death is logged")
|
||||
end
|
||||
|
||||
function tests.gameEndPlayerTurnPaysTheHirelingWageOnceTheIntervalElapses()
|
||||
local game = Game.new(5)
|
||||
local sellsword = Mercenaries.hire(game, "sellsword")
|
||||
ok(sellsword ~= nil, "the starting room around a fresh game has a free cell to hire into")
|
||||
game.player.gold = 1000
|
||||
game.turn = Mercenaries.WAGE_INTERVAL - 1
|
||||
local goldBefore = game.player.gold
|
||||
game:endPlayerTurn()
|
||||
eq(game.player.gold, goldBefore - sellsword.wage, "the wage interval turn bills the live hireling")
|
||||
end
|
||||
|
||||
function tests.mercenaryFieldsSurviveSerialization()
|
||||
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||
local medic = Mercenaries.hire(game, "medic")
|
||||
medic.loyalty = 55
|
||||
local restoredLevel = Level.fromData(game.level:toData())
|
||||
eq(#restoredLevel.entities, 1, "the hireling survives a level round trip")
|
||||
eq(restoredLevel.entities[1].kind, "hireling", "the hireling's kind survives a round trip")
|
||||
eq(restoredLevel.entities[1].species, "medic", "the hireling's roster key survives a round trip")
|
||||
eq(restoredLevel.entities[1].wage, Mercenaries.byKey.medic.wage, "the hireling's wage survives a round trip")
|
||||
eq(restoredLevel.entities[1].loyalty, 55, "the hireling's loyalty survives a round trip")
|
||||
end
|
||||
|
||||
function tests.shopPriceOfAContractMatchesItsRosterHireCost()
|
||||
eq(Shop.priceOf(Items.contract("sellsword")), Mercenaries.byKey.sellsword.hireCost,
|
||||
"a contract's catalog price matches its roster hire cost")
|
||||
end
|
||||
|
||||
function tests.shopStockCanRollAContractWithAPositiveCatalogPrice()
|
||||
local sawContract = false
|
||||
for seed = 1, 40 do
|
||||
for _, item in ipairs(Shop.stock(Rng.new(seed))) do
|
||||
end
|
||||
|
||||
local order = {
|
||||
|
||||
Reference in new issue
Block a user