Skip to content

API reference

tUnit.source, tUnit.userValue, the vehicle state bag, the element record, per-character positioning and the net events.

Three server functions, a state bag, one client export.

Commands

/tunit is open to any driver, /tunitedit needs tlib.admin, and tunit_diag is registered restricted so FiveM's own ACE covers it.

tUnit.source(name, fn)

Registers a named value your server computes, which an element names instead of a literal. Call it from config/sv_sources.lua, which is escrow_ignored and loaded after tUnit's own files.

lua
tUnit.source('callsign', function(src, vehicle)
    return exports['my-mdt']:GetCallsign(src)
end)

name is letters, digits, - and _, 48 characters max. fn gets the driver's server id and the server-side vehicle, and returns a string, a number or nil.

CaseWhat the registry does
Bad name, or not a functionReported and ignored; does not throw
Same name twiceWarning, not a refusal; the later wins
Name also in Config.userSourcesConfig error at startup; the computed one wins
The function throwsCaught, treated as empty, reported once per session

What happens to the return value

InputResult
114"114" — not 114.0
20.5"20.5"
"1a-12""1A-12" — upper-cased
"UNIT 12""UNIT 12" — whitespace collapsed, then trimmed
"Unit ✅ 12""UNIT 12" — filtered to the allowed glyphs
"", nil, a tableNo value; the element draws nothing
40 charactersTruncated to 24

Values are filtered, not escaped: A–Z, 0–9, - . _ / # and single spaces survive, everything else is dropped — including the whole of a non-Latin callsign. The set cannot be widened from config.lua.

tUnit.userValue(src, name)

What that player typed for a driver-set source, or nil:

lua
tUnit.source('full', function(src)
    local unit = tUnit.userValue(src, 'unit')
    if not unit then return nil end
    return ('METRO %s'):format(unit)
end)

tUnit.sourceNames()

Every registered source name, sorted — what the editor's Shows dropdown offers.

exports.tUnit:atlasTexture()

Client-side.

lua
local atlas = exports.tUnit:atlasTexture()
-- { txd = 'tunit_atlas', tex = 'tunit_cells_1849203', ready = true }

ready is false until the atlas has rendered. Do not construct tex yourself — the name changes on every bind, so a hardcoded one draws nothing after a restart.

The state bag

lua
Entity(vehicle).state.tunit = { unit = '114', callsign = '1A-12' }

Sources resolve when the driver changes. The same player re-seating resolves nothing; an empty seat clears the bag. Text can be up to a second late.

The element record

As stored in Config.layouts, in data.json and in a shared layout. chars entries are { dx, dy, rz }.

FieldDefaultNotes
idrequiredThe only field whose absence makes a record unusable
srcnilA source name, resolved per driver
textnilA literal, used when src is nil. Neither set draws nothing
x y z0Vehicle-local offset in metres, clamped to ±20
rx ry rz0Euler degrees, wrapped modulo 360
w h0.30 / 0.15Full size in metres, clamped 0.01–6.0, halved at draw
fontnilA key in Config.fonts; an unknown one falls back to Config.defaultFont
colornil#rrggbb, lower-cased in. Stored and shared but not rendered today
bonenilNamed vehicle bone. Path separators are excluded: it reaches GetEntityBoneIndexByName
perCharfalseKept only when true
charsnilSee Per-character positioning

Only the keys above are kept, so an extra field of your own is gone on the next read. A record with no usable id is dropped, and so is a duplicate id — it is not renamed.

Per-character positioning

perChar = true gives the element one plane per character — an arced wordmark, hand-kerned lettering, a marking no rectangle fits. It costs N cells and N planes against the maxPropsOnScreen ceiling.

chars[i] is a nudge from the automatic layout, not an absolute position, so perChar with no nudges looks identical to plain text. dx/dy are metres in the element's own plane, capped at ±1.0 m; rz is extra spin.

Nudges are addressed by index, not by character identity, and survive perChar being turned off. When the resolved text changes length, a nudge stays on its index rather than following its character.

Net events

Internal, with their gates:

GateEvents
The actual driver of the actual vehicletunit:setUserValue
Own values onlytunit:requestUserValues
tlib.admintunit:saveLayout, :saveElementTransform, :saveElementStyle, :removeElement
tlib.admin plus a rate limittunit:cc:upload / :list / :import (5 s / 3 s / 2 s)
None, server → clienttunit:receiveUserValues, :receiveElements, :cc:listResult, :setAdmin, :toast

Still stuck? Support can answer it directly.

Was this page helpful?