legacy-towing
Integration
Exports and events for other resources to call into legacy-towing.
Integration
Server exports
CarriedBy(rigNet) — server
Returns every vehicle net ID currently strapped to the given rig net ID. Added so another resource (e.g. a fleet/impound system) can refuse to delete a rig that still has cargo on its deck — deleting it would leave every client holding a dangling attachment.
-- server-side, e.g. a fleet recovery script
local vehs = exports['legacy-towing']:CarriedBy(rigNet)
if #vehs > 0 then
for _, vehNet in ipairs(vehs) do
TriggerEvent('legacy-towing:server:detach', vehNet)
end
end
DeleteEntity(rigEntity)
Note: there is a client-side export of the same name with a different signature — CarriedBy(rigEntity) in client/carry.lua, which awaits legacy-towing:server:getCarried instead. Be sure to call it on the correct side.
HasRig(model) — server
Returns true if the given model name (case-insensitive) is a configured rig in data/rigs.json — i.e. it has an authored deck/ramps/winch, distinguishing a real tow rig from a vehicle that merely looks like one.
-- server-side
if exports['legacy-towing']:HasRig('flatbed') then
-- treat as a real company tow truck asset
end
Client exports
Useful when writing custom ox_target options or scripts that need to know rig/winch state:
-- client-side
GetRig(entity) -- table|nil: rig definition for an entity
GetCategory(id) -- table|nil: category definition
CategoryOf(vehicle) -- string|nil: resolves a vehicle to a category id
OverlapsCarried(vehicle, carried) -- entity|nil: oriented-rectangle overlap test
CanCarry(rig, rigEntity, vehicle) -- boolean, string|nil: refusal reason if false
IsSlotMode(rig) -- boolean
IsFixedMode(rig) -- boolean
UnloadPosition(rig, rigEntity, veh) -- vector3, number (heading)
PlaceOnGround(veh, coords, heading) -- boolean
FixedLoadTarget(vehicle) -- rig, rigEntity, reason|nil
RigCapacity(rig) -- number
DeckPoints(rig) -- points, minZ, maxZ
IsOnDeck(rig, rigEntity, vehicle) -- boolean
NearbyRigs(radius) -- {entity, rig}[]
StowRamps(rigNet)
RampsDeployed(rigNet) -- boolean
DeployRamps(rigEntity, rigNet, rig, origin) -- boolean
RampSetExtra(veh, index, enabled) -- boolean (0=on,1=off wrapper — never call SetVehicleExtra directly)
RampExtraOn(veh, index) -- boolean
StartRampWatcher()
IsRampDown(rig, entity) -- boolean
SetRamp(rig, entity, down) -- boolean
CarriedBy(rigEntity) -- number[] (client-side, awaits server callback)
WinchActive(entity) -- boolean
WinchHooked(entity) -- boolean
WinchTaken(rigEntity) -- boolean (someone ELSE has the winch)
StartWinch(rigEntity, rig) -- begins/toggles a winch session
StopWinch()
WinchInReach(rigEntity, rig) -- boolean
Net events
legacy-towing:server:requestConfig() — client → server
Fire this on resource start (or player join) to receive the current rig config. The server responds with legacy-towing:client:config, then replays every currently-carried vehicle and active winch session so a late joiner sees the world correctly.
-- client, e.g. your own resource's init code if you need the config too
TriggerServerEvent('legacy-towing:server:requestConfig')
legacy-towing:client:config(cfg) — server → client
Delivers the resolved rig/category config. Handled internally in client/rigs.lua; listen to it yourself if another resource needs to react to rig config reloads.
legacy-towing:server:attach(vehNet, rigNet, off, rot, slot) — client → server
Requests vehNet be attached to rigNet at the given offset/rotation (and, for slot-mode rigs, a specific slot index). The server re-validates everything (existence, slot occupancy, cycles) regardless of client claims.
-- client — normally called by AttachToRig() in client/carry.lua, not directly
TriggerServerEvent('legacy-towing:server:attach', VehToNet(veh), VehToNet(rigEntity), { x = 0.0, y = 0.0, z = 0.0 }, { x = 0.0, y = 0.0, z = 0.0 })
Important: every offset/rotation component must be coerced to float with + 0.0 before it reaches a native. Lua 5.4 distinguishes integers from floats, and a whole-number value that round-trips through JSON decodes as an integer, which natives like AttachEntityToEntity silently treat as garbage.
legacy-towing:server:detach(vehNet) — client → server
Releases vehNet from whatever it's attached to, clears its legacyTowCarrier statebag, and broadcasts legacy-towing:client:carry with nil data.
TriggerServerEvent('legacy-towing:server:detach', VehToNet(veh))
legacy-towing:client:carry(vehNet, data) — server → client
Applies (data truthy) or removes (data falsy) a carry attachment on every client. data shape: { off = {x,y,z}, rot = {x,y,z} }.
legacy-towing:server:winchClaim(rigNet) — client → server (lib.callback)
Must be awaited before taking a rig's hook out — returns false if another player already holds the winch.
local claimed = lib.callback.await('legacy-towing:server:winchClaim', false, rigNet)
if not claimed then
lib.notify({ description = 'Someone else is using this winch.', type = 'error' })
return
end
legacy-towing:server:winch(rigNet, data) — client → server
Publishes or clears (with data = nil) the caller's winch session on rigNet. Only the claim holder's messages are accepted; data.state must be 'carrying' or 'hooked'.
legacy-towing:client:winch(rigNet, data) — server → client
Broadcasts a winch session update. data shape: { hook = netId, state = 'carrying'|'hooked', ped = netId, veh = netId, off = {x,y,z} }, or nil to clear.
legacy-towing:server:ramps(rigNet, down, origin) — client → server
Sets/clears the legacyTowRamps statebag for all ramp modes (props/extra/door). origin is { pos = {x,y,z}, heading }, only meaningful when down == true; accepted only if within Config.Ramps.originTolerance of the rig's live position, else the statebag falls back to plain true.
local pos = GetEntityCoords(entity)
TriggerServerEvent('legacy-towing:server:ramps',
NetworkGetNetworkIdFromEntity(entity),
true,
{ pos = { x = pos.x, y = pos.y, z = pos.z }, heading = GetEntityHeading(entity) })
Statebags (not net events, but part of the public contract)
legacyTowCarrier— set on a carried vehicle;nilmeans not carried.legacyTowWinch— set on a rig; carries the same shape aslegacy-towing:client:winch'sdata.legacyTowRamps— set on a rig;nil= ramps up,true= down (live transform), or a{pos, heading}table = down at a recorded transform.
All three are always cleared with nil, never false — a statebag set to false still replicates and still fires change handlers. Follow this convention if you write to them from another resource.
onResourceStop cleanup handlers
Each of client/admin.lua, client/carry.lua, client/hitch.lua, client/ramps.lua and client/winch.lua registers an onResourceStop handler that only runs cleanup if res == GetCurrentResourceName(). Restarting a dependency alone will not trigger any of this cleanup.