test: cover tests.sellItemGrantsHalfPriceAndRemovesIt
This commit is contained in:
1 parent
e7b64c1e92
commit
f5f4e93cd3
1 file changed
+173
+173
@@ -2205,6 +2205,179 @@ function tests.passiveShopkeeperIgnoresTheAdjacentPlayer()
|
||||
eq(game.player.hp, hpBefore, "an un-robbed shopkeeper does not attack even when adjacent")
|
||||
end
|
||||
|
||||
function tests.sellItemGrantsHalfPriceAndRemovesIt()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 3)
|
||||
local index = #game.player.inventory
|
||||
local item = game.player.inventory[index]
|
||||
local expected = Shop.sellValue(item)
|
||||
local goldBefore = game.player.gold
|
||||
local value = game:sellItem(index)
|
||||
eq(value, expected, "selling returns the catalog half-price")
|
||||
eq(game.player.gold, goldBefore + expected, "the sale proceeds land in the wallet")
|
||||
eq(#game.player.inventory, index - 1, "the sold item leaves the inventory")
|
||||
end
|
||||
|
||||
function tests.builderOccasionallySeedsAShopWithMatchingWares()
|
||||
local level = Builder.build(4, Rng.new(1), {})
|
||||
local keeper
|
||||
for _, e in ipairs(level.entities) do
|
||||
if e.species == "shopkeeper" then keeper = e end
|
||||
end
|
||||
ok(keeper ~= nil, "this seed's depth-4 floor spawns a shopkeeper")
|
||||
local waresFound = 0
|
||||
for _, ground in ipairs(level.floorItems) do
|
||||
if ground.price then
|
||||
waresFound = waresFound + 1
|
||||
eq(ground.shopId, keeper.id, "every priced ware is tagged with its shopkeeper's id")
|
||||
ok(ground.price > 0, "every priced ware has a positive price")
|
||||
end
|
||||
end
|
||||
ok(waresFound >= 3, "the shop stocks at least three wares")
|
||||
end
|
||||
|
||||
function tests.shopAndLooseGoldStayOffTheShallowFloors()
|
||||
for depth = 1, 3 do
|
||||
local level = Builder.build(depth, Rng.new(1), {})
|
||||
for _, ground in ipairs(level.floorItems) do
|
||||
ok(ground.item.kind ~= Items.GOLD, "no loose gold appears above depth 4")
|
||||
ok(not ground.price, "no shop ware appears above depth 4")
|
||||
end
|
||||
for _, e in ipairs(level.entities) do
|
||||
ok(e.species ~= "shopkeeper", "no shopkeeper appears above depth 4")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function arenaDark(w, h)
|
||||
local map = Map.new(w, h)
|
||||
for y = 2, h - 1 do for x = 2, w - 1 do map:set(x, y, Map.FLOOR) end end
|
||||
return Level.new({
|
||||
depth = 1, generator = "arena", map = map,
|
||||
entities = {}, floorItems = {}, traps = {}, lights = {}, dark = true, explored = {},
|
||||
upX = 2, upY = 2, downX = w - 1, downY = h - 1,
|
||||
})
|
||||
end
|
||||
|
||||
function tests.classApplyGrantsFighterStatBonuses()
|
||||
local base = Game.new(9)
|
||||
local fighter = Game.new(9, { class = "fighter" })
|
||||
eq(fighter.player.class, "fighter", "the requested class is stamped onto the player")
|
||||
eq(fighter.player.maxHp, base.player.maxHp + 8, "fighter grants a flat max hp bonus")
|
||||
eq(fighter.player.hp, base.player.hp + 8, "the hp bonus also tops up current hp")
|
||||
eq(fighter.player.attack, base.player.attack + 2, "fighter grants a flat attack bonus")
|
||||
end
|
||||
|
||||
function tests.classApplyGrantsRogueGoldAndAttack()
|
||||
local base = Game.new(9)
|
||||
local rogue = Game.new(9, { class = "rogue" })
|
||||
eq(rogue.player.gold, base.player.gold + 15, "rogue starts with extra gold")
|
||||
eq(rogue.player.attack, base.player.attack + 1, "rogue grants a small attack bonus")
|
||||
end
|
||||
|
||||
function tests.classApplyGrantsMageManaAndAttack()
|
||||
local base = Game.new(9)
|
||||
local mage = Game.new(9, { class = "mage" })
|
||||
eq(mage.player.maxMana, base.player.maxMana + 8, "mage grants a flat mana bonus")
|
||||
eq(mage.player.mana, base.player.mana + 8, "the mana bonus also tops up current mana")
|
||||
eq(mage.player.attack, base.player.attack - 1, "mage trades some attack for its mana pool")
|
||||
end
|
||||
|
||||
function tests.classApplyGrantsRangerBowAndAmmo()
|
||||
local ranger = Game.new(9, { class = "ranger" })
|
||||
ok(ranger.player.bow ~= nil, "ranger starts with a bow equipped")
|
||||
local hasAmmo = false
|
||||
for _, item in ipairs(ranger.player.inventory) do
|
||||
if item.kind == Items.AMMO then hasAmmo = true end
|
||||
end
|
||||
ok(hasAmmo, "ranger starts with a stack of arrows")
|
||||
end
|
||||
|
||||
function tests.invalidClassIsIgnored()
|
||||
local base = Game.new(9)
|
||||
local weird = Game.new(9, { class = "wizard" })
|
||||
eq(weird.player.class, nil, "an unrecognised class name is not applied")
|
||||
eq(weird.player.maxHp, base.player.maxHp, "stats are unaffected by an invalid class")
|
||||
end
|
||||
|
||||
function tests.skillLearnRespectsClassLevelAndPoints()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.class = "fighter"
|
||||
game.player.skillPoints = 0
|
||||
eq(game:learnSkill("cleave"), false, "learning fails without a banked skill point")
|
||||
game.player.skillPoints = 1
|
||||
eq(game:learnSkill("backstab"), false, "learning a skill from another class fails")
|
||||
eq(game.player.skillPoints, 1, "a failed learn spends no point")
|
||||
eq(game:learnSkill("berserk"), false, "learning fails below the skill's level requirement")
|
||||
eq(game:learnSkill("cleave"), true, "a same-class, level-eligible skill with a point banked learns")
|
||||
eq(game.player.skillPoints, 0, "learning spends the point")
|
||||
eq(Skills.has(game.player, "cleave"), true, "the skill is now known")
|
||||
game.player.skillPoints = 1
|
||||
eq(game:learnSkill("cleave"), false, "an already-known skill cannot be learned twice")
|
||||
eq(game.player.skillPoints, 1, "re-learning a known skill spends no point")
|
||||
end
|
||||
|
||||
function tests.skillCooldownBlocksImmediateReuseAndTicksDown()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.class = "fighter"
|
||||
game.player.skills.secondWind = true
|
||||
game.player.hp = game.player.maxHp - 10
|
||||
eq(game:useSkill("secondWind"), true, "second wind activates while off cooldown")
|
||||
eq(game:useSkill("secondWind"), false, "an immediate reuse fails while on cooldown")
|
||||
for _ = 1, Skills.active.secondWind.cooldown do
|
||||
game:endPlayerTurn()
|
||||
end
|
||||
eq(game.player.skillCooldowns.secondWind, 0, "the cooldown ticks down to zero over that many player turns")
|
||||
game.player.hp = game.player.maxHp - 10
|
||||
eq(game:useSkill("secondWind"), true, "second wind is usable again once its cooldown fully elapses")
|
||||
end
|
||||
|
||||
function tests.fighterCleaveHitsPrimaryAndAdjacentMonsters()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.class = "fighter"
|
||||
game.player.skills.cleave = true
|
||||
local primary = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
|
||||
local splash = Monsters.create(Monsters.byKey.orc, 2, 3, 5, 1)
|
||||
game.level.entities = { primary, splash }
|
||||
local success = game:useSkill("cleave", primary)
|
||||
eq(success, true, "cleave lands on an adjacent primary target")
|
||||
ok(primary.hp < primary.maxHp, "the primary target takes damage")
|
||||
ok(splash.hp < splash.maxHp, "a second monster adjacent to the player also takes splash damage")
|
||||
ok((splash.maxHp - splash.hp) < (primary.maxHp - primary.hp), "the splash hit deals less than the primary hit")
|
||||
eq(game.player.skillCooldowns.cleave, Skills.active.cleave.cooldown, "cleave starts its own cooldown")
|
||||
end
|
||||
|
||||
-- Cleave's splash targets must be mitigated by the wielder's own weapon
|
||||
-- damage type exactly like the primary target already is through
|
||||
-- Combat.damage/Combat.attack (see combat.lua's damageTypeOf and affixes.lua's
|
||||
-- own comment that a flaming sword "can do nothing at all against something
|
||||
-- fire-immune") - before this test the splash loop subtracted a raw
|
||||
-- attack-minus-defense number straight off hp with no Resistances.apply call
|
||||
-- at all, so a flaming weapon's cleave splash burned through fire immunity
|
||||
-- that the very same swing's primary hit already respected.
|
||||
function tests.fighterCleaveSplashRespectsAFireImmuneTargetLikeThePrimaryHitAlreadyDoes()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.class = "fighter"
|
||||
game.player.skills.cleave = true
|
||||
game.player.weapon = Affixes.decorate(Items.weapon("dagger", 2), Affixes.weapon[3])
|
||||
eq(game.player.weapon.damageType, "fire", "the flaming affix retags the dagger's swing as fire")
|
||||
local primary = Monsters.create(Monsters.byKey.orc, 1, 4, 6, 1)
|
||||
local immune = Monsters.create(Monsters.byKey.dragon, 2, 3, 5, 6)
|
||||
game.level.entities = { primary, immune }
|
||||
game:useSkill("cleave", primary)
|
||||
ok(primary.hp < primary.maxHp, "the primary target still burns for full mitigated damage")
|
||||
eq(immune.hp, immune.maxHp, "a fire-immune splash target takes zero cleave splash from a flaming weapon")
|
||||
end
|
||||
|
||||
function tests.fighterSecondWindRestoresPortionOfMaxHp()
|
||||
local game = stagedGame(1, arena(21, 11), 3, 6)
|
||||
game.player.class = "fighter"
|
||||
game.player.skills.secondWind = true
|
||||
game.player.hp = 5
|
||||
game:useSkill("secondWind")
|
||||
eq(game.player.hp, 5 + math.floor(game.player.maxHp * 0.3), "second wind restores three tenths of max hp")
|
||||
|
||||
game.player.skillCooldowns.secondWind = 0
|
||||
game.player.hp = game.player.maxHp - 1
|
||||
end
|
||||
|
||||
local order = {
|
||||
|
||||
Reference in new issue
Block a user