hotbar: say what the key you pressed just did

Pressing a hotbar key ran hotkey.action(state), which for an equipment
binding calls an itemActions entry's onActivation directly -- Wield,
Drink, Switch on, Pack away. None of those reach console.print or a
popup; most only play a sound. So the whole hotbar was silent: no item
name, no verb, no confirmation the key had registered at all.

patches/hotbar.lua wraps the hotbar VIEW factory, not models/hotkey:
models/hotbar.lua captures newHotkey as a file-local at require time,
and models/world.lua captures newHotbar the same way, both long before
our entry point. views/hotbar.lua is required only by the three states,
which load lazily -- the same ordering argument as patches/slider.lua.

The verb is computed BEFORE the action runs. The itemAction conditions
key off item.isEquipped, which the action itself flips, so asked
afterwards they return the opposite verb and a press that wielded would
announce "Unwield".

Also narrates the empty slot, the silent no-applicable-action branch,
binding and unbinding (sound-only in game), and action-type slots. Says
nothing when the item is missing from the inventory -- the game prints
that itself and patches/console.lua speaks it.

78 testbed checks, 0 failures. The prologue gives the player no items,
so the live path is exercised by planting a torch, binding it to slot 1,
pressing 1 twice and putting the world back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Accessibility Mod
2026-08-03 02:19:51 +02:00
co-authored by Claude Opus 5
parent 1f48dea550
commit 55b19dd537
4 changed files with 408 additions and 3 deletions
+104
View File
@@ -464,6 +464,110 @@ local function scenario()
end
end
-- The HOTBAR keys. patches/hotbar.lua has to work out which itemAction a
-- hotbar equipment command will resolve to *before* the action runs (the
-- item's isEquipped flips, and afterwards the conditions pick the opposite
-- verb). That mirrors a file-local table in models/hotkey.lua, so this is
-- its version-bump canary: every id must still exist and still have a
-- header, and a real item must still resolve to the expected verb.
do
local hotbarPatch = require("accessibility.patches.hotbar")
local itemActions = require("modules.item.itemActions")
local newItem = require("modules.item")
local missing = {}
for command, ids in pairs(hotbarPatch.actionIdsFor) do
for _, id in ipairs(ids) do
local action = itemActions[id]
if not (action and action.header) then
missing[#missing + 1] = command .. "/" .. id
end
end
end
check("every hotbar item action id still has a header",
#missing == 0, "missing: " .. table.concat(missing, ", "))
-- A torch is onWield/onUnequip, so equipUnequip must flip between the
-- two verbs with isEquipped -- exactly what the announcement says.
local torch = newItem{templateId = "torch", quantity = 1}
check("hotbar names the verb for an unequipped item",
hotbarPatch.headerFor(torch, "equipUnequip") == "Wield",
tostring(hotbarPatch.headerFor(torch, "equipUnequip")))
torch.isEquipped = true
check("hotbar names the opposite verb once equipped",
hotbarPatch.headerFor(torch, "equipUnequip") == "Unwield",
tostring(hotbarPatch.headerFor(torch, "equipUnequip")))
torch.isEquipped = false
local cannotDrink = hotbarPatch.headerFor(torch, "drink")
check("hotbar stays quiet for a command the item cannot do",
cannotDrink == nil, tostring(cannotDrink))
end
-- End to end: bind a real item to slot 1 and press it. The prologue gives
-- the player nothing, so plant a torch, bind it, press "1" twice (wield,
-- then unwield) and put everything back. This is the only check that
-- proves the whole chain -- patch applied, keybinding resolved, model
-- looked up, pack() read, verb chosen -- rather than one link of it.
do
local speech = require("accessibility.speech")
local player = require("models.player")
local world = require("models.world")
local spoken = {}
local realSay = speech.say
speech.say = function(text, ...)
spoken[#spoken + 1] = tostring(text)
return realSay(text, ...)
end
local hotbarModel = world.hotbars and world.hotbars[1]
local hadHotkey = hotbarModel and hotbarModel.hotkeys[1]
-- An empty slot is silent in game -- press before binding anything.
if hotbarModel and not hadHotkey then
spoken = {}
press("1", nil, "hotbar: empty slot")
check("hotbar says when a slot is empty",
table.concat(spoken, " | "):find("empty", 1, true) ~= nil,
table.concat(spoken, " | "))
end
if hotbarModel then
player.addItem{templateId = "torch", quantity = 1}
hotbarModel.addHotkey{
type = "equipment",
command = "equipUnequip",
itemTemplateId = "torch",
index = 1,
}
spoken = {}
press("1", nil, "hotbar: wield the torch")
local said = table.concat(spoken, " | ")
check("hotbar names the item and what it did to it",
said:find("Torch", 1, true) ~= nil and said:find("Wield", 1, true) ~= nil,
said)
spoken = {}
press("1", nil, "hotbar: put the torch away")
local saidAgain = table.concat(spoken, " | ")
check("a second press announces the opposite verb",
saidAgain:find("Unwield", 1, true) ~= nil, saidAgain)
-- Put the world back: a lit torch changes FoV and light, which the
-- viewwatch and loot-overlay checks below would then inherit.
hotbarModel.hotkeys[1] = hadHotkey
local planted = player.inventory.findItemByTemplateId("torch")
if planted then
planted.remove(1)
end
end
speech.say = realSay
end
-- Item DETAILS (the `=` key and the settle timer). Same problem as the
-- action menu above: the prologue inventory is empty, so there is no real
-- item to focus. The fragile parts are (a) item.getPopupMessage still