Using Loreline with C++

Loreline provides a C++ library with pre-built binaries for macOS, Linux, and Windows. The API uses C-style functions with a single header file, requiring only C++11. 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-cpp.zip from the GitHub releases page. The archive contains:

The archive can be used as a starting point for your own project, or as a reference for integrating Loreline into an existing one.

Project setup with CMake

To add Loreline to an existing CMake project, point to the include and lib directories:

target_include_directories(your_app PRIVATE /path/to/loreline/include)
target_link_directories(your_app PRIVATE /path/to/loreline/mac)  # or linux-x86_64, windows
target_link_libraries(your_app PRIVATE Loreline)

Make sure the shared library (libLoreline.dylib, libLoreline.so, or Loreline.dll) is next to your executable at runtime. On macOS and Linux, set the rpath:

# macOS
set_target_properties(your_app PROPERTIES BUILD_RPATH "@executable_path")
# Linux
set_target_properties(your_app PROPERTIES BUILD_RPATH "$ORIGIN")

On Windows, copy the DLL next to your executable as a post-build step.

Initialization and cleanup

Before using any Loreline function, call Loreline_init(). When you are done, call Loreline_dispose():

#include "Loreline.h"

int main() {
    Loreline_init();

    // ... use Loreline ...

    Loreline_dispose();
    return 0;
}

Loading a script

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

#include <fstream>
#include <sstream>
#include <string>

std::string readFile(const std::string& path) {
    std::ifstream f(path, std::ios::binary);
    if (!f.is_open()) return std::string();
    std::ostringstream ss;
    ss << f.rdbuf();
    return ss.str();
}

void onFileRequest(
    Loreline_String path,
    void (*provide)(Loreline_String content),
    void* userData
) {
    std::string content = readFile(path.c_str());
    provide(content.empty() ? Loreline_String() : Loreline_String(content.c_str()));
}

std::string content = readFile("story/CoffeeShop.lor");
Loreline_Script* script = Loreline_parse(
    content.c_str(), "story/CoffeeShop.lor", onFileRequest, NULL
);

If your script has no import statements, you can pass NULL for the file handler:

Loreline_Script* script = Loreline_parse(content.c_str(), "CoffeeShop.lor", NULL, NULL);

Handling dialogue

The dialogue handler is a C function pointer. It receives the interpreter, a character identifier (use isNull() to check for narrative text), the dialogue text, tags, and an advance function to call when ready to continue:

void onDialogue(
    Loreline_Interpreter* interp,
    Loreline_String character,
    Loreline_String text,
    const Loreline_TextTag* tags,
    int tagCount,
    void (*advance)(void),
    void* userData
) {
    if (!character.isNull()) {
        // Resolve display name from character definition
        Loreline_Value nameVal = Loreline_getCharacterField(interp, character, "name");
        const char* displayName = (nameVal.type == Loreline_StringValue && nameVal.stringValue)
            ? nameVal.stringValue.c_str()
            : character.c_str();
        printf("%s: %s\n", displayName, text.c_str());
    } else {
        // Narrative text
        printf("%s\n", text.c_str());
    }
    advance();
}

Loreline_String is a ref-counted string type, so you never need to free it manually. Use .c_str() to get a null-terminated C string, and .isNull() to check if it represents a null value.

Handling choices

The choice handler receives an array of Loreline_ChoiceOption. Each option has text (a Loreline_String) and enabled (a bool). Call select(index) with the 0-based index of the chosen option:

void onChoice(
    Loreline_Interpreter* interp,
    const Loreline_ChoiceOption* options,
    int optionCount,
    void (*select)(int index),
    void* userData
) {
    for (int i = 0; i < optionCount; i++) {
        if (options[i].enabled) {
            printf("  [%d] %s\n", i + 1, options[i].text.c_str());
        }
    }

    // Read player input
    printf("> ");
    fflush(stdout);
    char buf[64];
    if (fgets(buf, sizeof(buf), stdin)) {
        int choice = atoi(buf);
        if (choice >= 1 && choice <= optionCount) {
            select(choice - 1);
        }
    }
}

Handling script completion

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

void onFinish(Loreline_Interpreter* interp, void* userData) {
    printf("--- The End ---\n");
}

Complete example

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

#include "Loreline.h"
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <sstream>
#include <string>

static std::string readFile(const std::string& path) {
    std::ifstream f(path, std::ios::binary);
    if (!f.is_open()) return std::string();
    std::ostringstream ss;
    ss << f.rdbuf();
    return ss.str();
}

static void onFileRequest(
    Loreline_String path, void (*provide)(Loreline_String), void*
) {
    std::string content = readFile(path.c_str());
    provide(content.empty() ? Loreline_String() : Loreline_String(content.c_str()));
}

static void onDialogue(
    Loreline_Interpreter* interp, Loreline_String character,
    Loreline_String text, const Loreline_TextTag*, int,
    void (*advance)(void), void*
) {
    if (!character.isNull()) {
        Loreline_Value nameVal = Loreline_getCharacterField(interp, character, "name");
        const char* name = (nameVal.type == Loreline_StringValue && nameVal.stringValue)
            ? nameVal.stringValue.c_str() : character.c_str();
        printf("%s: %s\n\n", name, text.c_str());
    } else {
        printf("%s\n\n", text.c_str());
    }
    advance();
}

static void onChoice(
    Loreline_Interpreter*, const Loreline_ChoiceOption* options,
    int optionCount, void (*select)(int), void*
) {
    for (int i = 0; i < optionCount; i++) {
        if (options[i].enabled)
            printf("  [%d] %s\n", i + 1, options[i].text.c_str());
    }
    printf("> ");
    fflush(stdout);
    char buf[64];
    if (fgets(buf, sizeof(buf), stdin)) {
        int choice = atoi(buf);
        if (choice >= 1 && choice <= optionCount)
            select(choice - 1);
    }
}

static void onFinish(Loreline_Interpreter*, void*) {
    printf("--- The End ---\n");
}

int main() {
    std::string content = readFile("story/CoffeeShop.lor");
    if (content.empty()) {
        fprintf(stderr, "Error: cannot read story file\n");
        return 1;
    }

    Loreline_init();

    Loreline_Script* script = Loreline_parse(
        content.c_str(), "story/CoffeeShop.lor", onFileRequest, NULL
    );
    if (!script) {
        fprintf(stderr, "Error: failed to parse script\n");
        Loreline_dispose();
        return 1;
    }

    Loreline_Interpreter* interp = Loreline_play(
        script, onDialogue, onChoice, onFinish, Loreline_String(), NULL, NULL
    );

    if (interp) Loreline_releaseInterpreter(interp);
    Loreline_releaseScript(script);
    Loreline_dispose();
    return 0;
}

Going further

The loreline-cpp.zip download includes platform-specific build scripts (build-mac.sh, build-linux.sh, build-windows.bat) and a complete working sample with a story that uses character definitions and imports.