Eldrin Scripting Language
Even if you use visual scripting, having a basic understanding of the underlying Eldrin Script is helpful.
Eldrin is a fast, easy-to-understand scripting language based on a virtual machine. To get a basic understanding, let's look at the Orc source code from the Hideout2D example.
This code was automatically generated by the visual script editor:
fn event(event, value) {
if event == "startup" {
random_walk_in_sector( 1.0, 1.0, 4);
set_proximity_tracking( true, 4);
add_item( "GoldenKey");
set_attr( "target", "");
}
if event == "proximity_warning" {
let alignment = get_attr_of( value, "ALIGNMENT");
if alignment > 0 {
close_in( value, 1.5, 1.0);
}
}
if event == "closed_in" {
let target = get_attr( "target");
if target == "" {
let damage = random( 1, 3);
deal_damage( value, damage);
set_attr( "target", value);
notify_in( 4, "attack");
}
}
if event == "attack" {
let target = get_attr( "target");
if target != "" {
let damage = random( 1, 3);
deal_damage( target, damage);
notify_in( 4, "attack");
}
}
if event == "kill" {
set_proximity_tracking( false, 5.0);
goto( "Garden", 1.0);
set_attr( "target", "");
}
if event == "arrived" {
set_proximity_tracking( true, 5.0);
random_walk_in_sector( 1.0, 1.0, 4);
}
if event == "death" {
drop_items( "");
set_attr( "visible", false);
}
if event == "take_damage" {
let from_id = value.subject_id;
let amount = value.amount;
let attacker = get_attr_of( from_id, "name");
message( id(), "You hit the Orc for " + amount + " damage", "");
}
}
Events
The behavior of characters and items is based on an event system. Characters and items are sent events of a certain type with a given value.
Characters and items respond to events by issuing commands and setting attributes.
Events are chained to break complex tasks into small, manageable chunks.
All supported event types are listed in the Events chapter.
The same fn event(event, value) entry point is also used by:
- world scripts under Game / World / Eldrin Scripting
- region scripts under each Region / Eldrin Scripting
Use world scripts for global state and region scripts for map-local state.
Values
Values in Eldrin Script are represented by a built-in data container called a Packet. A packet holds three numeric fields and an optional string and is used for all commands, events, and attributes.
In Rust a Packet would look like this:
pub struct Packet {
pub x: f32,
pub y: f32,
pub z: f32,
pub string: Option<String>,
}
Creating Packets
The following examples all create a valid Packet:
// Creating numerical values
let a = 10.5; // Sets all three numerical fields to 10.5
let a = 10;
let a = vec2(10, 5);
let a = vec3(1, 2, 3.0);
// Creating strings
let a = "This is a string";
let a = "This has a value of: " + 10;
Accessing Packet fields
let b = a; // Creates a copy of a (Packets are copied by value, not by reference)
let b = a.x; // All numerical fields of b are set to the value of a.x
let b = a.xy; // Packets support swizzles
let b = a.string; // b is the value of a.string
// Setting packet fields
b.y = 10.0;
Comparison
When comparing Packets, the VM first checks if one of the packets has a valid string, if yes it compares the strings of the packets, otherwise it compares the .x fields.
if event == "startup" {} // Compares the string values of the packets
if value == 10 // Checks if value.x == 10
if value.y == 5 // Checks if value.y == 5
Field Aliases and Conventions
As events can set multiple fields of a packet, Eldrin supports conventions and aliases.
If a packet contains the id of another character or item, you can reference it with
value.subject_id // an alias for value.x.
As subject_id is an alias for the .x, you can also always just use value directly without the .x reference.
Entity/item IDs are always > 0.
0 is reserved and means "no id" (for example: no current target).
distance and amount are aliases for .y.
count is an alias for .z. Some events send a "," separated list of character or item ids in the string field, you can check how many ids are in the list via .count.
In this example the door checks how many entities are in its radius (to test if it is safe to close the door):
if event == "close_door" {
let entities = entities_in_radius();
if entities.count == 0 {
set_attr( "visible", true);
set_attr( "blocking", true);
}
else {
// Somebody blocks the door, test again later.
notify_in( 2, "close_door");
}
}
Runtime Render And Post State
World and region scripts can write runtime render state through namespaced variables.
Example:
fn event(event, value) {
if event == "startup" {
let world.render.pal.start = 0;
let world.render.pal.end = 9;
let world.render.pal.mode = "nearest";
let world.render.pal.blend = 1.0;
let region.render.background_color_2d = "#272744";
let region.render.fog_color = "#20242c";
let region.render.fog_density = 5.0;
let world.post.enabled = true;
let world.post.tone_mapper = "aces";
let world.post.exposure = 0.9;
let world.post.saturation = 0.7;
}
}
The most commonly used paths are:
world.render.pal.startworld.render.pal.endworld.render.pal.modeworld.render.pal.blendworld.render.background_color_2dworld.render.fog_colorworld.render.fog_densityworld.post.enabledworld.post.tone_mapperworld.post.exposureworld.post.gammaworld.post.saturationworld.post.luminance
Every world.* path also exists as region.*.
Render and post values are resolved in this order:
- Project configuration defaults
- World script overrides
- Region script overrides