Game-file footprint unchanged: still one line in main.lua.
Wall tones: stop the previous tone per direction before playing the new one.
The tones are 2.33s mono; steps land far faster than that and up to four
directions sound at once, so overlapping them stacked into a continuous drone
instead of reading as a per-step cue. Cutting the old tone also means the
distance/volume you hear always reflects where you are standing now.
ingame_dialogue descriptor: speaks speaker and message on a node change, but
only the response on a selection change -- re-reading the whole speech every
time you move between responses would be unusable.
Two more bugs found by the testbed:
4. Nested states.switch announced in reverse. ingame.open() sees
world.pendingDialogue and switches straight to ingame_dialogue; because we
announce after realSwitch returns, the inner announcement fired first and the
outer one interrupted and clobbered it, so entering a conversation said
'Game' rather than 'Conversation'. Now we stay quiet if we are no longer the
current state.
5. The game already numbers response text ('1. (Continue)'), matching the number
key that picks it, so our own numbering produced '1. 1. (Continue)'.
9/9 testbed checks, history unit tests passing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
2.9 KiB
Lua
101 lines
2.9 KiB
Lua
-- states/ingame_dialogue.lua
|
|
--
|
|
-- Conversations. Two things must be spoken, and they change independently:
|
|
-- * the NPC's line (speaker name + message blocks) -- changes when the node does
|
|
-- * the currently selected response -- changes as you press up/down
|
|
--
|
|
-- So this tracks the node itself and announces the full block on a node change,
|
|
-- but only the option on a selection change. Repeating the whole speech every
|
|
-- time you move between responses would be unusable.
|
|
--
|
|
-- Responses can also be picked directly with the number keys 1-0, which the game
|
|
-- already supports and which is the fastest way to work through a conversation.
|
|
|
|
local descriptor = {}
|
|
|
|
local lastNodeKey
|
|
|
|
local function nodeKeyFor(speakerName, blocks, options)
|
|
local parts = {tostring(speakerName), tostring(#options)}
|
|
|
|
for _, block in ipairs(blocks or {}) do
|
|
parts[#parts + 1] = tostring(block.text)
|
|
end
|
|
|
|
return table.concat(parts, "|")
|
|
end
|
|
|
|
descriptor.read = function(stateTable, menus)
|
|
local keypressed = stateTable.keypressed
|
|
|
|
local options = menus.getUpvalue(keypressed, "options")
|
|
local selected = menus.getUpvalue(keypressed, "selectedOption")
|
|
|
|
if type(options) ~= "table" or type(selected) ~= "number" then
|
|
return nil, nil
|
|
end
|
|
|
|
-- speakerName / messageTextBlocks are not touched by keypressed directly --
|
|
-- it calls updateText(), so they are upvalues of that function.
|
|
local updateText = menus.getUpvalue(keypressed, "updateText")
|
|
local speakerName, blocks
|
|
|
|
if type(updateText) == "function" then
|
|
speakerName = menus.getUpvalue(updateText, "speakerName")
|
|
blocks = menus.getUpvalue(updateText, "messageTextBlocks")
|
|
end
|
|
|
|
local nodeKey = nodeKeyFor(speakerName, blocks, options)
|
|
|
|
local option = options[selected]
|
|
local optionText = option and option.text or nil
|
|
|
|
-- Selection moved within the same node: just the response.
|
|
if nodeKey == lastNodeKey then
|
|
if not optionText then
|
|
return nil, nil
|
|
end
|
|
return string.format("%s, %d of %d", optionText, selected, #options),
|
|
nodeKey .. ":" .. selected
|
|
end
|
|
|
|
-- New node: speaker, what they said, then the options.
|
|
lastNodeKey = nodeKey
|
|
|
|
local parts = {}
|
|
|
|
if speakerName and speakerName ~= "" then
|
|
parts[#parts + 1] = speakerName
|
|
end
|
|
|
|
for _, block in ipairs(blocks or {}) do
|
|
if block.text then
|
|
parts[#parts + 1] = block.text
|
|
end
|
|
end
|
|
|
|
if #options > 0 then
|
|
parts[#parts + 1] = #options .. (#options == 1 and " response" or " responses")
|
|
for i, entry in ipairs(options) do
|
|
if entry.text then
|
|
-- The game already prefixes response text with its number
|
|
-- ("1. (Continue)"), matching the number keys that select it.
|
|
-- Adding our own gave "1. 1. (Continue)".
|
|
if entry.text:match("^%s*%d+%s*[%.%)]") then
|
|
parts[#parts + 1] = entry.text
|
|
else
|
|
parts[#parts + 1] = i .. ". " .. entry.text
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if #parts == 0 then
|
|
return nil, nil
|
|
end
|
|
|
|
return table.concat(parts, ". "), nodeKey .. ":" .. selected
|
|
end
|
|
|
|
return descriptor
|