Skip to content

World tools (tm.worldtools) ​

Client side only. In-world tooling for admin panels and editors: zone outlines, position pickers, ghost vehicles and peds, gizmo and keyboard placement, camera shots and appearance hooks.

lua
local spot = tm.worldtools.vehicle.place('sultan', nil, nil)
if spot then print(spot.coords.x, spot.heading) end

The tools are split by sub-module. Every one of them blocks the calling thread until the player confirms or cancels, so call them from a thread, an event handler or an NUI callback.

Sub-moduleWhat it is
tm.worldtools.groundFloor probe and model origin height.
tm.worldtools.zonesDraw, seed and outline poly, box and sphere zones.
tm.worldtools.coordsThe "stand here and press a key" position picker, multi pick, teleport.
tm.worldtools.vehicleGhost car placer and ghost cars at a list of spots.
tm.worldtools.pedGhost ped.
tm.worldtools.gizmoDrag a prop, ghost car or ghost ped with tm.gizmo.
tm.worldtools.objectKeyboard first placer for a prop you own.
tm.worldtools.lookCopy or design a look through a connected appearance resource.
tm.worldtools.cameraFrame a shot or record a rail with a freecam, preview them.
tm.worldtools.mathThe placement math the tools use.

Working with panels ​

Pass your panel's withHidden(fn) wrapper to anything that takes the player into the world. The wrapper hides the NUI and releases focus, runs fn and restores:

lua
local function withHidden(fn)
    SetNuiFocus(false, false)
    local result = fn()
    SetNuiFocus(true, true)
    return result
end

registerCallbacks(opts) on a sub-module registers the NUI callbacks the shared UI kit fields talk to. opts.guard is a fun(): boolean; return false to refuse (the reply is { ok = false, error = '...' } with a localized reason). Guard privileged operations. Replies are { ok = true, data = ... }, or { ok = false, error } / { ok = false, cancelled = true }.

Every tool holds tm.inputlock while it runs, so key mappings (inventory, radial) stay quiet. When the calling resource stops or the character unloads (tm_lib:fw:playerUnloaded), every running tool is ended: ghosts, outlines, cameras, gizmos and help text are removed. tm.worldtools.clearAll() does the same on demand (call it when the panel closes).

Dependencies ​

worldtools looks these up when a tool needs them, so a missing one disables only the tools that use it:

ImportUsed for
tm.creatorzones.draw, coords.pick, coords.pickMany.
tm.gizmogizmo.place and the gizmo mode of the other placers.
tm.animA ped stand-in or camera ghost that acts out an anim. Optional.
tm.freecam, tm.inputlockThe camera in the placers, and the input lock.

Help panels and toasts go through tm_lib's interface (tm.showTextUI, tm.notify), and every string is a locale key under worldtools. in tm_lib's locales/en.json.

Settings ​

Read at the moment of use, in the tm_lib namespace:

KeyDefaultEffect
worldtools.ghostAlpha150Alpha of ghost cars (ghost peds are 50 above it).
worldtools.outlineAlpha70Alpha of the zone outline faces.
worldtools.spotRadius120.0How close the player must be for vehicle.showSet ghosts to exist. They despawn 40 m further out.

Ground ​

tm.worldtools.ground.findSurfaceZ(x, y, z, ignore?, above?) ​

z of the first surface below (x, y, z), or nil. The probe starts above (default 1.5) meters over z, so an origin that sank a little into the floor still works. ignore is an entity the probe skips. While ground.onWater is true (set during boat placement) the water surface counts as a floor when it is higher than what the probe hit.

tm.worldtools.ground.originHeight(model) ​

Distance from a model's origin down to the bottom of its bounding box. model is a name or a hash.

Zones ​

Zones are plain tables: { type = 'poly', points = { {x,y,z}, ... }, thickness? }, { type = 'box', coords, size, rotation } or { type = 'sphere', coords, radius }. The legacy { points, thickness } polygon (no type) is accepted everywhere.

