diff --git a/tests/run.lua b/tests/run.lua index f9938ef..24692ec 100644 --- a/tests/run.lua +++ b/tests/run.lua @@ -13452,6 +13452,179 @@ function tests.examineMonsterListsActiveStatusEffectsWithRemainingDuration() "the report lists the active status and its remaining duration") end +-- Only reachable through infighting.lua's own bite chance (a rival monster +-- or the player's own companion/hireling/thrall can now catch a disease +-- from a carrier's bite, see infightingStrikeCanTransmitTheAttackersBite +-- DiseaseToAnyDefenderNotJustThePlayer above) - shown the same way the +-- afflicted-statuses line just above already surfaces a fact invisible on +-- the bare map glyph. +function tests.examineMonsterReportsADiseaseCaughtThroughInfighting() + local game = stagedGame(1, arena(21, 11), 5, 5) + local orc = Monsters.create(Monsters.byKey.orc, 1, 8, 5, 1) + ok(not contains(Examine.monster(game.player, orc), "diseased"), "a healthy monster's report mentions no disease") + Disease.infect(orc, Disease.PLAGUE) + ok(contains(Examine.monster(game.player, orc), "diseased: black plague (incubating)"), + "an incubating infection shows up in a monster's examine report") + orc.disease.stage = 1 + ok(contains(Examine.monster(game.player, orc), "diseased: black plague (stage 1)"), + "an erupted infection reports its current stage in a monster's examine report") +end + +-- commands.lua: `examine `/`examine monster `, the interface half of +-- examine.lua a typed session reaches through. + +function tests.commandsApplyExamineDescribesAnInventoryItemWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + game.player.inventory = { Items.weapon("dagger", 3) } + local turnBefore = game.turn + local consumesTurn, message = Commands.apply(game, "examine 1") + eq(consumesTurn, false, "examine never spends a turn") + eq(game.turn, turnBefore, "the turn counter stays put") + ok(contains(message, "dagger") and contains(message, "power 3"), "the report describes the inventory item") + local aliased = Commands.apply(game, "x 1") + eq(aliased, false, "the x alias behaves identically and never spends a turn") +end + +function tests.commandsApplyExamineMonsterDescribesTheSameEntryLookJustNumbered() + local game = stagedGame(1, arena(21, 11), 5, 5) + local rat = Monsters.create(Monsters.byKey.rat, 1, 8, 5, 1) + local orc = Monsters.create(Monsters.byKey.orc, 2, 9, 6, 1) + game.level.entities = { rat, orc } + local _, lookMessage = Commands.apply(game, "look") + ok(contains(lookMessage, "1) " .. rat.name), "look numbers the rat first") + ok(contains(lookMessage, "2) " .. orc.name), "look numbers the orc second") + local consumesTurn, examineMessage = Commands.apply(game, "examine monster 2") + eq(consumesTurn, false, "examining a monster never spends a turn") + ok(contains(examineMessage, orc.name), "examine monster 2 describes the same monster look numbered 2") +end + +function tests.commandsApplyExamineFailsCleanlyOnBadOrMissingArgumentsWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + eq(Commands.apply(game, "examine"), false, "examine with no argument spends no turn") + eq(Commands.apply(game, "examine 1"), false, "examining an empty inventory slot spends no turn") + eq(Commands.apply(game, "examine monster"), false, "examine monster with no index spends no turn") + eq(Commands.apply(game, "examine monster 1"), false, "examining with nothing in sight spends no turn") +end + +function tests.commandsApplyHelpMentionsExamine() + local game = stagedGame(1, arena(21, 11), 5, 5) + local _, message = Commands.apply(game, "help") + ok(contains(message, "examine"), "help mentions examine") +end + +-- commands.lua: crafting/economy verbs (forge/merge/socket/socketrune/ +-- temper/enchant/disenchant/repair/steal/dismiss/learn/brew), the interface +-- half of the API a typed session could not reach before this session. + +function tests.commandsApplyForgeAndMergeDriveTheGemPipelineAndSpendTheTurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + game.player.essence = Crafting.FORGE_COST * 2 + ok(Commands.apply(game, "forge fire"), "forging a known element spends the turn") + ok(Commands.apply(game, "forge fire"), "forging a second matching gem spends the turn") + local a, b + for i, item in ipairs(game.player.inventory) do + if item.kind == Items.GEM then + if not a then a = i else b = i end + end + end + local consumesTurn = Commands.apply(game, "merge " .. a .. " " .. b) + eq(consumesTurn, true, "merging two matching gems spends the turn") +end + +function tests.commandsApplyForgeRejectsAMissingOrUnknownElementWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + local missing, message1 = Commands.apply(game, "forge") + eq(missing, false, "forge with no element spends no turn") + ok(message1 ~= nil, "the failure names what is missing") + game.player.essence = Crafting.FORGE_COST + local unknown = Commands.apply(game, "forge lava") + eq(unknown, false, "an unrecognized element spends no turn") +end + +function tests.commandsApplyMergeRejectsAMissingOrMismatchedPairWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + local missing, message = Commands.apply(game, "merge") + eq(missing, false, "merge with no indices spends no turn") + ok(message ~= nil, "the failure names what is missing") + game.player.inventory = { Crafting.gem(Crafting.FIRE, 1), Crafting.gem(Crafting.FROST, 1) } + local mismatched = Commands.apply(game, "merge 1 2") + eq(mismatched, false, "mismatched gems spend no turn") + eq(#game.player.inventory, 2, "a rejected merge leaves both gems untouched") +end + +function tests.commandsApplySocketTemperEnchantDisenchantDriveTheEquippedWeaponAndSpendTheTurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + local weapon = Items.weapon("dagger", 2) + Items.equipWeapon(weapon, game.player) + game.player.inventory = { Crafting.gem(Crafting.FIRE, 1) } + + local socketed = Commands.apply(game, "socket weapon 1") + eq(socketed, true, "socketing a gem into the equipped weapon spends the turn") + eq(weapon.socket.element, Crafting.FIRE, "the socketed gem's element lands on the weapon") + + game.player.essence = Crafting.temperCost(weapon) + local tempered = Commands.apply(game, "temper weapon") + eq(tempered, true, "tempering the equipped weapon spends the turn") + + game.player.essence = Crafting.ENCHANT_COST + local enchanted = Commands.apply(game, "enchant weapon vicious") + eq(enchanted, true, "enchanting the equipped weapon spends the turn") + eq(weapon.affix.key, "vicious", "the chosen affix lands on the weapon") + + local disenchanted = Commands.apply(game, "disenchant weapon") + eq(disenchanted, true, "disenchanting the equipped weapon spends the turn") + eq(weapon.affix, nil, "the affix is gone once disenchanted") +end + +function tests.commandsApplySocketRuneCompletesARunewordAndSpendsTheTurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + Items.equipWeapon(Items.weapon("sword", 4), game.player) + game.player.inventory = { Runewords.rune("sol"), Runewords.rune("ith") } + ok(Commands.apply(game, "socketrune weapon 1"), "socketing the first rune spends the turn") + local consumesTurn, message = Commands.apply(game, "socketrune weapon 1") + eq(consumesTurn, true, "completing the runeword spends the turn") + ok(contains(game.log:last(), "Zenith"), "completing a runeword is announced by name") +end + +function tests.commandsApplyCraftingVerbsFailCleanlyWithBadTargetsWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + for _, verb in ipairs({ "socket", "socketrune", "temper", "enchant", "disenchant" }) do + local consumesTurn, message = Commands.apply(game, verb) + eq(consumesTurn, false, verb .. " with no target spends no turn") + ok(message ~= nil and #message > 0, verb .. " explains what it needs") + end + Items.equipWeapon(Items.weapon("dagger", 2), game.player) + local badSocket = Commands.apply(game, "socket weapon 1") + eq(badSocket, false, "socketing with an empty inventory slot spends no turn") +end + +function tests.commandsApplyRepairFixesTheEquippedWeaponAndSpendsTheTurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + Items.equipWeapon(Items.weapon("sword", 4), game.player) + game.player.weapon.durability = 0 + game.player.gold = 1000 + local consumesTurn = Commands.apply(game, "repair weapon") + eq(consumesTurn, true, "repairing an affordable broken weapon spends the turn") + eq(game.player.weapon.durability, Durability.MAX, "the weapon comes back to full durability") +end + +function tests.commandsApplyRepairRejectsAnUnknownSlotOrAnEmptyPurseWithoutSpendingATurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + local badSlot, message = Commands.apply(game, "repair shield") + eq(badSlot, false, "an unrecognized slot spends no turn") + eq(message, "repair what? weapon, armor or bow", "the failure names the valid slots") + Items.equipWeapon(Items.weapon("sword", 4), game.player) + game.player.weapon.durability = 0 + game.player.gold = 0 + local shortPurse = Commands.apply(game, "repair weapon") + eq(shortPurse, false, "an empty purse cannot repair, and spends no turn") +end + +function tests.commandsApplyStealTakesTheWardedWareUnderfootAndSpendsTheTurn() + local game = stagedGame(1, arena(21, 11), 5, 5) + local keeper = Monsters.create(Monsters.byKey.shopkeeper, 1, 10, 10, 4) +end + local order = { "rngDeterminism", "mapDeterminism", "tilesRegistry", "fovKnownScene", "losSightLine", "astarPathing", "combatResolution", "itemsAndEquipment", "affixes", "statusEffects",