Using Loreline with Java

Loreline provides a Java library packaged as a single JAR, compatible with Java 8+ and any JVM language. This guide uses Java for its examples, but the library works just as well with Kotlin or any other language that runs on the JVM. It covers how to set up a project, load a .lor script, and handle dialogue, choices, and script completion.

Setting up your project

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

Add loreline.jar to your classpath. To compile and run a simple program:

javac -cp loreline.jar MyStory.java
java -cp loreline.jar:. MyStory

On Windows, use ; instead of : as the classpath separator:

java -cp loreline.jar;. MyStory

Loading a script

Use Loreline.parse() to parse a .lor string. The second and third arguments handle import statements in your script:

import loreline.*;
import java.nio.file.*;

String content = Files.readString(Path.of("story/CoffeeShop.lor"));

Script script = Loreline.parse(content, "CoffeeShop.lor",
    path -> Files.readString(Path.of("story/" + path)));

Loreline.play(script, this::onDialogue, this::onChoice, this::onFinish);

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

Script script = Loreline.parse(content);

Handling dialogue

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

void onDialogue(Interpreter interpreter, String character, String text,
                List<TextTag> tags, Runnable advance) {
    if (character != null) {
        // Resolve display name from character definition
        Object nameObj = interpreter.getCharacterField(character, "name");
        String name = nameObj != null ? nameObj.toString() : character;
        System.out.println(name + ": " + text);
    } else {
        // Narrative text (no character)
        System.out.println(text);
    }
    advance.run();
}

Handling choices

The choice handler receives a list of ChoiceOption items. Each option has a text field and an enabled field. Call the IntConsumer with the index of the selected choice:

void onChoice(Interpreter interpreter, List<ChoiceOption> options,
              IntConsumer select) {
    List<Integer> enabled = new ArrayList<>();
    for (int i = 0; i < options.size(); i++) {
        if (options.get(i).enabled) {
            enabled.add(i);
            System.out.println("  [" + enabled.size() + "] " + options.get(i).text);
        }
    }

    // Read player input
    Scanner scanner = new Scanner(System.in);
    System.out.print("> ");
    int choice = scanner.nextInt();
    select.accept(enabled.get(choice - 1));
}

Handling script completion

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

void onFinish(Interpreter interpreter) {
    System.out.println("--- 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:

Engine.play(script, onDialogue, onChoice, onFinish, "MorningScene", null);

Interpreter options

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

InterpreterOptions options = new InterpreterOptions();
options.functions = new HashMap<>();
options.functions.put("roll", (interp, args) ->
    (int)(Math.random() * ((Number)args[0]).intValue()) + 1);
options.translations = Engine.extractTranslations(script);
options.strictAccess = true;

Engine.play(script, onDialogue, onChoice, onFinish, null, options);

Complete example

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

import loreline.*;

import java.io.IOException;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.function.IntConsumer;

public class MyStory {
    static String storyDir = "story/";
    static Scanner scanner = new Scanner(System.in);

    public static void main(String[] args) throws IOException {
        String content = Files.readString(Path.of(storyDir + "CoffeeShop.lor"));

        Script script = Loreline.parse(content, "CoffeeShop.lor",
            path -> readFile(storyDir + path));

        if (script != null) {
            Loreline.play(script, MyStory::onDialogue, MyStory::onChoice, MyStory::onFinish);
        }
    }

    static String readFile(String path) {
        try {
            return Files.readString(Path.of(path));
        } catch (IOException e) {
            return null;
        }
    }

    static void onDialogue(Interpreter interpreter, String character, String text,
                           List<TextTag> tags, Runnable advance) {
        if (character != null) {
            Object nameObj = interpreter.getCharacterField(character, "name");
            String name = nameObj != null ? nameObj.toString() : character;
            System.out.println(name + ": " + text);
        } else {
            System.out.println(text);
        }
        System.out.println();
        advance.run();
    }

    static void onChoice(Interpreter interpreter, List<ChoiceOption> options,
                         IntConsumer select) {
        List<Integer> enabled = new ArrayList<>();
        for (int i = 0; i < options.size(); i++) {
            if (options.get(i).enabled) {
                enabled.add(i);
                System.out.println("  [" + enabled.size() + "] " + options.get(i).text);
            }
        }
        System.out.print("> ");
        int choice = scanner.nextInt();
        select.accept(enabled.get(choice - 1));
    }

    static void onFinish(Interpreter interpreter) {
        System.out.println("--- The End ---");
    }
}

Going further

For a polished desktop example with Swing rendering, animations, and styled output, see the sample project included in loreline-jvm.zip from the GitHub releases page.