CallDescription
zones.toSeed(zone)Seed for tm.creator.startZone, so "Edit shape" starts from the zone that is already there. nil for an unusable zone.
zones.draw(zoneType, name, label, seed?, withHidden?, opts?)Runs the in-world zone creator. zoneType is 'poly', 'box' or 'sphere'. A seed of another shape is ignored. Returns a JSON safe zone { type, ... }, or nil when cancelled (a polygon with fewer than 3 points is rejected with a toast). opts is passed to the creator (freecam, camera).
zones.show(key, zone, colourIndex?, thickness?)Outlines a zone in the world, one outline per key, several at once. Polygons are drawn thickness (default 4) tall around their point heights unless the zone has its own. colourIndex picks one of seven colors. Returns true when shown.
zones.hide(key)Removes one outline.
zones.clearAll()Removes every outline.
zones.keys()The keys that are up.
zones.isShown(key)Whether a key is up.
zones.registerCallbacks(opts?)drawZone, zonePreview, zonePreviewClear (the kit's KZoneField). opts: withHidden, guard, thickness, freecam, camera.

Outlines are drawn by one thread that exists only while an outline is up. Outlines further than 250 m from the camera are skipped, and an empty view sleeps.

NUI payloads:

  • zonePreview { key, zone?, colour? } -> { ok, active }. No zone hides the key.
  • drawZone { zoneType, name, label, seed?, previewKey?, colour? } -> { ok, data = zone }. An outline already up under previewKey follows the new shape.

Coords ​

CallDescription
coords.pick(title?, withHidden?, opts?)Single position plus heading through tm.creator.getCoords. Returns { x, y, z, w }, or nil when cancelled. A missing heading falls back to the ped's.
coords.pickMany(withHidden?, opts?)Several positions (G adds, F finishes). Returns a list of { x, y, z, w } or nil.
coords.teleport(value)Moves the admin (and their vehicle) to a { x, y, z, w? } table or its JSON string. Returns false for an unusable value.
coords.registerCallbacks(opts?)pickCoords { title? }, pickCoordsMany, teleportToCoords { coords }.

Vehicle ​

tm.worldtools.vehicle.place(model, start?, opts?) ​

Ghost car placement. Blocks until the player confirms or cancels.

KeyAction
W A S DMove along the car's own heading
Left / RightRotate (T switches smooth and stepped, scroll picks the step)
PgUp / PgDnRaise / lower (R drops back to the floor)
Ctrl / ShiftFine / boost
G, 1 2 3Switch Keyboard, Gizmo and Fly
VFreecam (hold RMB to look without moving the car)
Enter / BackspaceConfirm / cancel

start puts the car on an existing slot: { coords, heading, zOffset }. Its coords are the car's origin, unless zOffset is given: then they are a stored spot (where a player stood, about a meter above the floor) and the car is dropped onto the floor beneath it. Without coords the car starts in front of the player, side on, or facing the way heading says. opts: freecam (default true), gizmo (start in gizmo mode), startCamera, gridSnap, gridSize, rotationMode ('smooth'/'step'), rotationStep, rotationSpeed, camera.

Returns { coords, heading, keepHeight, groundZ } (keepHeight means the car was deliberately lifted off the floor, groundZ is the floor under it), or nil when cancelled. Boats and jet skis are placed on the water surface.

Other calls ​

CallDescription
vehicle.defaultModel(vehicleType?)A preview model name: car (sultan), boat (dinghy), aircraft (maverick), bike (sanchez).
vehicle.spawnGhost(model, coords, heading?, alpha?)A local only ghost vehicle at coords (vehicle origin): no collision, see through, frozen, untouchable. Returns the entity or nil. You delete it.
vehicle.showSet(key, spots, model?, opts?)Ghost cars at spots ({ x, y, z, w } each, heading also accepted). Replaces whatever the key showed. opts: zOffset (how far above the floor a stored z sits, default 1), labels (index numbers, default true). Ghosts only exist while the player is in range.
vehicle.hideSet(key), vehicle.clearAll(), vehicle.isSetShown(key)
vehicle.registerCallbacks(opts?)spotPreview { key, spots?, model?, vehicleType?, zOffset?, labels? } (no spots hides the key) and spotPreviewClear.

Everything works with plain model names. There is no vehicle catalog: the caller names the model.

Ped ​

tm.worldtools.ped.spawnGhost(model, coords, heading?, appearance?, alpha?) ​

A local only ghost ped: frozen, invincible, ignores the world. coords is the ped's origin (where a player stood). Returns the entity, or nil and a reason. appearance is a table or JSON string; when it names a model that model wins, an unknown model falls back to model and then to a generic ped.

tm_lib has no appearance system, so the look is announced with the local event tm_lib:ped:applyAppearance (ped, appearance), the same hook tm.ped.create uses. Whatever appearance resource you run can listen for it.

Gizmo ​

tm.worldtools.gizmo.place(opts?) ​

Drags a stand-in with tm.gizmo and returns where it ended up. Blocks until Enter or Backspace. Returns { coords, heading, rotation }, or nil when cancelled. The stand-in is deleted afterwards.

OptionDescription
kind'object' (default, a see through cone), 'vehicle' (ghost car) or 'ped' (ghost ped).
model, appearance, alphaWhat the stand-in looks like.
coords, headingWhere it starts (default: in front of the player).
animA ped stand-in acts it out through tm.anim when available.
entityDrag a caller owned entity in place instead (you delete it).
hintHelp text (html, <br> separated) shown when no freecam is up. Pass a localized string.
extra, onFrame(entity)Extra help lines, and a callback that runs every frame.
constrain(coords)Clamp every move.
freecam, startCamera, startFly, cameraThe optional freecam.
uprightKeep the stand-in level ('locked' also pins the heading to 0).
outline, gridSnap, gridSizeGizmo options.

gizmo.registerCallbacks(opts?) registers gizmoPlace { kind?, model?, appearance?, anim?, coords?, heading? }. Kind defaults to 'ped' wearing the admin's own look (look.copy()). Reply: { ok, data = { x, y, z, w } } rounded to four decimals, or { ok = false, cancelled = true }.

Object ​

tm.worldtools.object.place(entity, opts?) ​

Keyboard first placement of one prop you own (stage lights, lasers, speakers). Create the prop first, delete it afterwards. Also accepts one table with entity in it. Blocks until Enter or Backspace and returns { coords, heading, rotation } or nil. The prop is left where the player put it.

KeyAction
W A S DMove away from / toward / left / right of where the camera looks (gameplay camera, or the freecam while on). A tap is one step (10 cm), a long hold glides.
Q / E, Left / RightTurn, stepped by default with key repeat, snapping to the step grid
ScrollTurn step: 1, 5, 15 or 45 degrees. T switches stepped and smooth turning
PgUp / PgDn, Up / DownRaise / lower. R or Left Alt drops back to the floor
Ctrl / ShiftFine / boost
CFollow the crosshair: the piece slides along the surface the camera looks at
G, 1 2 3Gizmo and back / Keyboard, Gizmo, Fly
VFreecam (hold RMB to look around)
F, HOnly with rotation = 'full': pick the turn axis (yaw, pitch, roll) / level the piece
Enter / BackspaceConfirm / cancel

By default the piece is held level and rotation in the result is { x = 0, y = 0, z = yaw } (yaw in -180..180). With rotation = 'full' (or level = false) the gizmo is free and the keyboard turns yaw, pitch and roll in GTA's rotation order 2, starting from the entity's own GetEntityRotation(entity, 2). Height is kept as lift above the surface below the piece.

Options: constrain(coords), extra, icon, outline, onFrame(entity, x, y, z, heading, rx, ry, rz) (runs every frame after the move; rz is the yaw in -180..180), freecam, startCamera, camera, follow (start with follow crosshair on), gizmo (start in gizmo mode), rotationMode, rotationStep, rotationSpeed, gridSnap, gridSize.

CallDescription
object.cancel()Ends a running placement from outside its loop (it returns nil).
object.isActive()Whether a placement is running.
object.rollSign(entity?)Which way the game turns a positive roll (1 or -1), measured once on a real entity.
object.normalizeRotation(x, y, z, sign?)The one form a rotation is reported in: pitch in -90..90, roll and yaw in -180..180.

Look ​

tm_lib has no appearance editor. A resource connects one with look.setProvider:

lua
tm.worldtools.look.setProvider({
    copy = function(ped) return myAppearance.get(ped) end,        -- the current look of a ped
    design = function(start) return myEditor.open(start) end,     -- blocks, returns the look or nil when cancelled
    restore = function(look) myAppearance.apply(look) end,        -- optional: puts the admin's look back
})
CallDescription
look.setProvider(provider?)Connects (or with nil disconnects) an appearance resource.
look.copy()The admin's current look. Without a provider this is just { model = <ped model hash> }.
look.design(start?, withHidden?)Opens the provider's editor on the admin's own ped, then restores the admin's look and health. Without a provider it shows a toast and returns nil.
look.registerCallbacks(opts?)copyLook -> { ok, appearance }, designLook { appearance? / model? } -> { ok, appearance } or { ok = false, cancelled = true } (the kit's KLookField).

Camera ​

A camera is { coords = {x,y,z}, rotation = {x,y,z}, fov }. A rail is a list of { t = ms, camera = camera }.

CallDescription
camera.capture(opts, withHidden?)Frames a fixed camera with a freecam anywhere in the world. Starts at opts.coords/rotation/fov when given, otherwise a few meters from opts.focus looking at it. opts.ghosts ({ x, y, z, w, anim?, model? }, up to 48) puts see through peds in the shot. maxDistance limits how far the camera flies (default 80), hint replaces the first help line. Enter keeps the shot, Backspace or Esc cancels. Returns a camera or nil.
camera.capturePath(opts, withHidden?)Records a rail: Enter starts and stops recording (at least 0.4 s), samples are spaced 400 ms and thinned to at most 32 keyframes. Returns a rail or nil. opts.camera is the starting camera.
camera.preview(shot, withHidden?, opts?)Shows a stored camera through a scripted camera until Enter, Backspace, Esc or opts.seconds (default 20). Returns false for an incomplete shot.
camera.previewPath(path, withHidden?, opts?)Plays a rail once.
camera.interpolate(path, index, u)The Catmull-Rom camera between samples index and index + 1, u in 0..1 (used by previewPath).
camera.registerCallbacks(opts?)captureCamera, captureCameraPath, previewCamera, previewCameraPath.

NUI payloads: captureCamera { focus, coords?, rotation?, fov?, maxDistance?, ghosts?, hint? } -> { ok, camera }; captureCameraPath { focus, camera?, maxDistance?, ghosts?, hint? } -> { ok, path }; previewCamera { camera, ghosts? }; previewCameraPath { path, ghosts? }. A cancel replies { ok = false, cancelled = true }.

While a camera session runs the player cannot start another, and a freecam that another tool already owns is left alone.

Math ​

tm.worldtools.math exposes the helpers the tools are built on, for callers that place things themselves:

CallDescription
wrap180(deg)Degrees into -180..180.
nextGridHeading(value, step, dir)The next heading on a grid of step degrees (dir +1 turns left and raises the heading). Off the grid it goes to the nearest line, on it a whole step, rounded so many steps never drift.
snapToGrid(value, size)Rounds to the nearest multiple of size.
moveRelative(x, y, yaw, advance, strafe)Moves a position relative to a camera yaw: forward is (-sin yaw, cos yaw), right is (cos yaw, sin yaw).
normalizeRotation(x, y, z, sign?)See object.normalizeRotation.

tm.worldtools.clearAll() ​

Ends every running tool and removes every outline and ghost this resource put up.