Files

194 lines
4.4 KiB
Lua
Raw Permalink Normal View History

-- history.lua — unbounded console history with a review cursor.
--
-- The game's own console buffer (modules/console.lua) is unusable as history: its
-- entries expire after 3 seconds and scroll away. We keep our own copy, appended
-- from a wrapper around console.print.
--
-- Coalescing mirrors the game exactly. console.print folds a repeated identical
-- message into the previous entry and bumps a `times` counter, drawing it as
-- "You feel cold (x3)". We do the same and speak the same string, so what is
-- heard matches what is on screen.
local speech = require("accessibility.speech")
local history = {}
local MAX_ENTRIES = 1000
local entries = {}
local reviewIndex = nil -- nil = following the tail (live)
--------------------------------------------------------------------------------
-- Rendering an entry
--------------------------------------------------------------------------------
local function entryText(entry)
if not entry then
return nil
end
if entry.times and entry.times > 1 then
return entry.text .. " (x" .. entry.times .. ")"
end
return entry.text
end
history.entryText = entryText
--------------------------------------------------------------------------------
-- Appending
--------------------------------------------------------------------------------
-- Called from the console.print wrapper. Returns the text that should be spoken,
-- or nil if nothing should be.
function history.append(text, colorTemplate)
text = speech.normalise(text)
if not text then
return nil
end
local last = entries[#entries]
if last and last.text == text then
-- Same fold the game performs.
last.times = last.times + 1
return entryText(last)
end
entries[#entries + 1] = {
text = text,
times = 1,
colorTemplate = colorTemplate,
}
-- Trim from the front. Keep the review cursor pointing at the same entry.
if #entries > MAX_ENTRIES then
table.remove(entries, 1)
if reviewIndex then
reviewIndex = math.max(1, reviewIndex - 1)
end
end
return entryText(entries[#entries])
end
function history.count()
return #entries
end
function history.clear()
entries = {}
reviewIndex = nil
end
--------------------------------------------------------------------------------
-- Review cursor
--------------------------------------------------------------------------------
-- While reviewing, the cursor holds still as new lines arrive; it only follows the
-- tail when the user has not moved it. Otherwise reading back through a fight
-- would be yanked forward by every new message.
local function speakAt(index)
if #entries == 0 then
speech.interrupt("No messages")
return
end
local entry = entries[index]
if not entry then
return
end
local position
if index == 1 then
position = "First message"
elseif index == #entries then
position = "Last message"
end
local text = entryText(entry)
if position then
-- Most game messages already end in punctuation; joining with ". " blindly
-- gives "You hit the rat.. First message".
local separator = text:match("[%.%!%?]$") and " " or ". "
text = text .. separator .. position
end
speech.interrupt(text)
end
function history.reviewPrevious()
if #entries == 0 then
speech.interrupt("No messages")
return
end
if reviewIndex == nil then
-- Entering review from live: start at the newest, then step back one.
reviewIndex = #entries
end
reviewIndex = math.max(1, reviewIndex - 1)
speakAt(reviewIndex)
end
function history.reviewNext()
if #entries == 0 then
speech.interrupt("No messages")
return
end
if reviewIndex == nil then
speakAt(#entries)
reviewIndex = #entries
return
end
reviewIndex = math.min(#entries, reviewIndex + 1)
speakAt(reviewIndex)
end
function history.reviewFirst()
if #entries == 0 then
speech.interrupt("No messages")
return
end
reviewIndex = 1
speakAt(reviewIndex)
end
function history.reviewLast()
if #entries == 0 then
speech.interrupt("No messages")
return
end
reviewIndex = nil -- back to following live output
speakAt(#entries)
end
function history.repeatCurrent()
if #entries == 0 then
speech.interrupt("No messages")
return
end
speakAt(reviewIndex or #entries)
end
function history.copyCurrent()
if #entries == 0 then
return
end
local text = entryText(entries[reviewIndex or #entries])
local ok = pcall(love.system.setClipboardText, text)
speech.interrupt(ok and "Copied" or "Copy failed")
end
return history