diff --git a/src/commands.lua b/src/commands.lua index e492369..7a8b375 100644 --- a/src/commands.lua +++ b/src/commands.lua @@ -159,3 +159,166 @@ Handlers.attack = function(game, args) return false, "there is nothing to attack there" end +Handlers.wait = function() return true, "you wait" end +Handlers.z = Handlers.wait +Handlers["."] = Handlers.wait + +Handlers.pickup = function(game) + game:pickupHere() + return true, nil +end +Handlers.g = Handlers.pickup + +-- Game:autoExplore already returns the exact (consumesTurn, message) shape +-- every handler here does - it just may have spent several turns getting +-- there instead of one, stopping itself the moment a hostile comes into +-- sight or the player takes damage rather than walking on regardless. +Handlers.explore = function(game) + return game:autoExplore() +end + +-- Game:restUntilHealed shares the same shape and the same "may spend several +-- turns before returning" behaviour as Game:autoExplore just above. +Handlers.rest = function(game) + return game:restUntilHealed() +end + +-- Game:travelTo shares that same "may spend several turns" shape too. "down"/ +-- "up" are the level's own stairs (self.level.downX/Y, upX/Y) - always a +-- valid, already-walkable destination regardless of what the player has +-- explored yet, exactly like choosePlayerAction's own goal-seeking already +-- assumes; a raw x/y command is deliberately not offered since nothing in +-- this interface ever shows the player a raw coordinate to aim one at. +Handlers.travel = function(game, args) + if args[1] == "down" then return game:travelTo(game.level.downX, game.level.downY) end + if args[1] == "up" then return game:travelTo(game.level.upX, game.level.upY) end + return false, "travel where? down or up" +end + +-- Every handler below that takes an inventory index shares the same shape: +-- a missing or non-numeric first arg fails without spending a turn, exactly +-- like an unknown verb, rather than crashing into the underlying Game +-- method with a nil index. +local function indexArg(args) + return tonumber(args[1]) +end + +Handlers.use = function(game, args) + local index = indexArg(args) + if not index then return false, "use what? give an inventory number" end + if game:useItem(index) then return true, nil end + return false, "you cannot use that" +end +Handlers.u = Handlers.use + +Handlers.drop = function(game, args) + local index = indexArg(args) + if not index then return false, "drop what? give an inventory number" end + if game:dropItem(index) then return true, nil end + return false, "you have nothing there to drop" +end + +Handlers.sell = function(game, args) + local index = indexArg(args) + if not index then return false, "sell what? give an inventory number" end + if game:sellItem(index) then return true, nil end + return false, "that cannot be sold here" +end + +-- Trailing target number is always the argument slot right after whatever +-- an individual handler needed for itself (an inventory index, a spell/skill +-- key) - see resolveTarget above for exactly what "explicit" changes. +Handlers.throw = function(game, args) + local index = indexArg(args) + if not index then return false, "throw what? give an inventory number" end + local target, explicit = resolveTarget(game, args[2]) + if not target then + if explicit then return false, "no monster in sight at that number" end + return false, "there is nothing in sight to throw at" + end + if game:throwAt(index, target) then return true, nil end + return false, "you cannot throw that" +end + +Handlers.capture = function(game, args) + local index = indexArg(args) + if not index then return false, "capture with what? give an inventory number" end + local target, explicit = resolveTarget(game, args[2]) + if not target then + if explicit then return false, "no monster in sight at that number" end + return false, "there is nothing in sight to capture" + end + if game:captureAt(index, target) then return true, nil end + return false, "the capture attempt fails" +end + +Handlers.fire = function(game, args) + local target, explicit = resolveTarget(game, args[1]) + if not target then + if explicit then return false, "no monster in sight at that number" end + return false, "there is nothing in sight to fire at" + end + if game:fireAt(target) then return true, nil end + return false, "you cannot fire right now" +end + +Handlers.cast = function(game, args) + local key = args[1] + if not key then return false, "cast what spell?" end + local def = Spells.catalog[key] + if not def then return false, "you know no spell by that name" end + local target = nil + if def.targeted then + local explicit + target, explicit = resolveTarget(game, args[2]) + if not target then + if explicit then return false, "no monster in sight at that number" end + return false, "there is nothing in sight to cast that at" + end + end + if game:castSpell(key, target) then return true, nil end + return false, "the spell fails" +end + +-- multishot's two targets are named together or not at all: giving only one +-- explicit number is ambiguous about which of the (up to) two shots it picks +-- out, so that case falls back to the same auto-pick-both-nearest behaviour +-- as giving none, rather than guessing which slot the player meant. +Handlers.useskill = function(game, args) + local key = resolveSkillKey(args[1]) + if not key then return false, "use which skill?" end + local success + if key == "cleave" then + local target, explicit = resolveTarget(game, args[2]) + if not target and explicit then return false, "no monster in sight at that number" end + success = game:useSkill(key, target) + elseif key == "multishot" then + if args[2] and args[3] then + local list = visibleHostilesInOrder(game) + local t1, t2 = list[tonumber(args[2]) or -1], list[tonumber(args[3]) or -1] + if not t1 or not t2 then return false, "no monster in sight at that number" end + success = game:useSkill(key, t1, t2) + else + local targets = nearestHostiles(game, 2) + success = game:useSkill(key, targets[1], targets[2]) + end + else + success = game:useSkill(key) + end + if success then return true, nil end + return false, "that skill fails" +end + +-- Zero-argument actions that always resolve against whatever stands on the +-- player's own tile (a chest, a corpse, a captive, a node, a stairwell...), +-- the same way every one of these Game methods already looks itself up by +-- self.player.x/y rather than taking a target. `nothingMessage` only ever +-- surfaces when the underlying method itself failed silently (no log line +-- of its own) - most branches already explain themselves through game.log. +local function hereHandler(methodName, nothingMessage) + return function(game) + if game[methodName](game) then return true, nil end + return false, nothingMessage + end +end +