items: read the details of the focused item
The item grids said which item you were on ("a torch, slot 3") but nothing
about what it was -- no description, no condition, and no "what does this
refine into", which is the whole of alchemy.
The game already composes all of it in one place: item.getPopupMessage()
(modules/item.lua:199) returns the cursor popup's line list, so read that
rather than re-deriving anything from templates.
It also already gates it behind a dwell -- the popup only fades in after
examinationPopupFadeInDuration (0.4s), so a sighted player who arrows past a
slot never sees the details either. Mirror that instead of inventing a
modality: stop on an item for 0.6s and the details speak themselves, queued.
"=" reads them on demand, Shift+= keeps the name line too. ("r" was the
obvious mnemonic but is a game-claimed scancode, and getCommand() dispatches
on scancode ignoring modifiers.)
New generic hook descriptor.details() returning a LIST of blocks, so each is
also pushed into the console history and [ ] \ walk a long description back
afterwards -- the journal page trick. Only _itemgrid.lua implements it so far.
The other half is the hint panel craft/refine/trade draw permanently beside
the grid, which the popup never contains: recipe components, and price, which
appears nowhere else in the game. The three panels share a shape, so one
reader covers them. Affordability survives only as a red colour in the hint
table and decides whether enter does anything, so it is decoded and spoken.
59 testbed checks, 0 failures: a synthetic ivy_leaves item as the version-bump
canary for getPopupMessage, hasUpvalue over every hint-panel local, four
assertions on the pure settleStep, and an end-to-end "=" press. Not yet
exercised against a live item -- the prologue inventory is empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c841bcc68d
commit
a00f327c68
@@ -20,7 +20,79 @@
|
||||
-- The menu is a closure (modules/item/itemMenu.lua) with no getters, so its
|
||||
-- `lines` and `currentSelectedAction` are read as upvalues of its own draw().
|
||||
|
||||
-- Third thing this owns: DETAILS (the `=` key and the settle timer, see
|
||||
-- accessibility/details.lua). Two sources, both already composed by the game:
|
||||
--
|
||||
-- * item.getPopupMessage() -- description, category, condition, refine yields,
|
||||
-- lore progress. Read through accessibility/itemdetails.lua.
|
||||
-- * the HINT PANEL that craft, refine and trade each draw permanently beside
|
||||
-- the grid, and which the cursor popup never contains. Notably PRICE exists
|
||||
-- only there (states/trade.lua:156), as do a recipe's component costs and
|
||||
-- whether you can afford them.
|
||||
--
|
||||
-- The three panels turn out to share a shape -- hintDescription, a componentHints
|
||||
-- list of {nameText, descriptionText, color}, and a budget line -- so one reader
|
||||
-- covers all three rather than three per-state callbacks. Fields a given screen
|
||||
-- doesn't use are simply nil (craft has no budget line; trade has no
|
||||
-- hintDescription; refine names an output instead of components).
|
||||
|
||||
local itemCategories = require("modules.item.categories")
|
||||
local itemdetails = require("accessibility.itemdetails")
|
||||
|
||||
-- The game marks an unaffordable component by colouring it red. That is the one
|
||||
-- fact which decides whether pressing enter does anything at all, so it has to be
|
||||
-- spoken -- and the colour is the only place it survives into the hint table.
|
||||
local function isUnaffordableColor(color)
|
||||
return type(color) == "table" and color[1] and color[1] > 0.8
|
||||
and color[2] and color[2] < 0.5
|
||||
end
|
||||
|
||||
-- craft / refine / trade draw a permanent hint panel beside the grid.
|
||||
local function hintBlocks(stateTable, menus)
|
||||
local blocks = {}
|
||||
|
||||
local function add(text)
|
||||
if type(text) == "string" then
|
||||
text = itemdetails.flatten(text)
|
||||
if text ~= "" then
|
||||
blocks[#blocks + 1] = text
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- craft: the recipe's brewing instructions. refine: how this refinement works.
|
||||
add(menus.getAnyUpvalue(stateTable, "hintDescription"))
|
||||
|
||||
-- refine: what you get out, and how much of it you already have.
|
||||
local outputName = menus.getAnyUpvalue(stateTable, "outputName")
|
||||
if outputName then
|
||||
local quantity = menus.getAnyUpvalue(stateTable, "outputQuantityText")
|
||||
add("Yields " .. outputName .. (quantity and (", " .. quantity) or ""))
|
||||
end
|
||||
|
||||
-- craft: one per recipe component. trade: the price, as a single "component".
|
||||
local componentHints = menus.getAnyUpvalue(stateTable, "componentHints")
|
||||
if type(componentHints) == "table" then
|
||||
for _, hint in ipairs(componentHints) do
|
||||
if type(hint) == "table" and hint.nameText then
|
||||
add("Costs " .. hint.nameText
|
||||
.. (hint.descriptionText and (", " .. hint.descriptionText) or "")
|
||||
.. (isUnaffordableColor(hint.color) and ", cannot afford" or ""))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- refine / trade: "You already have: x2.00 green paste".
|
||||
local budgetName = menus.getAnyUpvalue(stateTable, "budgetNameText")
|
||||
if budgetName then
|
||||
local budgetDescription = menus.getAnyUpvalue(stateTable, "budgetDescription")
|
||||
local budgetQuantity = menus.getAnyUpvalue(stateTable, "budgetQuantityText")
|
||||
add((budgetDescription or "You have:") .. " "
|
||||
.. (budgetQuantity or "") .. " " .. budgetName)
|
||||
end
|
||||
|
||||
return blocks
|
||||
end
|
||||
|
||||
return function(options)
|
||||
options = options or {}
|
||||
@@ -71,16 +143,60 @@ return function(options)
|
||||
"itemmenu:" .. tostring(selected) .. "/" .. tostring(#lines)
|
||||
end
|
||||
|
||||
-- The window closure, or nil if the screen isn't built yet.
|
||||
local function getWindow(stateTable, menus)
|
||||
local window = menus.getAnyUpvalue(stateTable, windowName)
|
||||
if type(window) ~= "table" or not window.getItemOnCursor then
|
||||
return nil
|
||||
end
|
||||
return window
|
||||
end
|
||||
|
||||
return {
|
||||
reset = function()
|
||||
lastTab = nil
|
||||
lastMenu = nil
|
||||
end,
|
||||
|
||||
read = function(stateTable, menus)
|
||||
local window = menus.getAnyUpvalue(stateTable, windowName)
|
||||
-- Everything the focus announcement is too terse to say. See details.lua.
|
||||
details = function(stateTable, menus, verbose)
|
||||
local window = getWindow(stateTable, menus)
|
||||
if not window then
|
||||
return nil
|
||||
end
|
||||
|
||||
if type(window) ~= "table" or not window.getItemOnCursor then
|
||||
-- The slider owns speech during quantity selection (patches/slider.lua);
|
||||
-- reading a description over the number being adjusted would fight it.
|
||||
if menus.getAnyUpvalue(stateTable, "confirmationWindowEnabled") then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Note the item action menu is NOT excluded here, unlike in read(). The
|
||||
-- menu is modal over the grid so the highlighted VERB is what read()
|
||||
-- announces, but the thing you want described is still the item the menu
|
||||
-- belongs to -- and that is exactly when you want to know what "Drink"
|
||||
-- would be drinking.
|
||||
local ok, item = pcall(window.getItemOnCursor)
|
||||
if not ok or not item then
|
||||
return nil
|
||||
end
|
||||
|
||||
local blocks = itemdetails.blocks(item, verbose) or {}
|
||||
|
||||
for _, block in ipairs(hintBlocks(stateTable, menus)) do
|
||||
blocks[#blocks + 1] = block
|
||||
end
|
||||
|
||||
if #blocks == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return blocks
|
||||
end,
|
||||
|
||||
read = function(stateTable, menus)
|
||||
local window = getWindow(stateTable, menus)
|
||||
if not window then
|
||||
return nil, nil
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user