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.
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.
| Case | What the registry does |
|---|---|
| Bad name, or not a function | Reported and ignored; does not throw |
| Same name twice | Warning, not a refusal; the later wins |
Name also in Config.userSources | Config error at startup; the computed one wins |
| The function throws | Caught, treated as empty, reported once per session |
What happens to the return value
| Input | Result |
|---|---|
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 table | No value; the element draws nothing |
| 40 characters | Truncated 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:
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.
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
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 }.
| Field | Default | Notes |
|---|---|---|
id | required | The only field whose absence makes a record unusable |
src | nil | A source name, resolved per driver |
text | nil | A literal, used when src is nil. Neither set draws nothing |
x y z | 0 | Vehicle-local offset in metres, clamped to ±20 |
rx ry rz | 0 | Euler degrees, wrapped modulo 360 |
w h | 0.30 / 0.15 | Full size in metres, clamped 0.01–6.0, halved at draw |
font | nil | A key in Config.fonts; an unknown one falls back to Config.defaultFont |
color | nil | #rrggbb, lower-cased in. Stored and shared but not rendered today |
bone | nil | Named vehicle bone. Path separators are excluded: it reaches GetEntityBoneIndexByName |
perChar | false | Kept only when true |
chars | nil | See 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:
| Gate | Events |
|---|---|
| The actual driver of the actual vehicle | tunit:setUserValue |
| Own values only | tunit:requestUserValues |
tlib.admin | tunit:saveLayout, :saveElementTransform, :saveElementStyle, :removeElement |
tlib.admin plus a rate limit | tunit:cc:upload / :list / :import (5 s / 3 s / 2 s) |
| None, server → client | tunit:receiveUserValues, :receiveElements, :cc:listResult, :setAdmin, :toast |
tUnit
Still stuck? Support can answer it directly.
Was this page helpful?
TIMMYG Studios