Using Loreline with Godot

Loreline provides a GDExtension plugin for Godot 4.2+. This guide shows how to set up your project, load a .lor script, and handle dialogue, choices, and script completion using GDScript.

Setup

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

To add Loreline to your project, copy the addons/loreline/ folder into the addons/ directory of your own Godot project.

Loading a script

Get the shared Loreline instance, then call loreline.parse() with the resource path to your .lor file:

var loreline: Loreline = Loreline.shared()

func _ready() -> void:
    var script_data: LorelineScript = loreline.parse("res://story/CoffeeShop.lor")

Loreline handles file reading and import resolution automatically.

Custom loading

If you need to control how files are loaded (e.g. encrypted files, network resources), you can load the source yourself and pass it to parse(). The second argument is the file path (used to resolve relative import paths), and the third is a callback called to load each file imported by the script:

var file := FileAccess.open("res://story/CoffeeShop.lor", FileAccess.READ)
var source := file.get_as_text()
file.close()

script_data = loreline.parse(source, "res://story/CoffeeShop.lor", _handle_file)

The file handler is not used for the root file (which you load yourself), but for each file referenced via import in the script. It receives the resolved path and returns the content as a string:

func _handle_file(path: String) -> String:
    var f := FileAccess.open(path, FileAccess.READ)
    if f == null:
        return ""
    return f.get_as_text()

Handling dialogue

Start playback by calling loreline.play() with the parsed script and your handler functions:

loreline.play(script_data, _on_dialogue, _on_choice, _on_finished)

The dialogue handler receives the interpreter, the character identifier, the text, an array of tags, and a callable to advance the script. Call advance.call() to continue to the next line:

func _on_dialogue(interp: LorelineInterpreter, character: String, text: String, tags: Array, advance: Callable) -> void:
    if character != "":
        var display_name: String = interp.get_character_field(character, "name")
        if display_name != "":
            character = display_name
        print(character + ": " + text)
    else:
        print(text)

    advance.call()

Handling choices

The choice handler receives the interpreter, an array of option dictionaries, and a callable to select an option. Each option has a "text" field and an "enabled" field. Call select.call(index) with the index of the chosen option:

func _on_choice(_interp: LorelineInterpreter, options: Array, select: Callable) -> void:
    var enabled_indices: Array[int] = []
    for i in range(options.size()):
        if options[i]["enabled"]:
            enabled_indices.append(i)
            print("  [" + str(enabled_indices.size()) + "] " + options[i]["text"])

    # In a real project, wait for player input here.
    # For this example, automatically select the first enabled choice:
    select.call(enabled_indices[0])

Handling script completion

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

func _on_finished(_interp: LorelineInterpreter) -> void:
    print("--- The End ---")

Starting from a specific beat

By default, play() starts from the beginning of the script. To start from a specific beat, pass its name:

loreline.play(script_data, _on_dialogue, _on_choice, _on_finished, "MorningScene")

Interpreter options

You can pass additional options to play() to register custom functions, apply translations, or enable strict variable access:

var options := LorelineOptions.new()
options.set_function("roll", func(interp, args): return randi_range(1, int(args[0])))
options.set_strict_access(true)

loreline.play(script_data, _on_dialogue, _on_choice, _on_finished, null, options)

Complete example

Here is a minimal GDScript that loads and plays a Loreline script, printing output to the console. Attach this script to any Node:

extends Node

var loreline: Loreline = Loreline.shared()

var awaiting_choice := false
var enabled_indices: Array[int] = []
var pending_select: Callable

func _ready() -> void:
    var script_data := loreline.parse("res://story/CoffeeShop.lor")

    if script_data:
        loreline.play(script_data, _on_dialogue, _on_choice, _on_finished)

func _on_dialogue(interp: LorelineInterpreter, character: String, text: String, _tags: Array, advance: Callable) -> void:
    if character != "":
        var display_name: String = interp.get_character_field(character, "name")
        if display_name != "":
            character = display_name
        print(character + ": " + text)
    else:
        print(text)
    print("")
    advance.call()

func _on_choice(_interp: LorelineInterpreter, options: Array, select: Callable) -> void:
    enabled_indices.clear()
    for i in range(options.size()):
        if options[i]["enabled"]:
            enabled_indices.append(i)
            print("  " + str(enabled_indices.size()) + ". " + options[i]["text"])
    pending_select = select
    awaiting_choice = true

func _unhandled_input(event: InputEvent) -> void:
    if not awaiting_choice:
        return
    if event is InputEventKey and event.pressed:
        var num := event.keycode - KEY_1
        if num >= 0 and num < enabled_indices.size():
            awaiting_choice = false
            pending_select.call(enabled_indices[num])

func _on_finished(_interp: LorelineInterpreter) -> void:
    print("\n--- The End ---")

Going further

For a complete Godot project with UI, animations, and styled output, the sample/ folder included in the loreline-godot.zip download from the GitHub releases provides a full working example.