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
|