Skip to content

CustomGame Objects

wezzzyrek1 edited this page Sep 18, 2026 · 1 revision

Objects and the sandbox

Map scripts and monster scripts share one Lua state, one object API and three shared files. This page is the reference for all of it.

The object

Every handler that receives an o receives the same thing, whether it came from Map.OnObject, Monster.OnRender or Map.Ambient(i). Properties are read and written directly.

Readable and writable

A tile is 100 world units. Colours and brightness are floats from 0 to 1.

Property Meaning Example
scale size multiplier; 1.0 is the model's own size o.scale = 1.4
alpha opacity: 0 invisible, 1 solid o.alpha = 0.5
timer a counter of the object's own; some client objects step their animation on it, and a script driving an object can do the same o.timer = o.timer + 0.1
hiddenMesh which part of the model is not drawn: -1 nothing, every mesh is drawn; 0, 1, 2... that one mesh (a monster's m.meshCount says how many it has); -2 the whole model, while its script and effects keep running o.hiddenMesh = 2 hides mesh 2, o.hiddenMesh = -2 everything
blendMesh a mesh drawn glowing (added) instead of solid; -1 none o.blendMesh = 0
blendMeshLight brightness of the blendMesh: 0 invisible, 1 full o.blendMeshLight = 0.65
blendMeshTexCoordU blendMeshTexCoordV texture offset, 1 being one whole texture; set it from world time every frame to scroll o.blendMeshTexCoordV = -(Map.WorldTime() % 10000) * 0.0001 - one turn every 10 s
positionX positionY positionZ world position: tile 184, 23 is x 18400, y 2300; Z is the height o.positionZ = Map.TerrainHeight(o.positionX, o.positionY)
angleX angleY angleZ rotation in degrees; angleZ turns it round the vertical axis - which way it faces o.angleZ = 90
lightR lightG lightB the object's light colour; 1, 1, 1 is full brightness o:SetLight(1, 0.6, 0.2) - warm orange
boundingBoxMaxX/Y/Z far corner of the box the client clicks and culls the object by, in world units from its origin Hydra: 100, 100, 150
renderShadow this object's own shadow switch o.renderShadow = false
enableShadow the wider shadow gate over renderShadow o.enableShadow = false
shadowScale size of the shadow birds: 10
visible whether the camera sees it - the client works it out again every frame, so it is for reading if not o.visible then return end
live the object exists; on a Map.Ambient or Map.Boid slot false frees it, and true, set last, starts it m.live = true
velocity speed per frame of an ambient or boid slot the client moves birds: 1
gravity on an ambient or boid slot, how many degrees a frame it may turn while flocking motes 9, birds 13
lifeTime frames an ambient object has left motes: 100
ai a boid's state: 0 flying, 1 diving, 2 on the ground, 3 climbing - see Birds in Map scripts b.ai = 0
directionX/Y/Z a boid's movement; directionZ is its climb per frame b.directionZ = -20 - a dive
alphaTarget the alpha the client's own fade moves towards o.alphaTarget = 1
alphaFadeRate the part of the remaining gap that fade covers each frame 0.12 - about a second
alphaEnable the blended render path - leave it off on map objects, it breaks their shadow and depth; fade them through alpha as Fading roofs does ambient motes: true
lightEnable lit by the terrain light where it stands m.lightEnable = true
billBoard always turned to face the camera, like a sprite motes: true
eyeLeftX/Y/Z where a trail created on the object hangs; keep it on the object for as long as the trail should follow m.eyeLeftX = m.positionX
type the object's type: on a map the model it was placed as, rewritten by the client every frame; on an ambient or boid slot the model to fly m.type = Helpers.Model(R, "bug")
subType the type's variant; most objects use 0 m.subType = 0

Read only

Property Meaning Example
id a stable key for this object, for script-side state kept in a table state[o.id] = { phase = 0 }
animation the object's animation number; with type it picks the object's row in the object tables 0
currentAction the action playing now - see Actions if o.currentAction == Enums.Action.Walk then

hiddenMesh = -2 hides the entire model, which is the usual way to keep an invisible marker object and draw your own effect at its position.

Methods

Method What it does Defaults
o:SpawnParticle(type, subType, scale, x, y, z) a particle, tinted with the object's light; type is a picture id - Helpers.Texture(R, "smoke") subType 0, scale the object's, x, y, z the object's position
o:SpawnSprite(type, scale, rotation, subType, cols, rows, ticks, x, y, z, r, g, b) a flat picture facing the camera, rotation in degrees, optionally animated - see Sprite sheets scale the object's, rotation 0, subType 0, cols, rows, ticks 1, position the object's, r, g, b its light
o:SpawnEffect(type, subType, scale, x, y, z, r, g, b, angleX, angleY, angleZ) one of the client's model effects, type from Helpers.ClientModel(R, name) - see Client effects subType 0, scale 0 - the effect's own size, position the object's, r, g, b its light, angles its angles
o:SpawnJoint(type, subType, scale, targetX, targetY, targetZ) one of the client's trails from the object to a point, type a client joint id - none ship, a trail of your own picture is Map.Beam; scale is its width subType 0, scale 10, target the object's position - a spark in place
o:TransformPosition(bone, dx, dy, dz) a model-local offset through bone bone → world x, y, z; nothing for a bone the model does not have dx, dy, dz 0
o:SetAction(action) plays an action from Enums.Action - and, with <SoundAction>, its sound; one the model does not have is ignored -
o:SetLight(r, g, b) sets lightR/G/B at once, 0..1 a missing value is 0
o:AddTerrainLight(r, g, b, range) lights the ground around the object, r, g, b 0..1, range in tiles range 3
o:SetTexCoord(u, v) sets blendMeshTexCoordU/V at once a missing value is 0
o:GetPosition() → x, y, z -

TransformPosition returns nothing for a bone the model does not have, so always check:

local x, y, z = o:TransformPosition(4, 0, -20, 0)

if x then
    o:SpawnParticle(smoke, 0, 1.0, x, y, z)
end

AddTerrainLight's range defaults to 3 - enough that a lava pit lights the rock around it. Call it every frame for as long as the ground should glow; the light is laid down once per 40 ms game step - see Light and sound in Map scripts.

Sprite sheets

SpawnSprite's last three arguments animate it: the texture is cut into cols x rows cells, the frame advances every ticks of world time, and the frame count is cols * rows. All three default to 1, meaning no animation. Values below 1 are raised to 1.

The sandbox

Scripts run in an isolated state with a deliberately small standard library:

Available: base, table, string, math, and from os only time, clock, date and difftime.

Not available: ffi, jit, package, io, require on anything outside the shipped files, loadstring, os.execute / remove / rename / getenv / exit, and every Lua UI binding.

There is no bit library, so flag tests are arithmetic:

local isMonster = math.floor(kind / Enums.Kind.Monster) % 2 == 1

os.* is wall clock. For anything that animates, use Map.WorldTime() - it follows the client's own clock, so effects do not drift with the frame rate or keep running while the game is paused.

Cross-calling

Map.* may only be called from a map script and Monster.* only from a monster script, even though both are visible everywhere. A mismatch is an error at load time rather than a handler that silently replaces someone else's.

Shared files

Five shared files sit next to the script folders. A script loads one with require("Name"); each loads once per state and is shared from then on.

Data\Custom\Scripts\Game\Enums.gsc          names
Data\Custom\Scripts\Game\Helpers.gsc        functions
Data\Custom\Scripts\Game\Registry.gsc       what may be loaded, and from where
Data\Custom\Scripts\Game\Weather.gsc        weather presets
Data\Custom\Scripts\Game\SkillEffects.gsc   monster skills round the caster

Enums

Names only - no functions.

Table Contents
Enums.Action animation actions
Enums.AttackAction the attack actions in <AttackRate A1..A5> order
Enums.RenderFlags body render flags
Enums.Skill skill ids
Enums.Kind what an object represents - bit flags
Enums.TerrainWall what a tile allows, as Map.TerrainWall returns it - bit flags
Enums.Color named colours, packed 0xRRGGBBAA

Actions

The numbering is not contiguous - the extra attacks were appended after the original eight, so Attack3 is 8 rather than 5.

Name Value Name Value
Stop1 0 Die 6
Stop2 1 Appear 7
Walk 2 Attack3 8
Attack1 3 Attack4 9
Attack2 4 Run 10
Shock 5 Attack5 11

o:SetAction() is safe with any of them - an action the model does not have is dropped. The client starts Appear and Run only for a few of its own monsters; on a custom monster they play when a script sets them.

Colours

Enums.Color values are packed 0xRRGGBBAA, which is what Log.AddC takes. It is not what lights take - SetLight, bodyLight* and the colour arguments of SpawnSprite / SpawnEffect all want three 0..1 floats, so run the value through Helpers.LightOf first:

o:SetLight(Helpers.LightOf(Enums.Color.Gold))

Client effects are found by their file

Some of the client's effects decide what they do by their id - a thrown stone's arc, Inferno's ring of fire - so a copy of the model in a slot of your own would only stand still. Such an effect is named in Registry.lua by the file the client loads it from, and Helpers.ClientModel(R, name) asks the plugin which id the client gave that file. No number sits in the script or in the plugin, so a new client needs no id hunting:

local inferno = Helpers.ClientModel(R, "inferno")

if inferno then
    o:SpawnEffect(inferno, 0, 0, x, y, z)
end

Anything that is only a picture - a laser, a trail, a flame - is drawn from your own copy instead, with o:SpawnSprite or Map.Beam.

A few of the client's effects are built in its code and load no file at all - Raining Arrow's rain is one. Those cannot be named this way, so SkillEffects.lua works the id out from the files of the effects sitting next to it and refuses it unless they line up; see Monster skills. Either way no script writes a client number down.

Helpers

Function Purpose
Helpers.Oscillate(periodMs) smooth 0..1 oscillation
Helpers.PhaseOf(o) per-object phase from its rotation, no state kept
Helpers.AtBones(o, bones, fn) run fn(x, y, z) at each bone, skipping missing ones
Helpers.RenderLight(o, texture, scale, bone, ox, oy, oz) a pulsing sprite pinned to a bone
Helpers.RenderLightAt(o, texture, scale, x, y, z) the same at a position already worked out
Helpers.LightOf(rgba) unpacks 0xRRGGBBAA into three 0..1 floats
Helpers.Texture(R, name) texture id for a Registry name, loading on first use
Helpers.Model(R, name) model id for a Registry name, loading on first use
Helpers.ClientModel(R, name) the id the client gave one of its own model files, for a client effect - see Client effects
Helpers.Sound(R, name) sound id for a Registry name, loading on first use
Helpers.NewIdSet(startAt, maxCount, label) a named id pool
Helpers.FadeUnder(o, cfg) fades a roof while the player is under it - see Fading roofs
Helpers.FadeArea(o, area) the same for whatever stands in a rectangle of tiles
Helpers.FadeAboveRoofs(o) whatever hangs over the player in a whole + upper building opens with its roof
Helpers.RegisterAll() registers every object type, at load

Use RenderLightAt when several lights share one bone - the transform is then done once instead of per light.

Registry

Data only: what may be loaded and from where. Adding a picture is a one-line change.

R.file =
{
    -- a plain string is the common case
    raindrop = "World1\\rain01.tga",

    -- a table adds the wrap mode and the filter
    sand01 = { path = "Effect\\sand01.jpg", repeat_ = true },
}

R.modelFile =
{
    bug = { folder = "Data\\Object161\\", name = "Bug", index = 2, file = "Bug02.bmd" },
}

-- a client effect, by the file the client loads it from - for Helpers.ClientModel
R.clientModel =
{
    inferno = "Data\\Skill\\Inferno01.bmd",
}

R.sound =
{
    -- any .wav, the client's own or a file of yours
    rain = "Data\\Sound\\aRain.wav",
}
Key Meaning
path file, written as the logical .jpg / .tga name - the loader finds the packed form
repeat_ wrap mode. Required for anything whose texture coordinates scroll past 1, or the edge pixels smear across the screen
smooth mipmapped filter. Only for a picture that has mipmaps - without them it draws as flat white

Then in a script:

local R = require("Registry")
local sand = Helpers.Texture(R, "sand01")

Resolve on first use rather than at load - a picture needs a map that is already up. Asking again is free once the slot is filled.

This Registry.lua has nothing to do with the Lua UI one. Different state, different pools, and the same name in both means two different things.

Logging

Function Purpose
Log.Add(text) one line to the Lua console
Log.AddC(color, text) the same, coloured
RGBA(r, g, b, a) packs a colour for Log.AddC

The console only exists in dev mode, so both are no-ops otherwise and safe to leave in. Use string.format for anything with values in it.

See Also

Clone this wiki locally