-- 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