Using Loreline with Lua

Loreline provides a Lua module that works with Lua 5.1 and later, with no external dependencies. This guide shows how to set up a project, load a .lor script, and handle dialogue, choices, and script completion.

Installing the library

Download loreline-lua.zip from the GitHub releases page. The archive contains:

Copy the loreline/ folder into your project so that require("loreline") can find it.

Loading a script

Use loreline.parse() to parse a .lor string. The third argument is a file handler for resolving import statements in your script:

local loreline = require("loreline")

local function handle_file(path, provide)
    local f = io.open(path, "r")
    if f then
        provide(f:read("*a"))
        f:close()
    else
        provide("")
    end
end

local f = io.open("story/CoffeeShop.lor", "r")
local content = f:read("*a")
f:close()

local script = loreline.parse(content, "story/CoffeeShop.lor", handle_file)
loreline.play(script, on_dialogue, on_choice, on_finish)

If your script has no import statements, you can omit the file handler:

local script = loreline.parse(content)

Handling dialogue

The dialogue handler receives the interpreter, a character identifier (or nil for narrative text), the dialogue text, any tags, and a callback to advance the script:

local function on_dialogue(interp, character, text, tags, advance)
    if character ~= nil then
        -- Resolve display name from character definition
        local name = interp:get_character_field(character, "name")
        print((name or character) .. ": " .. text)
    else
        -- Narrative text (no character)
        print(text)
    end
    advance()
end

In a UI application, you would typically display the text and call advance() when the player is ready to continue.

Handling choices

The choice handler receives a list of options (a 1-indexed Lua table). Each option has a text field and an enabled field. Call the callback with the 0-based index of the selected choice:

local function on_choice(interp, options, select)
    for i, opt in ipairs(options) do
        if opt.enabled then
            print("  [" .. i .. "] " .. opt.text)
        end
    end

    -- In a real app, you would show buttons and call select(index) on click
    select(0)  -- select first option (0-based)
end

Note that while the options table is 1-indexed (standard Lua), the select callback expects a 0-based index.

Handling script completion

The finish handler is called when the script reaches its end:

local function on_finish(interp)
    print("--- The End ---")
end

Complete example

Here is a complete console application that loads and plays a Loreline script:

local loreline = require("loreline")

local function read_file(path)
    local f = io.open(path, "r")
    if not f then return "" end
    local content = f:read("*a")
    f:close()
    return content
end

local function script_dir()
    local info = debug.getinfo(1, "S")
    local path = info.source:match("^@(.*/)")
    return path or "./"
end

local function handle_file(path, provide)
    provide(read_file(path))
end

local function on_dialogue(interp, character, text, tags, advance)
    local formatted = text:gsub("\n", "\n  ")
    if character ~= nil then
        local name = interp:get_character_field(character, "name")
        io.write("  " .. (name or character) .. ": " .. formatted .. "\n")
    else
        io.write("  " .. formatted .. "\n")
    end
    advance()
end

local function on_choice(interp, options, select)
    io.write("\n")
    local enabled = {}
    for i, opt in ipairs(options) do
        if opt.enabled then
            enabled[#enabled + 1] = i - 1  -- 0-based for select()
            io.write("  " .. #enabled .. ". " .. opt.text .. "\n")
        end
    end

    while true do
        io.write("\n> ")
        io.flush()
        local raw = io.read("*l")
        if raw == nil then break end
        local choice = tonumber(raw:match("^%s*(.-)%s*$"))
        if choice and choice >= 1 and choice <= #enabled then
            select(enabled[choice])
            return
        end
        io.write("  Please enter a valid choice number.\n")
    end
end

local function on_finish(interp)
    io.write("\n--- The End ---\n")
end

local story_dir = script_dir() .. "story/"
local story_path = story_dir .. "CoffeeShop.lor"

local source = read_file(story_path)
if source == "" then
    io.stderr:write("Error: could not read " .. story_path .. "\n")
    os.exit(1)
end

local script = loreline.parse(source, story_path, handle_file)
if script == nil then
    io.stderr:write("Error: failed to parse script\n")
    os.exit(1)
end

io.write("=== CoffeeShop ===\n\n")
loreline.play(script, on_dialogue, on_choice, on_finish)

Going further

The loreline-lua.zip download includes a complete working sample with a story that uses character definitions and imports. Download it from the GitHub releases page.