chroma_solstice/dialogue_manager.lua
2026-08-08 00:09:50 -04:00

110 lines
3.4 KiB
Lua

-- Dead-simple modal dialogue overlay driven by a narrator (Ink) story.
-- No variables, no saved state: open a book, walk its paragraphs, pick a
-- choice, then a keypress dismisses it. `narrator` is the global set up in
-- main.lua; Dialogable entities (see npc.lua) call Dialogue.open on bump.
Dialogue = {}
local story -- active narrator story, or nil when the box is closed
local paragraphs = {} -- text lines currently shown
local choices = {} -- current choices (empty once we reach an ending)
local selected = 1
function Dialogue.isOpen()
return story ~= nil
end
-- Run the story forward to the next choice point (or the end), gathering every
-- paragraph printed along the way.
local function advance()
paragraphs = {}
while story:can_continue() do
for _, p in ipairs(story:continue()) do
table.insert(paragraphs, p.text)
end
end
choices = story:can_choose() and story:get_choices() or {}
selected = 1
end
function Dialogue.open(book)
if story then return end -- already talking: ignore repeat bumps
story = narrator.init_story(book)
story:begin()
advance()
end
function Dialogue.close()
story, paragraphs, choices, selected = nil, {}, {}, 1
end
function Dialogue.keypressed(key)
if #choices == 0 then
-- an ending is on screen: any key dismisses the box
Dialogue.close()
elseif key == "up" then
selected = selected > 1 and selected - 1 or #choices
elseif key == "down" then
selected = selected < #choices and selected + 1 or 1
elseif key == "return" or key == "space" or key == "x" then
story:choose(selected)
advance()
end
end
-- LCD font sized to the current play area, rebuilt only when the size changes,
-- so dialogue text scales with the game like every sprite does.
local dlgFont, dlgFontSize
local function dialogueFont()
local size = math.max(8, math.floor(height / gridHeight * 0.4))
if not dlgFont or size ~= dlgFontSize then
dlgFont = love.graphics.newFont("font/LCD_Solid.ttf", size)
dlgFontSize = size
end
return dlgFont
end
function Dialogue.draw()
if not story then return end
local fnt = dialogueFont()
local fh = fnt:getHeight()
local pad, lineH = fh * 0.8, fh * 1.3
local boxH = height * 0.4
local y0 = height - boxH
local wrap = width - pad * 2
-- Dialogue obeys the world's colour rule: only the channels the player has
-- are rendered. One colour -> that colour; all three -> white (additive), so
-- a red-only player literally reads the sign in red.
local has = { red = false, green = false, blue = false }
for _, c in ipairs(currentRoom.colorsPlayerHas) do has[c] = true end
local r = has.red and 1 or 0
local g = has.green and 1 or 0
local b = has.blue and 1 or 0
local prevFont = love.graphics.getFont()
love.graphics.setFont(fnt)
love.graphics.setColor(0, 0, 0, 0.82)
love.graphics.rectangle("fill", 0, y0, width, boxH)
local y = y0 + pad
love.graphics.setColor(r, g, b, 1)
for _, text in ipairs(paragraphs) do
love.graphics.printf(text, pad, y, wrap)
local _, lines = fnt:getWrap(text, wrap)
y = y + math.max(1, #lines) * lineH
end
y = y + lineH * 0.5
for i, choice in ipairs(choices) do
local dim = (i == selected) and 1 or 0.5 -- selected is brighter + "> "
love.graphics.setColor(r * dim, g * dim, b * dim, 1)
local line = ((i == selected) and "> " or " ") .. choice.text
love.graphics.printf(line, pad, y, wrap)
local _, lines = fnt:getWrap(line, wrap)
y = y + math.max(1, #lines) * lineH
end
love.graphics.setFont(prevFont)
end