Lua API reference
Generated from the engine's bindings each time the site is built (tools/docs_site/lua_api.py), so it lists exactly what exists on main. How to use them, with examples, is in Scripting; the tutorials use them step by step.
Each table exists only when its module is in the game: no RigidBodyModule, no physics. Arguments in {} are one table of named fields, for example physics.box{pos = Vec(0, 1, 0), size = Vec(1, 1, 1)}.
Vec
hook
timer
__kke_nettable
__kke_netview
ai
Creatures with a mind ("Creatures" below, docs/AI.md "Lua"): add(inst, species), orders goTo/follow/attack/hold/flee/fetch/free, anims(inst, {idle, walk, run, ...}), actor(id, pos [, species, velocity]), doing, need, knows, noise, ...
audio
impact(pos, material, intensity) (material id or name), materials() → {Stone = 1, Wood = 2, ...}; the game's own files (AUDIO.md "Sounds and music from scripts"): sound(file) → id, play(sound, {pos, volume, pitch, loop, near, range, fade, category}) → voice, stop(voice [, fade]), move, volume(voice, v [, seconds]), playing; music(file or nil [, {fade, volume}]) crossfades; bus(category [, v [, seconds]]), snapshot({music = 0.4, ...} [, seconds]), duck(by, target [, {volume, attack, release}]), clearDucks()
breakable
FEMFX objects that really break. box{pos, size, material, pattern, cells, chunk, velocity, arm} → id (material glass, stone, wood, ice, iron; pattern shards, voronoi, splinters, radial, solid, default by material), ball{pos, radius, velocity, material} → id (iron by default: a projectile), remove(id), broken(id), pieces(id), count(). The Break hook says when one breaks.
camera
position(), target(), forward(), fov(): what the player sees (during a cutscene, its shot). Cutscenes in any game: path(shots, opts) (shots {pos=, look=, fov=, t=}; opts loop, hold, blend, blendOut, seconds when the shots have no t; returns its length), hold(pos, look [, {fov=, blend=}]), release([blend]), directing(), shake(strength 0..1 [, seconds]). Hook CameraDone when a path ends (cameras, kke/CameraDirector.h)
clock
Time of day (MOODS.md "Time of day"): start{hour, dayLength, noon, sunrise, sunset}, set(hour), pause, resume, stop, hour(), time() → "07:30", isDay(), running(), sun() → direction, elevation, dayLength([seconds]); hooks Dawn, Dusk, Hour (e.hour)
decal
Marks laid onto surfaces (DECALS.md): place(texture, pos, opts) → id, or nil and the reason (a built-in, textures() lists them, or assets/decals/<name>.png); opts: normal (out of the surface, default up), size (m, default 1), depth, angle (radians), color, opacity, roughness (0.05 wet, -1 keeps the surface's), life and fade (s; 0 stays); remove(id) → true if it was there, clear(), count(). 1024 at once; the oldest go first
fx
Particle effects by name (effects.lua, the list): play(name, pos, opts) → true, or nil and the reason (an unknown name); start(name, pos, opts) → id (keeps going), move(id, pos, opts), stop(id) (what's in the air finishes), count() → this script's running effects, names(), define(name, effect) → true, or nil and what's wrong (an effect as a Lua table, the fields of the YAML), wind(vec) (m/s; smoke and flakes drift); opts: normal, velocity, scale, floor. 8000 particles for all scripts together; the oldest go first
define(id, label, defaultKey [, padButton]) (pad button names: "a", "b", "x", "y", "lb", "rb", "lt", "rt", "start", "back", "dpad_up"...), pressed(id), held(id), value(id); actions show up in the rebinding screen like any other. Button prompts (INPUT.md "Button prompts"): style([player]) → "keyboard", "xbox", "playstation", "switch", "steamdeck", "steamcontroller" or "touch", the device the player uses now; prompt(action or button [, label, player]) → RML with the button's picture (for ui.rml); promptText("{jump} jump, {sprint} run" [, player]) → RML; glyph(action or button [, player or style]) → the picture's path (for your own <img>), or nil + why. In documents, <prompt action="jump" label="Jump"/> redraws by itself.
kke
log(...), time(), dt() (and plain print)
light
Lamps (lamps.lua): point{pos, color, intensity, range, shadows} → id, or nil and the reason when every light is in use (16 in all: light 0 is the sun, and a game's own lights take slots too); range in metres (default 10; 0 = reaches everywhere); shadows = true casts shadows (the first 4 lights that ask); move(id, pos), set(id, {color, intensity, range, shadows, on}) (missing fields stay as they are), remove(id), count() → this script's lights
models
load(name) → model (an asset name: a model in the game's own models/ folder by file name, e.g. "Fox" for models/Fox.glb, or from an installed pack, e.g. "SM_Prop_Crate_01", or a path inside the game's folder; nil + reason if missing), spawn(model, {pos, yaw, scale, tint}) → instance, move(inst, pos [, yaw, scale]), remove(inst), tint(inst, Vec), visible(inst, bool), play(inst, clip [, loop, speed]) (clip name or number; nil stops), event(model, clip, seconds, name) (an animation event: the AnimEvent hook runs each time a script model playing that clip passes seconds, a looping clip every cycle; event(model, clip, nil) clears the clip's; MOVEMENT.md "Animation events"), clips(model) → names, bounds(model) → min, max, position(inst) → pos, yaw
mood
The sky, sun, fog, colour look and ambience in one go (MOODS.md): set(name) → true, or false and the reason ("clear_day", "golden_hour", "sunset", "night", "misty_morning", ... or a mood file of the game's own in moods/), current() → name, list() → names
nav
Pathfinding ("Finding the way" below): build({radius, height, climb, slope, cell, tile, dynamic}) → true, info, path(from, to) → points, partial, walkable(from, to), nearest(pos [, reach]), move(from, to), random(centre, radius), obstacle{pos, size, yaw} or {pos, radius, height} → id, remove(id), ready()
net
role() ("offline", "host", "client"), isServer(), connected(), playerId(), players() → { {id, name}, ... }, send(name, data): from a client to the host, from the host to every client; data is nil, a boolean, number, string or a table of those (about 500 bytes encoded, one network event); call(name, data [, function(ok, answer) end]) → sent? and handle(name, function(data, from) end): a question to the host and its answer ("Calls" below); table(name) → a synced table (host/server only: :set(key, row), :get(key), :remove(key), :rows(), :count(), :clear()) and watch(name [, filter] [, function(event, key, row, old) end]) → a view (:rows(), :get(key), :ready(), :count(), :stop()) ("Synced tables" below)
physics
box{pos, size, density, material, color, velocity, bounce, friction, static, visible, glass, cloudy, group} / sphere{pos, radius, ...} → id (visible = false: collides but isn't drawn, an invisible wall; glass = true: see-through, tinted by color, cloudy 0 clear to 1 milky; the look reaches every player); showHidden([on]) → on: draw the invisible ones faintly, on this machine (building a level; also a checkbox in the F1 Scripts panel); remove(id), position(id), velocity(id), setVelocity(id, v), impulse(id, v [, point]), raycast(from, dir [, maxDist]) → {pos, normal, distance, body, material} or nil, count(); joints ("Joints" below): joint{type, a, b, pos, posB, axis, min, max, frequency, damping, friction} → joint or nil + reason, motor(joint, mode [, target, maxForce]), jointValue(joint), removeJoint(joint)
saves
Save slots for a "Load game" menu (SAVING.md): write(slot, data [, {title, playtime}]), read(slot) → data, info, list() newest first, exists, remove, version([n]), migrate(from, fn)
scene
list() → names in the game's own scenes/ folder and the shared scenes/, load(name, origin) → scene, missing count (or nil + reason), unload(scene), spawnPoint(scene) → pos, yaw
server
store
What outlives the session ("Saving" below): save(name, value) → true or false, reason; load(name [, default]); add(name [, n]) → new count; remove(name); keys([prefix]) → names in order
terrain
Hills and valleys (TERRAIN.md): create{size, resolution, pos, height, seed, ridged, image, flat, layers} → true, height(x, z) → y, on, ground(pos), normal, slope, raise/lower(pos, radius, metres), smooth(pos, radius [, strength]), flatten(pos, radius [, height, strength]), paint(pos, radius, layer [, strength]), scatter{model, density, layer, maxSlope, scale, align, solid, clear} → group, count, unscatter(group), scattered(), layer, info, remove
ui
RmlUi documents. open(rml) / load("file.rml") (next to the scripts) → doc, text(doc, id, text) (plain text, shown as typed), rml(doc, id, markup), class(doc, id, name, on), property(doc, id, name, value), show(doc, bool), close(doc), onClick(doc, id, fn), profile() → "phone", "desktop" or "console" (the kind of screen, INPUT.md "Phone, PC and console"; every document's body also has the class kke-phone/kke-desktop/kke-console and kke-portrait/kke-landscape for RCSS)
Note
Not described in SCRIPTING.md yet: __kke_nettable, __kke_netview, server.
Your own tables
A game adds its own with ScriptVM::registerFunction from a C++ module. The starter template's player table is the example (tutorial 3):
player