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
@@ -0,0 +1,89 @@
|
||||
-- itemdetails.lua — turn an item into speakable detail blocks.
|
||||
--
|
||||
-- The game already composes the whole of an item's detail text in one place:
|
||||
-- `item.getPopupMessage()` (modules/item.lua:199). It returns a list of
|
||||
-- `{text =, icon =, font =}` lines that the cursor popup renders:
|
||||
--
|
||||
-- a torch. <- name, with (quantity) if stackable
|
||||
-- It is a tool. <- category, from item/categories.lua
|
||||
-- It is pretty worn (43% condition).<- only when canDegrade
|
||||
-- <the free-text description>
|
||||
-- Yields: <- only when the item can be refined
|
||||
-- x1 when ground in a mortar. <- one per refinement type
|
||||
-- Contained knowledge: <- only when containedLore
|
||||
-- (2/5 fragments learned so far)
|
||||
--
|
||||
-- That is exactly what a player needs to evaluate an item, including "what does
|
||||
-- this turn into", and it is already assembled -- so we read it rather than
|
||||
-- re-deriving any of it from templates.
|
||||
--
|
||||
-- The icons are dropped: every icon line's `text` already names the thing in
|
||||
-- words ("x1 when ground in a mortar"), so nothing is lost by ear.
|
||||
--
|
||||
-- Pure formatting, no LÖVE and no game state, so the testbed can exercise it
|
||||
-- against a synthetic item.
|
||||
|
||||
local itemdetails = {}
|
||||
|
||||
-- The game wraps its own text and joins the wrapped lines with "\n", so those
|
||||
-- newlines are pure layout. Left in, the reader pauses mid-sentence at every
|
||||
-- wrap point. Same treatment as journal pages (states/journal.lua).
|
||||
local function flatten(text)
|
||||
return (tostring(text):gsub("%s*\n%s*", " "):gsub("^%s+", ""):gsub("%s+$", ""))
|
||||
end
|
||||
|
||||
itemdetails.flatten = flatten
|
||||
|
||||
-- Does this popup line merely repeat the name the focus announcement just said?
|
||||
-- getPopupMessage's first line is "<name>." or "<name> (3)." -- speaking it again
|
||||
-- immediately after "a torch, slot 1" is pure repetition.
|
||||
local function isNameLine(text, item)
|
||||
local name = item and item.name
|
||||
if type(name) ~= "string" then
|
||||
return false
|
||||
end
|
||||
return text:sub(1, #name) == name
|
||||
end
|
||||
|
||||
-- Returns a list of speakable blocks for `item`, or nil if there is nothing to
|
||||
-- say. `verbose` keeps the leading name line as well.
|
||||
function itemdetails.blocks(item, verbose)
|
||||
if type(item) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
local blocks = {}
|
||||
|
||||
local ok, lines = pcall(function()
|
||||
return item.getPopupMessage and item.getPopupMessage()
|
||||
end)
|
||||
|
||||
if ok and type(lines) == "table" then
|
||||
for index, line in ipairs(lines) do
|
||||
local text = type(line) == "table" and line.text or (type(line) == "string" and line)
|
||||
|
||||
if type(text) == "string" then
|
||||
text = flatten(text)
|
||||
|
||||
if text ~= "" and not (index == 1 and not verbose and isNameLine(text, item)) then
|
||||
blocks[#blocks + 1] = text
|
||||
end
|
||||
end
|
||||
end
|
||||
elseif type(item.description) == "string" then
|
||||
-- getPopupMessage missing or raising means the game changed shape. Degrade
|
||||
-- to the raw description rather than going silent.
|
||||
local text = flatten(item.description)
|
||||
if text ~= "" then
|
||||
blocks[#blocks + 1] = text
|
||||
end
|
||||
end
|
||||
|
||||
if #blocks == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
return blocks
|
||||
end
|
||||
|
||||
return itemdetails
|
||||
Reference in New Issue
Block a user