-- Patch: views/levelCursorPopup.lua and views/inventoryCursorPopup.lua -- -- `inventoryCursorPopup.set` is the convergence point for focus text in four -- screens: inventory, craft, refine and trade. Patching it narrates all four -- without touching any state file. -- -- inventoryCursorPopup.set(lines, x, y, alignX, alignY) -- lines is a TABLE -- of {text=, icon=} -- -- NOTE: levelCursorPopup (the examine cursor) is deliberately NOT handled here. -- It is set from an animation-completion callback, so its text lands a keypress -- late unless the player enables the game's "quickExaminationPopup" option. -- accessibility/states/ingame_observe.lua polls the cursor position instead, -- which is immediate and setting-independent. local speech = require("accessibility.speech") -- Suppress repeats: the setter is called on every mouse move, often with -- unchanged content. local lastInventoryText local function linesToText(lines) if type(lines) ~= "table" then return nil end local parts = {} for _, line in ipairs(lines) do if type(line) == "table" and line.text then parts[#parts + 1] = line.text elseif type(line) == "string" then parts[#parts + 1] = line end end if #parts == 0 then return nil end return table.concat(parts, ". ") end return function() ---------------------------------------------------------------------------- -- inventoryCursorPopup: inventory, craft, refine, trade ---------------------------------------------------------------------------- local inventoryPopup = require("views.inventoryCursorPopup") local realInventorySet = inventoryPopup.set if not realInventorySet then error("views.inventoryCursorPopup has no set function -- game structure changed") end inventoryPopup.set = function(lines, ...) local result = realInventorySet(lines, ...) pcall(function() -- Stand down for any state that has its own descriptor. A descriptor -- knows about empty slots and tab changes, which this popup never sees -- (it is only called when there IS an item), so letting both speak -- would double up on every item. local states = require("states") local menus = require("accessibility.menus") if menus.loadDescriptor(states.getCurrent()) then return end local text = linesToText(lines) if not text then lastInventoryText = nil return end text = speech.normalise(text) if not text or text == lastInventoryText then return end lastInventoryText = text speech.say(text, true) end) return result end end