Using Loreline with Haxe

Loreline is written in Haxe, so using it from Haxe gives you direct access to the library with no wrappers. This guide shows how to set up a Haxe project, load a .lor script, and handle dialogue, choices, and script completion.

Installing the library

Install Loreline from haxelib:

haxelib install loreline
haxelib install hscript

Or install directly from the GitHub repository using haxelib git:

haxelib git loreline https://github.com/jeremyfa/loreline
haxelib install hscript

Project setup

Create a build file (e.g. build.hxml) for your target:

# Build: haxe build.hxml
# Run:   node out/main.js

--class-path src
--main Main
--library loreline
--library hscript
-D hscriptPos
-D js-es=6
-D loreline_use_js_types
-D loreline_typedef_options
-D loreline_functions_map_dynamic_access
-D loreline_node_id_class
--js out/main.js
# Build: haxe build.hxml
# Run:   neko out/main.n

--class-path src
--main Main
--library loreline
--library hscript
-D hscriptPos
--neko out/main.n
# Build: haxe build.hxml
# Run:   cd out/cs && dotnet run

--class-path src
--main Main
--library loreline
--library hscript
--library hxcs
-D hscriptPos
-D erase-generics
-D loreline_use_cs_types
-D loreline_cs_api
--cs out/cs
# Build: haxe build.hxml
# Run:   ./out/cpp/Main

--class-path src
--main Main
--library loreline
--library hscript
--library hxcpp
-D hscriptPos
--cpp out/cpp
# Build: haxe build.hxml
# Run:   python3 out/main.py

--class-path src
--main Main
--library loreline
--library hscript
-D hscriptPos
-D loreline_typedef_options
-D loreline_functions_map_dynamic_access
-D loreline_node_id_class
--python out/main.py
# Build: haxe build.hxml
# Run:   lua out/main.lua

--class-path src
--main Main
--library loreline
--library hscript
-D hscriptPos
-D loreline_typedef_options
-D loreline_functions_map_dynamic_access
-D loreline_node_id_class
--lua out/main.lua

Adding the hscript library and hscriptPos define is recommended, as they enable function scripting support. Without them, custom functions written in Loreline scripts are not supported.

Build defines

The -D flags above configure how Loreline compiles for each target. Here is what each one does:

Define Description
hscriptPos Enables position tracking in hscript for better error messages with line and column numbers. Required for function scripting.
js-es=6 Outputs ES6 JavaScript (uses class, let, arrow functions).
loreline_use_js_types Uses native JavaScript types for better interop with JS code.
loreline_use_cs_types Uses native .NET collection types (List<object>, Dictionary<string,object>) instead of Haxe collections.
loreline_cs_api Enables the C# interop layer for direct access to .NET APIs.
loreline_typedef_options Defines InterpreterOptions as a typedef instead of a class, for lighter interop on dynamic targets.
loreline_functions_map_dynamic_access Uses dynamic access for the functions map, for better performance on dynamic targets.
loreline_node_id_class Represents node IDs as a class instead of an abstract type, for compatibility with targets that don't inline abstracts.

Loading a script

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

var content = sys.io.File.getContent("story/CoffeeShop.lor");

function handleFile(path:String, callback:(String) -> Void) {
  var dir = "story/";
  callback(sys.io.File.getContent(dir + path));
}

Loreline.parse(content, "CoffeeShop.lor", handleFile, function(script) {
  if (script != null) {
    // Script parsed successfully, ready to play
  }
});

If your script has no import statements, you can pass null for the file handler and use the synchronous return value instead:

var 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 callback to advance the script:

function handleDialogue(
  interpreter:loreline.Interpreter,
  character:String,
  text:String,
  tags:Array<loreline.Interpreter.TextTag>,
  callback:() -> Void
) {
  if (character != null) {
    // Resolve display name from character definition
    var name = interpreter.getCharacterField(character, "name");
    var displayName = (name != null) ? Std.string(name) : character;
    Sys.println(displayName + ": " + text);
  } else {
    // Narrative text (no character)
    Sys.println(text);
  }
  callback();
}

Handling choices

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

function handleChoice(
  interpreter:loreline.Interpreter,
  options:Array<loreline.Interpreter.ChoiceOption>,
  callback:(Int) -> Void
) {
  for (i in 0...options.length) {
    if (options[i].enabled) {
      Sys.println("  [" + (i + 1) + "] " + options[i].text);
    }
  }

  // Read player input
  Sys.print("> ");
  var input = Sys.stdin().readLine();
  var choice = Std.parseInt(input);
  if (choice != null && choice >= 1 && choice <= options.length) {
    callback(choice - 1);
  }
}

Handling script completion

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

function handleFinish(interpreter:loreline.Interpreter) {
  Sys.println("--- The End ---");
}

Complete example

Here is a complete Main.hx that loads and plays a Loreline script from the command line:

import loreline.Loreline;
import loreline.Interpreter;

class Main {
  static var storyDir = "story/";

  static function main() {
    var content = sys.io.File.getContent(storyDir + "CoffeeShop.lor");

    Loreline.parse(content, "CoffeeShop.lor", handleFile, function(script) {
      if (script != null) {
        Loreline.play(script, onDialogue, onChoice, onFinish);
      }
    });
  }

  static function handleFile(path:String, callback:(String) -> Void) {
    try {
      callback(sys.io.File.getContent(storyDir + path));
    } catch (e:Dynamic) {
      callback(null);
    }
  }

  static function onDialogue(
    interp:Interpreter, character:String, text:String,
    tags:Array<Interpreter.TextTag>, callback:() -> Void
  ) {
    if (character != null) {
      var name = interp.getCharacterField(character, "name");
      Sys.println((name != null ? Std.string(name) : character) + ": " + text);
    } else {
      Sys.println(text);
    }
    Sys.println("");
    callback();
  }

  static function onChoice(
    interp:Interpreter, options:Array<Interpreter.ChoiceOption>,
    callback:(Int) -> Void
  ) {
    for (i in 0...options.length) {
      if (options[i].enabled) {
        Sys.println("  [" + (i + 1) + "] " + options[i].text);
      }
    }
    Sys.print("> ");
    var input = Sys.stdin().readLine();
    var choice = Std.parseInt(input);
    if (choice != null && choice >= 1 && choice <= options.length) {
      callback(choice - 1);
    }
  }

  static function onFinish(interp:Interpreter) {
    Sys.println("--- The End ---");
  }
}

Going further

Since Loreline is a Haxe library, you have access to the full source code and internal APIs. Check the Loreline repository on GitHub for more advanced usage patterns, including save/restore, translations, and custom functions.