From 4e4d22cf2edbcb2ea787f3adc8147269221aedf2 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 21:29:54 +0200 Subject: [PATCH] feat: add targetArg to commands --- src/commands.lua | 155 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/src/commands.lua b/src/commands.lua index 7a8b375..6975164 100644 --- a/src/commands.lua +++ b/src/commands.lua @@ -322,3 +322,158 @@ local function hereHandler(methodName, nothingMessage) end end +Handlers.open = hereHandler("openChest", "there is nothing here to open") +Handlers.butcher = hereHandler("butcherCorpse", "there is nothing here to butcher") +Handlers.raise = hereHandler("raiseCorpse", "there is nothing here to raise") +Handlers.scavenge = hereHandler("scavengeCorpse", "there is nothing here to scavenge") +Handlers.harvest = hereHandler("harvestNode", "there is nothing here to harvest") +Handlers.ransom = hereHandler("ransomCaptive", "there is nothing here to ransom") +Handlers.sellcaptive = hereHandler("sellCaptive", "there is nothing here to sell") +Handlers.interrogate = hereHandler("interrogateCaptive", "there is nothing here to interrogate") +Handlers.descend = hereHandler("descend", "there are no stairs down here") +Handlers[">"] = Handlers.descend +Handlers.ascend = hereHandler("ascend", "there are no stairs up here") +Handlers["<"] = Handlers.ascend +Handlers.enterrift = hereHandler("enterRift", "there is no rift here to cross") +Handlers.exitrift = hereHandler("exitRift", "you are not standing across a rift") + +-- Game:startHorde is a deliberate, one-way trial (see its own header: the +-- dungeon floor left behind is simply abandoned, there is no exit back) that +-- always succeeds and, unlike every hereHandler above, carries no location +-- requirement of its own - so this guards against the one real footgun a +-- typed command adds that the method itself does not: retyping "horde" +-- mid-run would otherwise silently discard the current wave and its +-- progress by building a brand new arena on top of it. +Handlers.horde = function(game) + if game.horde then return false, "you are already fighting in the arena" end + game:startHorde() + return true, nil +end + +Handlers.pray = function(game) + local shrine = game.level:shrineAt(game.player.x, game.player.y) + if not shrine then return false, "there is no shrine here" end + if game:prayAt(shrine) then return true, nil end + return false, "your prayer goes unanswered" +end + +Handlers.quest = function(game) + local board = game.level:questBoardAt(game.player.x, game.player.y) + if not board then return false, "there is no quest board here" end + if game:tryAcceptQuest(board) then return true, nil end + return false, "you cannot take up that bounty" +end + +-- Every faction key factions.lua defines, spelled out here (rather than +-- discovered from the module at runtime) purely so a bad argument's error +-- message can list them by name; the set itself is built from the same +-- constants Game:appeaseFaction already trusts, so a typo can never buy +-- standing with a faction that does not exist. +local FACTION_NAMES = { + Factions.MERCHANT, Factions.GOBLINOID, Factions.VERMIN, Factions.ORCISH, + Factions.UNDEAD, Factions.BEAST, Factions.CULT, Factions.DRAKE, Factions.BANDIT, +} +local FACTION_SET = {} +for _, name in ipairs(FACTION_NAMES) do FACTION_SET[name] = true end + +Handlers.appease = function(game, args) + local faction = args[1] + if not faction or not FACTION_SET[faction] then + return false, "appease which faction? " .. table.concat(FACTION_NAMES, ", ") + end + if game:appeaseFaction(faction) then return true, nil end + return false, nil +end + +Handlers.dig = function(game, args) + local dir = DIRS[args[1]] + if not dir then return false, "dig which direction?" end + if game:dig(dir[1], dir[2]) then return true, nil end + return false, "you cannot dig there" +end + +-- Disarms a detected-but-unsprung trap one step away in `dir` - the same +-- Chebyshev-1 reach Disarm.sweep already uses to notice it in the first +-- place (see Game:endPlayerTurn's "you notice a hidden trap nearby"). The +-- validity check below duplicates Game:disarmTrap's own guard on purpose: +-- unlike every other handler here, a disarm attempt against a real trap +-- must still spend the turn even when Disarm.attempt fails and springs it, +-- but Game:disarmTrap's own boolean return conflates that spring with "no +-- trap here" into the same false - so this handler tells the two apart +-- itself instead of trusting the method's return value for turn cost. +Handlers.disarm = function(game, args) + local dir = DIRS[args[1]] + if not dir then return false, "disarm which direction?" end + local trap = game.level:trapAt(game.player.x + dir[1], game.player.y + dir[2]) + if not trap or not trap.detected or trap.revealed then + return false, "there is nothing here you have noticed to disarm" + end + game:disarmTrap(trap) + return true, nil +end + +-- Crafting/economy verbs (game.lua's forgeGem/mergeGemsAt/socketItem/ +-- socketRune/temperItem/enchantItem/disenchantItem/repairEquipped/ +-- stealFromShop/dismissHireling/learnSkill/brewAt), the other half of the +-- API choosePlayerAction can already reach that a typed session could not +-- until now. Every one of these Game methods already logs its own success +-- or failure reason, so a handler only needs its own message for an +-- argument so malformed the underlying method can never even be called. + +-- Shared by socket/socketrune/temper/enchant/disenchant: their `target` +-- resolves through Game:resolveGear exactly like this, either the literal +-- word for currently equipped gear or an inventory slot number for +-- anything else (a spare ring, an unequipped bow). +local function targetArg(token) + if token == "weapon" or token == "armor" then return token end + return tonumber(token) +end + +Handlers.forge = function(game, args) + local element = args[1] + if not element then return false, "forge which element? fire, frost, shock or venom" end + if game:forgeGem(element) then return true, nil end + return false, nil +end + +Handlers.merge = function(game, args) + local a, b = tonumber(args[1]), tonumber(args[2]) + if not a or not b then return false, "merge which two gems? give two inventory numbers" end + if game:mergeGemsAt(a, b) then return true, nil end + return false, "those cannot be merged" +end + +Handlers.socket = function(game, args) + local target, gemIndex = targetArg(args[1]), tonumber(args[2]) + if not target or not gemIndex then + return false, "socket what into what? give a target (weapon/armor/item number) and a gem number" + end + if game:socketItem(target, gemIndex) then return true, nil end + return false, "that cannot be socketed" +end + +Handlers.socketrune = function(game, args) + local target, runeIndex = targetArg(args[1]), tonumber(args[2]) + if not target or not runeIndex then + return false, "socket which rune into what? give a target (weapon/armor/item number) and a rune number" + end + if game:socketRune(target, runeIndex) then return true, nil end + return false, "that cannot be socketed" +end + +Handlers.temper = function(game, args) + local target = targetArg(args[1]) + if not target then return false, "temper what? weapon, armor or an item number" end + if game:temperItem(target) then return true, nil end + return false, "that cannot be tempered" +end + +Handlers.enchant = function(game, args) + local target, affixKey = targetArg(args[1]), args[2] + if not target or not affixKey then + return false, "enchant what with what? give a target (weapon/armor/item number) and an affix name" + end + if game:enchantItem(target, affixKey) then return true, nil end + return false, "that cannot be enchanted" +end +