Loreline is an open-source scripting language for writing interactive fictions.
Here's a most basic example of Loreline script:
The warm aroma of coffee fills the café.
barista: Hi there! How are you doing today?
choice
Having a great day
barista: Wonderful! Coffee will make it even better.
Need caffeine...
barista: Say no more! Let me help with that.
Your name is Alex, right?
barista: Oh, I didn't expect you'd remember it!
Even if you've never seen such script before, you can probably understand what it does: it describes a scene in a café and gives the player choices that lead to different outcomes.
Let's explore how Loreline helps you create interactive stories. We'll start with the basic building blocks and gradually build up to more complex features.
Although you can write right at the beginning of a Loreline script, as your story becomes more complex, you'll want to organize it better. That's where Loreline "beats" come into play - sections that contain related scenes or moments. Think of beats as chapters or scenes in your story:
beat EnterCafe
The morning sun streams through the café windows as you step inside.
barista: <friendly> Welcome! I don't think I've seen you here before.
choice
Just looking around
barista: Take your time! I'm here when you're ready.
-> ExploreMenu
Actually, I could use some coffee
barista: <happy> You're in the right place!
-> TakeOrder
beat ExploreMenu
Beside you, a regular customer sips her drink contentedly.
sarah: Their lattes are amazing. I come here every morning.
barista: <cheerful> Sarah's right! Want to try one?
choice
Sure, I'll have what she's having
sarah: <pleased> Good choice!
-> TakeOrder
What else do you recommend?
-> TakeOrder
beat TakeOrder
barista: So, what can I get started for you?
choice
A latte sounds perfect
barista: <excited> Coming right up! I'll make it special for your first visit.
sarah: <smile> You won't regret it.
-> EndVisit
Just a regular coffee today
barista: Sometimes the classics are the best choice!
-> EndVisit
beat EndVisit
You find a cozy spot to enjoy your drink.
sarah: <friendly> Hope to see you around more often!
The arrow syntax (->
) lets you move between beats, creating a branching storyline. Each beat can have its own narrative flow, choices, and consequences.
When writing dialogue, you can define your characters along with their properties:
character barista
name: Alex
friendship: 0 // Track relationship with player
shiftStarted: true
character customer
name: Sam
visits: 0
favoriteDrink: null
Once defined, characters can speak using a simple syntax - their identifier, followed by a colon:
barista: Welcome to Coffee Dreams! What can I get you today?
customer: Just a regular coffee, please.
barista: Coming right up!
In Loreline, you can write narrative text naturally, just as you would in a book. You don't need any special markers - just write:
The warm aroma of coffee fills the café. Sunlight streams through the windows, casting long shadows across the wooden floor.
A gentle murmur of conversation fills the space.
Tags enclosed in angle brackets (<tag>
) can be used in any text - whether it's dialogue or narrative:
barista: <friendly> Welcome back! Your usual?
customer: <tired> Yes please, I really need it today.
The machine <whirs>hums to life</whirs> as steam <hiss>escapes with a sharp sound</hiss>.
These tags can be used to express character emotions or change how text is displayed, depending on what's possible in your game or application.
Interactive stories need to remember choices and track progress. Loreline uses state declarations for this. There are two types of state: persistent and temporary.
Persistent state remains throughout your story:
state
coffeeBeans: 100 // Track inventory
rushHour: false // Is it busy?
dayNumber: 1 // Which day of the story
You can change these values as your story progresses:
coffeeBeans -= 10 // Use some beans
rushHour = true // Start rush hour
dayNumber += 1 // Move to next day
Sometimes you want state that only exists within a specific beat. Use the new
keyword to create temporary state that resets each time you enter the beat:
beat CoffeeTasting
// These values reset every time we enter CoffeeTasting
new state
cupsTasted: 0
currentRoast: light
enjoymentLevel: 5
choice
Try another sip if cupsTasted < 3
cupsTasted += 1
Interesting notes in this one...
Finish tasting
-> OrderDrink
In this example, cupsTasted
, currentRoast
, and enjoymentLevel
reset to their initial values every time the player enters the CoffeeTasting beat.
The heart of interactive fiction is letting readers make choices:
beat OrderDrink
choice
Order a cappuccino
coffeeBeans -= 15
barista: <happy> One cappuccino coming right up!
-> PrepareDrink
Ask about tea options
barista: We have a lovely selection of green and herbal teas.
-> TeaMenu
Just browse the menu
You take your time reading through the extensive drink list.
-> DrinkMenu
Choices can be conditional - only available when certain conditions are met:
beat SpecialMenu
choice
Order special roast if coffeeBeans >= 20
coffeeBeans -= 20
barista: Excellent choice! Our Ethiopian blend is amazing.
-> PrepareDrink
Chat with barista if barista.friendship > 2
barista: <friendly> Want to hear about my coffee journey?
-> BaristaChat
Make your text responsive to the game state using the $
symbol for variable interpolation:
beat CheckInventory
barista: We have $coffeeBeans beans left in stock.
Your total comes to ${price * quantity} dollars.
Characters can also be referenced by their identifier, which will display their name
property:
beat CloseShop
$barista begins cleaning up for the day. // Will show "Alex begins cleaning up for the day"
$customer waves goodbye as they leave. // Will show "Sam waves goodbye as they leave"
Keep your script organized with comments:
// Track customer loyalty
customer.visits += 1
/* Check if we should
trigger the special event */
if customer.visits > 10
-> LoyaltyReward
Here's a complex example putting multiple features together:
beat CoffeeTasting
state
cupsTasted: 0
favoriteRoast: null
lastImpression: ""
barista: <enthusiastic> Ready to explore our new roasts?
choice
Try light roast if cupsTasted < 3
cupsTasted += 1
lastImpression = bright and citrusy
The bright, citrusy notes dance on your tongue.
if chance(3) // 1 in 3 chance
favoriteRoast = light
barista: <happy> I see that spark in your eyes!
-> DiscussTaste
Try medium roast if cupsTasted < 3
cupsTasted += 1
lastImpression = "nutty and balanced"
A pleasant nuttiness fills your mouth.
-> DiscussTaste
Discuss coffee origins if barista.friendship > 1
barista: <passionate> Let me tell you about our farmers...
-> CoffeeOrigins
Finish tasting if cupsTasted > 0
if favoriteRoast != null
-> OrderFavorite
else
-> RegularOrder
beat DiscussTaste
barista: What do you think about the $lastImpression notes?
choice
Express enthusiasm
barista.friendship += 1
-> CoffeeTasting
Nod politely
-> CoffeeTasting
This syntax guide covered the main features of Loreline, but there's always more to discover as you write your own stories. Experiment with different combinations of these features to create rich narratives.
Happy writing!
Loreline scripts are written in .lor
files. See CoffeeShop.lor and Minimal.lor as examples.
You can write these with any text editor, but the best option is using Visual Studio Code along with the Loreline Extension. This will make your editor support syntax highlighting of .lor
files, which makes the content much more readable and easy to work with:
It will also provide code completion and additional info from your script:
A binary to run loreline can be downloaded for your platform in the Releases page.
loreline play story.lor
Alternatively, you can use haxelib:
haxelib install loreline
haxelib run loreline play story.lor
Loreline runtime is written with the Haxe programming language, so it can be transpiled to many target languages such as Javascript, C++, C#, Java bytecode, PHP, Python...
At the moment, it's still early days of this project, so you'll need to use Haxe if you want to integrate loreline in your code, although it is planned in the foreseeable future to make it work out of the box in more languages!
// Load script content
final content = File.getContent('story.lor');
// Parse the script
final script = Loreline.parse(content);
// Play the story
Loreline.play(
script,
// Called to display a text
(_, character, text, tag, done) -> {
if (character != null) {
Sys.println(character + ': ' + text);
}
else {
Sys.println(text);
}
done(); // Call done() when finished
},
// Called to prompt a choice
(_, options, callback) -> {
for (i in 0...options.length) {
Sys.println((i + 1) + '. ' + options[i].text);
}
// Let the user make a choice
final choice:Int = ...;
callback(choice); // Call back with the choice index
},
// Called when the execution has finished
_ -> {
// Finished script execution
}
);
You can also take a look at Cli.hx source code as another reference using Loreline.