110 lines
3.4 KiB
Lua
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
|