Phase 4 remainder: crafting screens, slider, config options
Game-file footprint unchanged: still one line in main.lua. The slider turned out to be genuinely generic: modules/slider.lua is a factory, so one wrapper on it narrates all four users -- craft, refine, trade and ingame_wait (the wait-N-turns screen). Same leverage as the console.print and inventoryCursorPopup patches. The options screens do NOT share it, though: config_menu changes values through config.increase/config.decrease, so it needed its own descriptor. It reports tab, option name and value, and its focus key includes the value so left/right re-announce even though the focused option has not moved. states/_itemgrid.lua is now shared by inventory, craft, refine and trade, all of which build a views/inventoryWindow. It stays silent while confirmationWindowEnabled is set so the slider owns speech during quantity selection instead of the two fighting. testbed gains walkTo(), which navigates using the game's own pathfinder rather than hardcoded direction sequences -- those drift as soon as anything moves the player. The config test only adjusts a value when the focused option is demonstrably safe, and puts it straight back. The slider patch is applied and confirmed by the self-test but not exercised live: ingame_wait is unreachable in the prologue because tutorial_bed overrides bed's tryInteractFunction with an empty function by design, and the crafting screens need an alchemy table and real items. Recorded as a NOTE rather than a failing check, since it is a property of the game and not of the mod. 25/25 testbed checks, history unit tests passing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d8bec6a3a8
commit
50b226620e
@@ -0,0 +1,78 @@
|
||||
-- Shared descriptor for the item-grid screens: inventory, craft, refine, trade.
|
||||
--
|
||||
-- All four build a `views/inventoryWindow` and move a cursor over a grid of
|
||||
-- cells. The inventoryCursorPopup patch speaks item details, but ONLY when a cell
|
||||
-- actually holds an item -- so empty cells and empty tabs were silent and you
|
||||
-- could not tell the cursor had moved. This owns narration for these screens
|
||||
-- instead (patches/popups.lua stands down wherever a descriptor exists).
|
||||
--
|
||||
-- Returns a descriptor; call with the state's window upvalue name.
|
||||
|
||||
local itemCategories = require("modules.item.categories")
|
||||
|
||||
return function(options)
|
||||
options = options or {}
|
||||
|
||||
local windowName = options.window or "inventoryWindow"
|
||||
local lastTab
|
||||
|
||||
return {
|
||||
read = function(stateTable, menus)
|
||||
-- While the quantity confirmation window is open, the slider owns
|
||||
-- speech (see patches/slider.lua). Announcing the grid cell over the
|
||||
-- top of it would fight the number the player is adjusting.
|
||||
local confirming = menus.getAnyUpvalue(stateTable, "confirmationWindowEnabled")
|
||||
if confirming then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local window = menus.getAnyUpvalue(stateTable, windowName)
|
||||
|
||||
if type(window) ~= "table" or not window.getItemOnCursor then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local position = window.cursorPosition
|
||||
if type(position) ~= "number" then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- activeTabIndex lives inside the window closure, not the state file,
|
||||
-- so reach it through one of the window's own methods.
|
||||
local tabIndex
|
||||
if window.adjustTab then
|
||||
tabIndex = menus.getUpvalue(window.adjustTab, "activeTabIndex")
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
|
||||
if tabIndex and tabIndex ~= lastTab then
|
||||
lastTab = tabIndex
|
||||
local category = itemCategories[tabIndex]
|
||||
parts[#parts + 1] = (category and category.header) or ("tab " .. tabIndex)
|
||||
end
|
||||
|
||||
local ok, item = pcall(window.getItemOnCursor)
|
||||
|
||||
if ok and item then
|
||||
parts[#parts + 1] = item.name or item.templateId or "item"
|
||||
if item.quantity and item.quantity > 1 then
|
||||
parts[#parts + 1] = "x" .. item.quantity
|
||||
end
|
||||
if item.isUnavailable then
|
||||
parts[#parts + 1] = "unavailable"
|
||||
end
|
||||
else
|
||||
parts[#parts + 1] = "empty"
|
||||
end
|
||||
|
||||
parts[#parts + 1] = "slot " .. position
|
||||
|
||||
return table.concat(parts, ", "),
|
||||
table.concat({
|
||||
tostring(tabIndex), tostring(position),
|
||||
tostring(ok and item and (item.name or "?")),
|
||||
}, ":")
|
||||
end,
|
||||
}
|
||||
end
|
||||
@@ -0,0 +1,64 @@
|
||||
-- states/config_menu.lua — the options screens.
|
||||
--
|
||||
-- These do NOT use modules/slider.lua. Values are changed through
|
||||
-- config.increase / config.decrease on the focused option, so the slider patch
|
||||
-- never sees them and this descriptor has to report the value itself.
|
||||
--
|
||||
-- Three things move independently here: the tab (tabLeft/tabRight), the option
|
||||
-- (up/down), and the option's VALUE (left/right). All three must be announced,
|
||||
-- and the value especially -- pressing right with no feedback is indistinguishable
|
||||
-- from pressing a dead key.
|
||||
|
||||
local config = require("models.config")
|
||||
|
||||
local lastTab
|
||||
|
||||
return {
|
||||
read = function(stateTable, menus)
|
||||
local activeTab = menus.getAnyUpvalue(stateTable, "activeTab")
|
||||
local selected = menus.getAnyUpvalue(stateTable, "currentSelectedOption")
|
||||
|
||||
if type(activeTab) ~= "table" or type(selected) ~= "number" then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local options = activeTab.options
|
||||
if type(options) ~= "table" then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local option = options[selected]
|
||||
if not option then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
|
||||
if activeTab.header and activeTab.header ~= lastTab then
|
||||
lastTab = activeTab.header
|
||||
parts[#parts + 1] = activeTab.header
|
||||
end
|
||||
|
||||
-- config.output returns (name, "< value >"). Strip the angle brackets:
|
||||
-- they are on-screen affordances for the left/right keys, not speech.
|
||||
local ok, name, value = pcall(config.output, option)
|
||||
|
||||
if ok and name then
|
||||
parts[#parts + 1] = (name:gsub(":%s*$", ""))
|
||||
if value then
|
||||
parts[#parts + 1] = (value:gsub("^<%s*", ""):gsub("%s*>$", ""))
|
||||
end
|
||||
else
|
||||
parts[#parts + 1] = option.name or "?"
|
||||
end
|
||||
|
||||
parts[#parts + 1] = selected .. " of " .. #options
|
||||
|
||||
-- Key includes the value, so adjusting with left/right re-announces even
|
||||
-- though the focused option has not changed.
|
||||
local valueKey = ok and tostring(value) or "?"
|
||||
|
||||
return table.concat(parts, ", "),
|
||||
table.concat({tostring(activeTab.header), tostring(selected), valueKey}, ":")
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- states/craft.lua -- shared item-grid descriptor, see _itemgrid.lua
|
||||
return require("accessibility.states._itemgrid")()
|
||||
@@ -1,61 +1,2 @@
|
||||
-- states/inventory.lua — the item grid.
|
||||
--
|
||||
-- The inventoryCursorPopup patch already speaks item details, but only when an
|
||||
-- item is actually under the cursor. Moving onto an empty slot, or switching to
|
||||
-- an empty tab, produced silence -- so you could not tell whether the cursor had
|
||||
-- moved, which tab you were on, or that the tab was simply empty.
|
||||
--
|
||||
-- This descriptor owns narration for this screen instead: it reports the item if
|
||||
-- there is one, "empty" if not, and the category whenever the tab changes.
|
||||
-- patches/popups.lua stands down for any state that has a descriptor, so the two
|
||||
-- never double-speak.
|
||||
|
||||
local itemCategories = require("modules.item.categories")
|
||||
|
||||
local lastTab
|
||||
|
||||
return {
|
||||
read = function(stateTable, menus)
|
||||
local window = menus.getAnyUpvalue(stateTable, "inventoryWindow")
|
||||
|
||||
if type(window) ~= "table" or not window.getItemOnCursor then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
local position = window.cursorPosition
|
||||
if type(position) ~= "number" then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
-- activeTabIndex lives inside the window closure, not the state file, so
|
||||
-- reach it through one of the window's own methods.
|
||||
local tabIndex
|
||||
if window.adjustTab then
|
||||
tabIndex = menus.getUpvalue(window.adjustTab, "activeTabIndex")
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
|
||||
if tabIndex and tabIndex ~= lastTab then
|
||||
lastTab = tabIndex
|
||||
local category = itemCategories[tabIndex]
|
||||
parts[#parts + 1] = (category and category.header) or ("tab " .. tabIndex)
|
||||
end
|
||||
|
||||
local ok, item = pcall(window.getItemOnCursor)
|
||||
|
||||
if ok and item then
|
||||
parts[#parts + 1] = item.name or item.templateId or "item"
|
||||
if item.quantity and item.quantity > 1 then
|
||||
parts[#parts + 1] = "x" .. item.quantity
|
||||
end
|
||||
else
|
||||
parts[#parts + 1] = "empty"
|
||||
end
|
||||
|
||||
parts[#parts + 1] = "slot " .. position
|
||||
|
||||
return table.concat(parts, ", "),
|
||||
tostring(tabIndex) .. ":" .. tostring(position) .. ":" .. tostring(ok and item and (item.name or "?"))
|
||||
end,
|
||||
}
|
||||
-- states/inventory.lua -- shared item-grid descriptor, see _itemgrid.lua
|
||||
return require("accessibility.states._itemgrid")()
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- states/refine.lua -- shared item-grid descriptor, see _itemgrid.lua
|
||||
return require("accessibility.states._itemgrid")()
|
||||
@@ -0,0 +1,2 @@
|
||||
-- states/trade.lua -- shared item-grid descriptor, see _itemgrid.lua
|
||||
return require("accessibility.states._itemgrid")()
|
||||
Reference in New Issue
Block a user