LightningScript is the AngelTech Engine's scripting language for defining entity behaviors, world interactions, and dynamic events. It replaces the legacy ParasiteScript system.
Files use the .ozls extension.
.ozls)entity "<name>" : <type> {
mesh = "<path>"
texture = "<path>"
icon = "<path>"
stats {
<name> = <value>
vec3 <name> = (<x>, <y>, <z>)
}
variants {
"<variantName>" { mesh_override = "<path>" texture_override = "<path>" }
}
actions {
on_<action> {
<script lines>
}
}
}
| Type | Purpose |
|---|---|
weapon |
Ranged/melee weapon (hotbar, projectile dispatch) |
armor / upgrade |
Equipment items |
consumable |
Consumables (use via hotbar on_use) |
pickup |
World pickup (item_id/respawn_time stats) |
skyzone |
Zone volume with env/fog/skybox/zone actions |
pawn |
NPC hook-carrier (FSM actions: on_patrol/on_chase/on_return/on_death) |
projectile |
Reserved (no runtime consumer yet) |
Defines runtime numeric properties:
stats {
damage = 25.0
magazine = 12
vec3 color = (1.0, 0.2, 0.1)
}
| Stat | Type | Ranged Default | Melee Default | Description |
|---|---|---|---|---|
damage |
float | 10 | 10 | Damage per projectile or melee hit |
fire_rate |
float | 0.25 | — | Seconds between shots (ranged) |
swing_speed |
float | — | 0.25 | Seconds between swings (melee) |
magazine |
float | none | — | Ammo capacity per magazine (ranged only) |
reload_time |
float | 2.0 | — | Seconds to complete a reload |
projectile_speed |
float | 20 | — | Projectile travel speed in units/sec |
projectile_lifetime |
float | 2.0 | — | Projectile lifespan in seconds (legacy key lifetime also accepted) |
projectile_count |
float | 1 | — | Number of projectiles per shot |
spread |
float | 0 | — | Random spread angle in degrees |
reach |
float | — | 3.0 | Melee attack range in units |
Sound is authored per-entity in the same stats block. Values are asset paths;
they resolve def-relative first, then via the package loader, so a .wav
inside a .ozsnd works exactly like a loose file.
| Stat | Type | Applies To | Default | Description |
|---|---|---|---|---|
fire_sound |
string | ranged weapon | Audio/Sounds/Guns/machgf3b.wav |
One-shot per projectile fired |
swing_sound |
string | melee weapon | (silent) | One-shot per melee swing |
hit_sound |
string | melee weapon | (silent) | Melee connect that is not blocked by geometry |
reload_sound |
string | any weapon | (silent) | Once per reload (manual and auto) |
equip_sound |
string | any weapon | Audio/Sounds/Entities/weapload.wav |
Selecting the weapon in the hotbar |
fire_volume / swing_volume / hit_volume / reload_volume / equip_volume |
float | matching key | 1.0 | Linear gain |
fire_pitch / swing_pitch / hit_pitch |
float | matching key | 1.0 | Playback rate (0.85 = deeper) |
Player sound lives on Player.ozls (GameData/Tex/Global/Models/), read off the
auto-spawned Player entity: jump_sound, land_sound, walk_sound,
run_sound, hurt_sound, death_sound, each with a _volume.
stats {
fire_sound = GameData/Audio/Sounds/Guns/machgf3b.wav
fire_volume = 0.9
fire_pitch = 0.85
}
Authoring rules — these fail silently if broken:
stats string value is stored verbatim; thefire_sound = a/b.wav, never fire_sound = "my gun.wav". A space truncatesfire_sound and equip_sound fall back to aswing_sound,hit_sound, reload_sound and land_sound have no fallback — anhit_sound fires only on a connect that cleared geometry. A swing that stopson_hit and makes no connect sound.run_sound is chosen off the sprint key while the player is moving;walk_sound otherwise. Both are loops.All of these are editable from the AngelEd Property Window — see
Editor-Usage.md.
Scripts attached to specific events:
| Action | Trigger |
|---|---|
on_use |
Player selects the entity from hotbar and presses E/Enter |
on_equip |
Entity is equipped |
on_unequip |
Entity is unequipped |
on_fire |
Ranged weapon fired (before projectile spawn) |
on_swing |
Melee weapon swung (before range check) |
on_hit |
Melee swing connects with a target |
on_reload |
Weapon reload triggered (auto or manual via R key) |
on_collect |
Entity is picked up |
on_enter |
Player enters a zone/skyzone volume |
on_exit |
Player exits a zone/skyzone volume |
on_tick |
Called every frame while active |
on_patrol / on_chase / on_return / on_death |
Pawn FSM state transitions (requires a pawn-named .ozls, e.g. Walker.ozls) |
| Opcode | Syntax | Description |
|---|---|---|
var |
var x = 5 |
Declare int (no decimal) or float (has decimal) |
$ |
$x = 42 |
Assign to runtime variable (creates if not exists) |
$ += |
$x += 5 |
Add and assign |
$ -= |
$x -= 3 |
Subtract and assign |
$ *= |
$x *= 2 |
Multiply and assign |
$ /= |
$x /= 2 |
Divide and assign |
| RHS arithmetic | $x = $x - 1 |
Assignments support one binary op (+ - * /) with variables/numbers |
Variables are referenced in conditions with $ prefix (e.g., $health > 0).
$flag<idx> (e.g. $flag0) reads the instance's toggle flags set by wtflag/toggle_flag.
| Opcode | Syntax | Description |
|---|---|---|
if / else / endif |
if ($x == 0) ... endif |
Conditional execution (brace style if (...) { ... } also supported) |
goto / jump |
goto label_name |
Jump to a label |
stop / end |
stop |
Halt script execution |
say |
say "hello" |
Print to log |
set_cooldown |
set_cooldown 1.5 |
Set cooldown timer in seconds |
| Opcode | Syntax | Description |
|---|---|---|
wtflag |
wtflag idx val |
Write toggle flag (0-63) |
rtflag |
rtflag idx |
Read flag value into $result |
toggle_flag |
toggle_flag idx |
Flip a toggle flag (0-63) |
Hotbar/equipment instance flags are persisted in save games (flags= section). Zone-instance flags are session-only.
| Opcode | Syntax | Description |
|---|---|---|
set_fog |
set_fog r g b density |
Set fog color and density |
restore_fog |
restore_fog |
Restore fog to world default |
set_ambient |
set_ambient r g b |
Set ambient light color |
restore_ambient |
restore_ambient |
Restore default ambient |
set_skybox |
set_skybox "name" |
Set skybox by name |
restore_skybox |
restore_skybox |
Restore default skybox |
play_sound |
play_sound "path" |
Queue sound playback (package-aware: resolves .ozsnd via SoundManager::PlayScriptSound) |
msg |
msg "text" |
On-screen text message |
heal / damage |
heal 25 |
Immediate player health delta |
playerstat |
playerstat health -= 5 |
Deferred write to a player stat |
consume |
consume |
Mark consumable as used (removes from hotbar) |
spawn_pawn |
spawn_pawn "Walker" x y z |
Spawn an NPC |
spawn_pickup |
spawn_pickup "Coin" x y z [respawn] |
Spawn a pickup |
These queue a deferred ScriptCommand the host applies after the action body
runs. Engine-generic ones are applied by LightningEntityManager; the ones that
need the camera or world loader are drained by Core.hpp.
| Opcode | Syntax | Effect |
|---|---|---|
emit_event |
emit_event "zone.enter" [int] |
Publish an EventBus event (topic, optional int). The bridge runs on_<topic> on the def named by the event's s0. |
objective |
objective <id> += 1 |
Advance an ObjectiveSystem objective (=, +=, -=); emits objective.progress/objective.complete. |
set_worldflag |
set_worldflag <name> <value> |
Write a cross-instance world flag (survives across entities, serialised with the world). |
get_worldflag |
get_worldflag <name> |
Read a world flag into $result. |
give_xp |
give_xp 50 |
Add player XP. |
grant_item |
grant_item "Medkit" [qty] |
Spawn an entity def and place it in the hotbar. |
teleport |
teleport x y z |
Move the player (engine Y-up space). |
change_world |
change_world "Dust_Ravine" |
Request a world load. |
apply_area_damage |
apply_area_damage <radius> <damage> |
Damage pawns around the player. |
spawn_projectile |
spawn_projectile <speed> <damage> <life> [count] [spread] |
Fire a projectile from the player. |
play_clip |
play_clip "Attack" |
Plays a named one-shot skeletal clip and returns to Idle. Two legs: a forced clip on matching pawns (PawnSystem::PlayPawnClip), and — when the command matches the held weapon def — the first-person view-model (ViewModel::PlayClip). The no-pawn warn fires only when neither leg plays. |
The engine publishes facts on the EventBus; a def subscribes by declaring an
action named on_<topic> with . replaced by _:
| Topic | Action | Emitted when |
|---|---|---|
world.load |
on_world_load |
A world finishes loading |
entity.death |
on_entity_death |
A pawn's FSM enters DEAD (s0 = pawn def name) |
entity.chase / entity.patrol / entity.return |
matching on_entity_* |
Pawn FSM transitions |
pickup.collect |
on_pickup_collect |
A pickup is collected (s0 = pickup def name) |
player.hurt / player.death |
on_player_hurt / on_player_death |
Player takes damage / dies |
trigger.enter / trigger.exit |
on_trigger_enter / on_trigger_exit |
Player crosses an OZONE trigger volume (s0 = trigger def name) |
A trigger line places a volume whose meaning is a : trigger def:
trigger Trigger_EnterVolume minX minY minZ maxX maxY maxZ [once] [team=N]
On the player's entry/exit the engine emits trigger.enter / trigger.exit
with the def name; once latches after the first entry.
Used in if statements within action blocks (both styles work):
if ($health <= 0) {
say "Player defeated"
end
}
if ($result == 0)
say "first time"
endif
Supported operators: ==, !=, >, <, >=, <=, plus &&, || and
parentheses (e.g. if (($a == 1 || $b == 2) && $c > 0)).
entity "automag" : weapon {
mesh = "automag_lvl1.obj"
texture = "automag_lvl1_texture.png"
stats {
damage = 15
fire_rate = 0.4
magazine = 12
reload_time = 2.0
projectile_lifetime = 2.5
}
actions {
on_fire {
say "Bang! (15 damage)"
set_cooldown 0.4
}
on_reload {
say "Reloading..."
}
}
variants {
"lvl1" { mesh_override = "automag_lvl1.obj" }
"lvl2" { mesh_override = "automag_lvl2_alt.obj" }
"lvl3" { mesh_override = "automag_heavy_rifle_lvl3.obj" }
}
}
variants are resolved when the instance spawns: the engine parses the lvlN
name for its tier and picks the highest tier that does not exceed the player's
level (clamped to the highest declared tier). A level-1 player therefore always
sees lvl1. The chosen variant applies to both the world pickup and the
first-person view-model. Names without an lvlN prefix are treated as tier 0.
Override mesh_override paths are resolved relative to the def's own directory,
so include the file extension.
A skyzone entity can author its atmosphere directly rather than through
set_fog / set_ambient opcodes. These are applied when the player enters the
zone:
entity "skyzone_citadel" : skyzone {
skybox = "..."
music = "..." // asset prefix, handed to SoundManager::PlayWorldMusic
fog_color = (0.62, 0.52, 0.46)
fog_density = 0.0022
ambient_light = (0.50, 0.46, 0.46)
actions {
on_enter { msg "..." }
on_exit { restore_fog }
}
}
fog_color, fog_density and ambient_light are applied on on_enter; the
restore_fog / restore_ambient / restore_skybox opcodes revert them to the
level defaults. Note the vec3 syntax is name = (x, y, z) — a vec3 prefix
does not parse.
entity "acidpool" : skyzone {
actions {
on_enter {
wtflag 0 1
var timer = 60
play_sound "GameData/Audio/Sounds/Entities/Hurt.mp3"
msg "Acid! The pooled run-off burns your boots!"
}
on_tick {
if ($flag0 >= 1) {
$timer = $timer - 1
if ($timer <= 0) {
playerstat health -= 5
var timer = 60
}
}
}
on_exit {
wtflag 0 0
msg "You scramble clear of the acid."
}
}
}
.ozls files are parsed by LightningScriptParser into EntityDef structs (recursively scanned from all of GameData/ + packages)LightningEntityRegistry stores all registered definitionsLightningEntityManager manages runtime instances with individual LightningScriptContext per instanceon_equip/on_unequip; use triggers on_usePawnSystem calls zone actions on entry/exit; pawn FSM transitions dispatch the matching on_* action