test: cover tests.corpsesButcheringFreshMeatAddsFoodToInventoryAndCon...
This commit is contained in:
1 parent
183e67da75
commit
c58888f5c0
1 file changed
+173
+173
@@ -3243,6 +3243,179 @@ function tests.corpsesGoesRottenPastItsThresholdButStaysButcherableUntilThen()
|
|||||||
corpse.decay = Corpses.ROT_TURNS
|
corpse.decay = Corpses.ROT_TURNS
|
||||||
ok(Corpses.isRotten(corpse), "a corpse right at its rot threshold counts as rotten")
|
ok(Corpses.isRotten(corpse), "a corpse right at its rot threshold counts as rotten")
|
||||||
corpse.decay = Corpses.ROT_TURNS + 1
|
corpse.decay = Corpses.ROT_TURNS + 1
|
||||||
|
ok(not Corpses.isRotten(corpse), "a corpse one round above the threshold is still good")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.corpsesButcheringFreshMeatAddsFoodToInventoryAndConsumesTheCorpse()
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
local level = { corpses = {} }
|
||||||
|
Corpses.spawn(level, rat)
|
||||||
|
local corpse = level.corpses[1]
|
||||||
|
local game = { player = corpsePlayer(), level = level }
|
||||||
|
local line = Corpses.butcher(game, corpse)
|
||||||
|
eq(#level.corpses, 0, "butchering removes the corpse from the level")
|
||||||
|
eq(#game.player.inventory, 1, "butchering fresh meat adds a food item to the inventory")
|
||||||
|
eq(game.player.inventory[1].kind, Items.FOOD, "the yield is a food item")
|
||||||
|
eq(game.player.inventory[1].nutrition, corpse.nutrition, "the food item carries the corpse's own nutrition value")
|
||||||
|
ok(contains(line, "carve"), "a fresh butchering is logged as carving meat")
|
||||||
|
ok(#game.player.statuses == 0, "fresh meat never poisons the player")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.corpsesButcheringRottenMeatPoisonsThePlayerInsteadAndStillConsumesTheCorpse()
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
local level = { corpses = {} }
|
||||||
|
Corpses.spawn(level, rat)
|
||||||
|
local corpse = level.corpses[1]
|
||||||
|
corpse.decay = Corpses.ROT_TURNS
|
||||||
|
local game = { player = corpsePlayer(), level = level }
|
||||||
|
local line = Corpses.butcher(game, corpse)
|
||||||
|
eq(#level.corpses, 0, "butchering removes a rotten corpse too")
|
||||||
|
eq(#game.player.inventory, 0, "rotten meat yields no food item")
|
||||||
|
ok(Status.has(game.player, Status.POISON), "eating rotten meat poisons the player")
|
||||||
|
ok(contains(line, "rotted"), "a rotten butchering is logged as such")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.corpseFieldsSurviveSerialization()
|
||||||
|
local level = arena(21, 11)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
Corpses.spawn(level, rat)
|
||||||
|
level.corpses[1].x, level.corpses[1].y = 4, 4
|
||||||
|
local restored = Level.fromData(level:toData())
|
||||||
|
eq(#restored.corpses, 1, "the corpse list survives a round trip")
|
||||||
|
eq(restored.corpses[1].species, "rat", "a corpse's species survives a round trip")
|
||||||
|
eq(restored.corpses[1].nutrition, level.corpses[1].nutrition, "a corpse's nutrition survives a round trip")
|
||||||
|
eq(restored.corpses[1].decay, level.corpses[1].decay, "a corpse's decay survives a round trip")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.corpseRendersItsGlyphWhenVisible()
|
||||||
|
local rendered = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 6, 1)
|
||||||
|
Corpses.spawn(rendered.level, rat)
|
||||||
|
rendered:computeVisible()
|
||||||
|
ok(contains(rendered:render(), "%"), "a corpse on a visible cell renders as its glyph")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameAwardKillLeavesACorpseForAWildMonsterButNotForUndeadOrMimic()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 4, 6, 1)
|
||||||
|
rat.hp = 0
|
||||||
|
game.level.entities = { rat }
|
||||||
|
game:awardKill(rat)
|
||||||
|
eq(#game.level.corpses, 1, "killing a wild monster through awardKill leaves a corpse")
|
||||||
|
eq(game.level.corpses[1].x, 4, "the corpse sits exactly where the monster fell")
|
||||||
|
|
||||||
|
local zombie = Monsters.create(Monsters.byKey.zombie, 2, 4, 7, 4)
|
||||||
|
zombie.hp = 0
|
||||||
|
game.level.entities = { zombie }
|
||||||
|
game:awardKill(zombie)
|
||||||
|
eq(#game.level.corpses, 1, "killing an undead monster through awardKill leaves no new corpse")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameButcherCorpseAtFreshMeatFeedsThePlayerAndClearsTheCorpse()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
Corpses.spawn(game.level, rat)
|
||||||
|
local before = #game.player.inventory
|
||||||
|
ok(game:butcherCorpse(), "butchering the corpse underfoot succeeds")
|
||||||
|
eq(#game.level.corpses, 0, "the corpse is gone after butchering")
|
||||||
|
eq(#game.player.inventory, before + 1, "the player's inventory gains the carved meat")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.gameButcherCorpseFailsWithoutACorpseUnderfoot()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
ok(not game:butcherCorpse(), "butchering empty ground fails")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.autopilotButchersAFreshCorpseWhenIdleAndStandingOnIt()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
Corpses.spawn(game.level, rat)
|
||||||
|
local before = #game.player.inventory
|
||||||
|
game:choosePlayerAction()
|
||||||
|
eq(#game.level.corpses, 0, "the autopilot butchers a fresh corpse it stands on")
|
||||||
|
eq(#game.player.inventory, before + 1, "the autopilot's butchering actually yields food")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.autopilotLeavesARottenCorpseAloneRatherThanRiskingPoison()
|
||||||
|
local game = stagedGame(1, arena(21, 11), 5, 5)
|
||||||
|
local rat = Monsters.create(Monsters.byKey.rat, 1, 5, 5, 1)
|
||||||
|
Corpses.spawn(game.level, rat)
|
||||||
|
game.level.corpses[1].decay = Corpses.ROT_TURNS
|
||||||
|
game:choosePlayerAction()
|
||||||
|
eq(#game.level.corpses, 1, "the autopilot never touches a rotten corpse")
|
||||||
|
ok(not Status.has(game.player, Status.POISON), "the autopilot never risks poisoning itself on rotten meat")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerCurrentDefaultsToCapForAFreshEntity()
|
||||||
|
eq(Hunger.current({}), Hunger.CAP, "an entity with no satiation field reads as fully fed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerTierOfClassifiesEachBandAtItsBoundaries()
|
||||||
|
eq(Hunger.tierOf({ satiation = Hunger.CAP }), Hunger.FED, "a full meter is fed")
|
||||||
|
eq(Hunger.tierOf({ satiation = Hunger.HUNGRY_MAX + 1 }), Hunger.FED, "just above the hungry ceiling is still fed")
|
||||||
|
eq(Hunger.tierOf({ satiation = Hunger.HUNGRY_MAX }), Hunger.HUNGRY, "the hungry ceiling itself reads as hungry")
|
||||||
|
eq(Hunger.tierOf({ satiation = Hunger.WEAK_MAX + 1 }), Hunger.HUNGRY, "just above the weak ceiling is still hungry")
|
||||||
|
eq(Hunger.tierOf({ satiation = Hunger.WEAK_MAX }), Hunger.WEAK, "the weak ceiling itself reads as weak")
|
||||||
|
eq(Hunger.tierOf({ satiation = 1 }), Hunger.WEAK, "one point above zero is still weak, never starving")
|
||||||
|
eq(Hunger.tierOf({ satiation = 0 }), Hunger.STARVING, "zero itself is starving")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerAttackAndDefensePenaltiesEscalateByTier()
|
||||||
|
eq(Hunger.attackPenalty({ satiation = Hunger.CAP }), 0, "a fed body carries no attack penalty")
|
||||||
|
eq(Hunger.attackPenalty({ satiation = Hunger.HUNGRY_MAX }), 0, "merely hungry still carries no attack penalty")
|
||||||
|
eq(Hunger.attackPenalty({ satiation = Hunger.WEAK_MAX }), -2, "weak costs two points of attack")
|
||||||
|
eq(Hunger.attackPenalty({ satiation = 0 }), -4, "starving costs four points of attack")
|
||||||
|
eq(Hunger.defensePenalty({ satiation = Hunger.CAP }), 0, "a fed body carries no defense penalty")
|
||||||
|
eq(Hunger.defensePenalty({ satiation = Hunger.HUNGRY_MAX }), 0, "merely hungry still carries no defense penalty")
|
||||||
|
eq(Hunger.defensePenalty({ satiation = Hunger.WEAK_MAX }), -1, "weak costs one point of defense")
|
||||||
|
eq(Hunger.defensePenalty({ satiation = 0 }), -3, "starving costs three points of defense")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.entityEffectiveAttackAndDefenseFoldInTheHungerPenalty()
|
||||||
|
local entity = Entity.new({ x = 1, y = 1, glyph = "@", name = "you", kind = "player", hp = 20, attack = 6, defense = 4 })
|
||||||
|
eq(entity:effectiveAttack(), 6, "a fresh entity with no satiation field carries no penalty")
|
||||||
|
entity.satiation = 0
|
||||||
|
eq(entity:effectiveAttack(), 2, "a starving body's attack penalty is live in effectiveAttack")
|
||||||
|
eq(entity:effectiveDefense(), 1, "a starving body's defense penalty is live in effectiveDefense")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerSuppressesRegenOnlyAtWeakOrWorse()
|
||||||
|
ok(not Hunger.suppressesRegen({ satiation = Hunger.HUNGRY_MAX }), "merely hungry never blocks passive regen")
|
||||||
|
ok(Hunger.suppressesRegen({ satiation = Hunger.WEAK_MAX }), "weak blocks passive regen")
|
||||||
|
ok(Hunger.suppressesRegen({ satiation = 0 }), "starving blocks passive regen")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerTickDrainsByOneAndLogsOnlyOnATierCrossing()
|
||||||
|
local entity = { satiation = Hunger.HUNGRY_MAX + 2, hp = 20 }
|
||||||
|
eq(Hunger.tick(entity), nil, "a drain turn that stays within its tier reports no line")
|
||||||
|
eq(entity.satiation, Hunger.HUNGRY_MAX + 1, "the drain was still deducted even without a tier crossing")
|
||||||
|
eq(Hunger.tick(entity), "you feel hungry", "crossing down into hungry is announced")
|
||||||
|
eq(entity.satiation, Hunger.HUNGRY_MAX, "the meter keeps draining turn over turn")
|
||||||
|
eq(entity.hp, 20, "hunger alone never costs hp above starving")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerTickBitesForOneHpEveryRoundWhileStarving()
|
||||||
|
local entity = { satiation = 1, hp = 10 }
|
||||||
|
eq(Hunger.tick(entity), "you are starving", "crossing down into starving is announced")
|
||||||
|
eq(entity.satiation, 0, "starving is a floor, not a negative value")
|
||||||
|
eq(entity.hp, 9, "reaching starving costs a point of hp immediately")
|
||||||
|
eq(Hunger.tick(entity), "you are starving", "starving keeps announcing and biting every round it holds")
|
||||||
|
eq(entity.hp, 8, "a second starving round costs a second point of hp")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerEatCapsAtTheCeilingAndWastesAnOverfullMealIntoBloat()
|
||||||
|
local entity = { satiation = Hunger.CAP, statuses = {} }
|
||||||
|
local bloated = Hunger.eat(entity, 400)
|
||||||
|
ok(bloated, "eating while already at the cap reports the meal as wasted")
|
||||||
|
eq(entity.satiation, Hunger.CAP, "an overfull meal never lifts satiation past the cap")
|
||||||
|
ok(Status.has(entity, Status.SLOW), "an overfull meal leaves the diner slowed")
|
||||||
|
end
|
||||||
|
|
||||||
|
function tests.hungerEatBelowTheCeilingBanksNutritionWithNoWaste()
|
||||||
|
local entity = { satiation = Hunger.HUNGRY_MAX, statuses = {} }
|
||||||
|
local bloated = Hunger.eat(entity, 100)
|
||||||
|
ok(not bloated, "an ordinary meal below the cap is never wasted")
|
||||||
|
eq(entity.satiation, Hunger.HUNGRY_MAX + 100, "the full nutrition value is banked")
|
||||||
end
|
end
|
||||||
|
|
||||||
local order = {
|
local order = {
|
||||||
|
|||||||
Reference in new issue
Block a user