From 193ffb38ac4a39458d6334c865b697e265de79d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Burak=20G=C3=BCne=C5=9F?= Date: Sat, 2 May 2026 03:14:38 +0300 Subject: [PATCH 1/2] feat(imports): add server-side entity spawn wrappers and shared helper Add `lib.createObject`, `lib.createPed`, and `lib.createVehicle` for the server, plus a shared `lib.spawnEntity` helper used by both client and server wrappers. Each wrapper splits its body at module load time based on `cache.game` so that the correct CREATE_OBJECT/PED/VEHICLE native signature is used on FiveM vs RedM. Server wrappers default `orphanMode` to 2 (KeepEntity). Server `createVehicle` uses `CreateVehicleServerSetter` (with `vehicleType`) on FiveM and the CFX `CreateVehicle` native on RedM, where `vehicleType`, `properties`, and `seat` are not exposed. Existing client wrappers were refactored to delegate model loading to `lib.spawnEntity`, with a few client-side fixes folded in: - pass heading as the float arg in `SetEntityHeading` for createObject - replace undefined `netMissionEntity` with `isNetwork` in createPed - drop duplicate `isNetwork` assert in createPed - mark `isNetwork`, `netMissionEntity`, and `doorFlag` as optional in the type annotations to match runtime behavior --- fxmanifest.lua | 4 ++ imports/createObject/client.lua | 83 ++++++++++++++++++++++++++++++++ imports/createObject/server.lua | 38 +++++++++++++++ imports/createPed/client.lua | 58 ++++++++++++++++++++++ imports/createPed/server.lua | 26 ++++++++++ imports/createVehicle/client.lua | 82 +++++++++++++++++++++++++++++++ imports/createVehicle/server.lua | 77 +++++++++++++++++++++++++++++ imports/spawnEntity/client.lua | 31 ++++++++++++ imports/spawnEntity/server.lua | 51 ++++++++++++++++++++ 9 files changed, 450 insertions(+) create mode 100644 imports/createObject/client.lua create mode 100644 imports/createObject/server.lua create mode 100644 imports/createPed/client.lua create mode 100644 imports/createPed/server.lua create mode 100644 imports/createVehicle/client.lua create mode 100644 imports/createVehicle/server.lua create mode 100644 imports/spawnEntity/client.lua create mode 100644 imports/spawnEntity/server.lua diff --git a/fxmanifest.lua b/fxmanifest.lua index 4fc3efda4..a4387a883 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -43,6 +43,10 @@ client_scripts { server_scripts { 'imports/callback/server.lua', 'imports/getFilesInDirectory/server.lua', + 'imports/spawnEntity/server.lua', + 'imports/createObject/server.lua', + 'imports/createPed/server.lua', + 'imports/createVehicle/server.lua', 'resource/**/server.lua', 'resource/**/server/*.lua', } diff --git a/imports/createObject/client.lua b/imports/createObject/client.lua new file mode 100644 index 000000000..89b5f83f0 --- /dev/null +++ b/imports/createObject/client.lua @@ -0,0 +1,83 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +if cache.game == 'redm' then + ---**RedM build** — calls the 9-argument `CREATE_OBJECT` native + ---(`modelHash, x, y, z, isNetwork, bScriptHostObj, dynamic, p7, p8`). + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param isNetwork? boolean Whether to create a network object. If false, the object exists only locally. + ---@param bScriptHostObj? boolean Pin the object to the script host in the R* network model. + ---@param dynamic? boolean Whether the object should be dynamic (physics-driven, can move). + ---@param p7? boolean Undocumented RedM parameter; passed as `false` if omitted. + ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. + ---@param heading? number Heading of the object + ---@param rotation? vector3 Rotation of the object; defaults to no rotation. + lib.createObject = function(model, coords, isNetwork, bScriptHostObj, dynamic, p7, p8, heading, rotation) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(bScriptHostObj == nil or type(bScriptHostObj) == 'boolean', 'Invalid bScriptHostObj flag provided, expected boolean') + assert(dynamic == nil or type(dynamic) == 'boolean', 'Invalid dynamic flag provided, expected boolean') + assert(p7 == nil or type(p7) == 'boolean', 'Invalid p7 flag provided, expected boolean') + assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') + + local object = lib.spawnEntity(model, function(modelHash) + return CreateObject(modelHash, coords.x, coords.y, coords.z, isNetwork or false, bScriptHostObj or false, dynamic or false, p7 or false, p8 or false) + end) + + if object == 0 then return 0 end + + if heading then + SetEntityHeading(object, heading + 0.0) + end + + if rotation then + SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) + end + + return object + end +else + ---**GTA5/FiveM build** — calls the 7-argument `CREATE_OBJECT` native + ---(`modelHash, x, y, z, isNetwork, netMissionEntity, doorFlag`). + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param isNetwork? boolean Whether to create a network object. If false, the object exists only locally. + ---@param netMissionEntity? boolean Pin the object to the script host in the R* network model. + ---@param doorFlag? boolean Set true to spawn door models in network mode. + ---@param heading? number Heading of the object + ---@param rotation? vector3 Rotation of the object; defaults to no rotation. + lib.createObject = function(model, coords, isNetwork, netMissionEntity, doorFlag, heading, rotation) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(netMissionEntity == nil or type(netMissionEntity) == 'boolean', 'Invalid netMissionEntity flag provided, expected boolean') + assert(doorFlag == nil or type(doorFlag) == 'boolean', 'Invalid doorFlag provided, expected boolean') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') + + local object = lib.spawnEntity(model, function(modelHash) + return CreateObject(modelHash, coords.x, coords.y, coords.z, isNetwork or false, netMissionEntity or false, doorFlag or false) + end) + + if object == 0 then return 0 end + + if heading then + SetEntityHeading(object, heading + 0.0) + end + + if rotation then + SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) + end + + return object + end +end + +return lib.createObject diff --git a/imports/createObject/server.lua b/imports/createObject/server.lua new file mode 100644 index 000000000..25f8f9e0d --- /dev/null +++ b/imports/createObject/server.lua @@ -0,0 +1,38 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@param model string | number The model to spawn +---@param coords vector3 Spawn coordinate +---@param doorFlag? boolean Set true to spawn door models in network mode. +---@param heading? number Heading of the object +---@param rotation? vector3 Rotation of the object; defaults to no rotation. +---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. +lib.createObject = function(model, coords, doorFlag, heading, rotation, orphanMode) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(doorFlag == nil or type(doorFlag) == 'boolean', 'Invalid doorFlag provided, expected boolean') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') + + local object = lib.spawnEntity('object', model, function(modelHash) + return CreateObject(modelHash, coords.x, coords.y, coords.z, true, true, doorFlag or false) + end, orphanMode) + + if object == 0 then return 0 end + + if heading then + SetEntityHeading(object, heading + 0.0) + end + + if rotation then + SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) + end + + return object +end + +return lib.createObject diff --git a/imports/createPed/client.lua b/imports/createPed/client.lua new file mode 100644 index 000000000..077d377b0 --- /dev/null +++ b/imports/createPed/client.lua @@ -0,0 +1,58 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +if cache.game == 'redm' then + ---**RedM build** — calls the 9-argument `CREATE_PED` native + ---(`modelHash, x, y, z, heading, isNetwork, bScriptHostPed, p7, p8`). + ---RedM has no `pedType` parameter. + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading of the ped + ---@param isNetwork? boolean Whether to create a network ped. If false, the ped exists only locally. + ---@param bScriptHostPed? boolean Pin the ped to the script host in the R* network model. + ---@param p7? boolean Undocumented RedM parameter; passed as `false` if omitted. + ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. + lib.createPed = function(model, coords, heading, isNetwork, bScriptHostPed, p7, p8) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(bScriptHostPed == nil or type(bScriptHostPed) == 'boolean', 'Invalid bScriptHostPed flag provided, expected boolean') + assert(p7 == nil or type(p7) == 'boolean', 'Invalid p7 flag provided, expected boolean') + assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') + + local headingValue = heading and heading + 0.0 or 0.0 + + return lib.spawnEntity(model, function(modelHash) + return CreatePed(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostPed or false, p7 or false, p8 or false) + end) + end +else + ---**GTA5/FiveM build** — calls the 8-argument `CREATE_PED` native + ---(`pedType, modelHash, x, y, z, heading, isNetwork, bScriptHostPed`). + ---@param pedType number AI behavior type. 4 = CIVMALE, 5 = CIVFEMALE, etc. + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading of the ped + ---@param isNetwork? boolean Whether to create a network ped. If false, the ped exists only locally. + ---@param bScriptHostPed? boolean Pin the ped to the script host in the R* network model. + lib.createPed = function(pedType, model, coords, heading, isNetwork, bScriptHostPed) + assert(type(pedType) == 'number', 'Invalid pedType provided, expected number') + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(bScriptHostPed == nil or type(bScriptHostPed) == 'boolean', 'Invalid bScriptHostPed flag provided, expected boolean') + + local headingValue = heading and heading + 0.0 or 0.0 + + return lib.spawnEntity(model, function(modelHash) + return CreatePed(pedType, modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostPed or false) + end) + end +end + +return lib.createPed diff --git a/imports/createPed/server.lua b/imports/createPed/server.lua new file mode 100644 index 000000000..03493e7e6 --- /dev/null +++ b/imports/createPed/server.lua @@ -0,0 +1,26 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@param pedType number AI behavior type. 4 = CIVMALE, 5 = CIVFEMALE, etc. Ignored on RedM (`Unused` per the native docs). +---@param model string | number The model to spawn +---@param coords vector3 Spawn coordinate +---@param heading? number Heading of the ped +---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. +lib.createPed = function(pedType, model, coords, heading, orphanMode) + assert(type(pedType) == 'number', 'Invalid pedType provided, expected number') + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + + local headingValue = heading and heading + 0.0 or 0.0 + + return lib.spawnEntity('ped', model, function(modelHash) + return CreatePed(pedType, modelHash, coords.x, coords.y, coords.z, headingValue, true, true) + end, orphanMode) +end + +return lib.createPed diff --git a/imports/createVehicle/client.lua b/imports/createVehicle/client.lua new file mode 100644 index 000000000..0ae31e8fe --- /dev/null +++ b/imports/createVehicle/client.lua @@ -0,0 +1,82 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@alias SeatPosition +---| -2 # SF_ANY +---| -1 # SF_FrontDriverSide +---| 0 # SF_FrontPassengerSide +---| 1 # SF_BackDriverSide +---| 2 # SF_BackPassengerSide +---| 3 # SF_AltFrontDriverSide +---| 4 # SF_AltFrontPassengerSide +---| 5 # SF_AltBackDriverSide +---| 6 # SF_AltBackPassengerSide + +if cache.game == 'redm' then + ---**RedM build** — calls the 9-argument `CREATE_VEHICLE` native + ---(`modelHash, x, y, z, heading, isNetwork, bScriptHostVeh, bDontAutoCreateDraftAnimals, p8`). + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading to face towards, in degrees. + ---@param isNetwork? boolean Whether to create a network vehicle. If false, the vehicle exists only locally. + ---@param bScriptHostVeh? boolean Pin the vehicle to the script host in the R* network model. + ---@param bDontAutoCreateDraftAnimals? boolean Skip automatic creation of draft animals (e.g. horses on a wagon). + ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. + lib.createVehicle = function(model, coords, heading, isNetwork, bScriptHostVeh, bDontAutoCreateDraftAnimals, p8) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(bScriptHostVeh == nil or type(bScriptHostVeh) == 'boolean', 'Invalid bScriptHostVeh flag provided, expected boolean') + assert(bDontAutoCreateDraftAnimals == nil or type(bDontAutoCreateDraftAnimals) == 'boolean', 'Invalid bDontAutoCreateDraftAnimals flag provided, expected boolean') + assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') + + local headingValue = heading and heading + 0.0 or 0.0 + + return lib.spawnEntity(model, function(modelHash) + return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostVeh or false, bDontAutoCreateDraftAnimals or false, p8 or false) + end) + end +else + ---**GTA5/FiveM build** — calls the 7-argument `CREATE_VEHICLE` native + ---(`modelHash, x, y, z, heading, isNetwork, netMissionEntity`). + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading to face towards, in degrees. + ---@param isNetwork? boolean Whether to create a network vehicle. If false, the vehicle exists only locally. + ---@param netMissionEntity? boolean Pin the vehicle to the script host in the R* network model. + ---@param seat? SeatPosition The SeatPosition for any vehicle seat. + ---@param properties? table A table of vehicle properties to set on the vehicle after spawning. See `lib.setVehicleProperties` for more details. + lib.createVehicle = function(model, coords, heading, isNetwork, netMissionEntity, seat, properties) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') + assert(netMissionEntity == nil or type(netMissionEntity) == 'boolean', 'Invalid netMissionEntity flag provided, expected boolean') + assert(seat == nil or type(seat) == 'number', 'Invalid seat provided, expected number') + assert(properties == nil or type(properties) == 'table', 'Invalid properties provided, expected table') + + local headingValue = heading and heading + 0.0 or 0.0 + + local vehicle = lib.spawnEntity(model, function(modelHash) + return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, netMissionEntity or false) + end) + + if vehicle == 0 then return 0 end + + if seat then + TaskWarpPedIntoVehicle(cache.ped, vehicle, seat) + end + + if properties then + lib.setVehicleProperties(vehicle, properties) + end + + return vehicle + end +end + +return lib.createVehicle diff --git a/imports/createVehicle/server.lua b/imports/createVehicle/server.lua new file mode 100644 index 000000000..795651ddf --- /dev/null +++ b/imports/createVehicle/server.lua @@ -0,0 +1,77 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---Vehicle category for `CreateVehicleServerSetter` (GTA5 only). +---@alias VehicleType +---| 'automobile' +---| 'bike' +---| 'boat' +---| 'heli' +---| 'plane' +---| 'submarine' +---| 'trailer' +---| 'train' + +if cache.game == 'redm' then + ---**RedM build** — calls the CFX `CREATE_VEHICLE` server native + ---(`modelHash, x, y, z, heading, isNetwork, netMissionEntity`). + ---`vehicleType`, `properties`, and `seat` from the GTA5 wrapper are not + ---available on RedM (`setVehicleProperties` and `CreateVehicleServerSetter` + ---are GTA5-only). + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading to face towards, in degrees. + ---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. + lib.createVehicle = function(model, coords, heading, orphanMode) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + + local headingValue = heading and heading + 0.0 or 0.0 + + return lib.spawnEntity('vehicle', model, function(modelHash) + return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, true, true) + end, orphanMode) + end +else + ---**GTA5/FiveM build** — calls `CreateVehicleServerSetter` with the supplied + ---`vehicleType`. Optionally applies vehicle properties and warps a player + ---into a seat after the entity exists. + ---@param model string | number The model to spawn + ---@param coords vector3 Spawn coordinate + ---@param heading? number Heading to face towards, in degrees. + ---@param vehicleType? VehicleType Vehicle category; defaults to `'automobile'`. + ---@param properties? table Properties applied via `lib.setVehicleProperties` after spawning. + ---@param seat? { source:number, seat:SeatPosition } If provided, the source will be warped into the specified seat after spawning. + ---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. + lib.createVehicle = function(model, coords, heading, vehicleType, properties, seat, orphanMode) + assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') + assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') + assert(vehicleType == nil or type(vehicleType) == 'string', 'Invalid vehicleType provided, expected string') + assert(properties == nil or type(properties) == 'table', 'Invalid properties provided, expected table') + + local headingValue = heading and heading + 0.0 or 0.0 + + local vehicle = lib.spawnEntity('vehicle', model, function(modelHash) + return CreateVehicleServerSetter(modelHash, vehicleType or 'automobile', coords.x, coords.y, coords.z, headingValue) + end, orphanMode) + + if vehicle == 0 then return 0 end + + if seat then + TaskWarpPedIntoVehicle(cache.ped, vehicle, seat) + end + + if properties then + lib.setVehicleProperties(vehicle, properties) + end + + return vehicle + end +end + +return lib.createVehicle diff --git a/imports/spawnEntity/client.lua b/imports/spawnEntity/client.lua new file mode 100644 index 000000000..7fb73abca --- /dev/null +++ b/imports/spawnEntity/client.lua @@ -0,0 +1,31 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---Load `model`, run `spawn` to create the entity, then release the model. +---Used internally by `lib.createObject`, `lib.createPed`, and `lib.createVehicle`. +---@async +---@param model string | number Model name or precomputed hash. +---@param spawn fun(modelHash: number): number Native spawner returning the entity handle. +---@param timeout? number Milliseconds to wait for the model to load. Defaults to 10000. +---@return number entity Entity handle, or 0 on failure. +function lib.spawnEntity(model, spawn, timeout) + assert(type(model) == 'string' or type(model) == 'number', 'Invalid model provided, expected string or number') + assert(type(spawn) == 'function', 'Invalid spawn provided, expected function') + assert(timeout == nil or type(timeout) == 'number', 'Invalid timeout provided, expected number') + + local modelHash = lib.requestModel(model, timeout or 10000) + local entity = spawn(modelHash) + + SetModelAsNoLongerNeeded(modelHash) + + if entity == 0 or not DoesEntityExist(entity) then return 0 end + + return entity +end + +return lib.spawnEntity diff --git a/imports/spawnEntity/server.lua b/imports/spawnEntity/server.lua new file mode 100644 index 000000000..d7a1a9cc8 --- /dev/null +++ b/imports/spawnEntity/server.lua @@ -0,0 +1,51 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---Server-side entity cleanup mode. Used by `SetEntityOrphanMode`. +---@alias EntityOrphanMode +---| 0 # DeleteWhenNotRelevant — default; deletes when no player is relevant. +---| 1 # DeleteOnOwnerDisconnect — deletes when the original owner disconnects. +---| 2 # KeepEntity — never deleted by server relevancy checks. + +---Spawn a server-side entity, wait for it to materialize, then apply an orphan mode. +---Used internally by `lib.createObject`, `lib.createPed`, and `lib.createVehicle`. +---@async +---@param assetType string Label used in error messages, e.g. 'object', 'ped', 'vehicle'. +---@param model string | number Model name or precomputed hash. +---@param spawn fun(modelHash: number): number Native spawner returning the entity handle. +---@param orphanMode? EntityOrphanMode Defaults to 2 (KeepEntity). +---@param timeout? number Milliseconds to wait for the entity to exist. Defaults to 5000. +---@return number entity Entity handle, or 0 on failure. +function lib.spawnEntity(assetType, model, spawn, orphanMode, timeout) + assert(type(model) == 'string' or type(model) == 'number', 'Invalid model provided, expected string or number') + assert(type(spawn) == 'function', 'Invalid spawn provided, expected function') + assert(orphanMode == nil or (type(orphanMode) == 'number' and orphanMode >= 0 and orphanMode <= 2), 'Invalid orphanMode provided, expected number between 0 and 2') + assert(timeout == nil or type(timeout) == 'number', 'Invalid timeout provided, expected number') + + local modelHash = type(model) == 'number' and model or joaat(model) + local entity = spawn(modelHash) + + if entity == 0 then return 0 end + + -- Spawning can lag in high-population sessions; wait until the entity + -- actually exists before continuing or giving up. + local success, err = pcall(lib.waitFor, function() + if DoesEntityExist(entity) then return true end + end, ('failed to spawn %s %s'):format(assetType, model), timeout or 5000) + + if not success then + lib.print.error(err) + return 0 + end + + SetEntityOrphanMode(entity, orphanMode or 2) + + return entity +end + +return lib.spawnEntity From 75ddb0dbbad7b5640f3f30e9411693f6e9fc4617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mustafa=20Burak=20G=C3=BCne=C5=9F?= Date: Sat, 2 May 2026 16:41:17 +0300 Subject: [PATCH 2/2] update --- fxmanifest.lua | 4 - imports/createObject/client.lua | 83 ---------- imports/createObject/server.lua | 38 ----- imports/createPed/client.lua | 58 ------- imports/createPed/server.lua | 26 --- imports/createVehicle/client.lua | 82 ---------- imports/createVehicle/server.lua | 77 --------- imports/entity/shared.lua | 270 +++++++++++++++++++++++++++++++ imports/object/client.lua | 61 +++++++ imports/object/server.lua | 65 ++++++++ imports/ped/client.lua | 63 ++++++++ imports/ped/server.lua | 66 ++++++++ imports/spawnEntity/client.lua | 31 ---- imports/spawnEntity/server.lua | 51 ------ imports/vehicle/client.lua | 95 +++++++++++ imports/vehicle/server.lua | 100 ++++++++++++ 16 files changed, 720 insertions(+), 450 deletions(-) delete mode 100644 imports/createObject/client.lua delete mode 100644 imports/createObject/server.lua delete mode 100644 imports/createPed/client.lua delete mode 100644 imports/createPed/server.lua delete mode 100644 imports/createVehicle/client.lua delete mode 100644 imports/createVehicle/server.lua create mode 100644 imports/entity/shared.lua create mode 100644 imports/object/client.lua create mode 100644 imports/object/server.lua create mode 100644 imports/ped/client.lua create mode 100644 imports/ped/server.lua delete mode 100644 imports/spawnEntity/client.lua delete mode 100644 imports/spawnEntity/server.lua create mode 100644 imports/vehicle/client.lua create mode 100644 imports/vehicle/server.lua diff --git a/fxmanifest.lua b/fxmanifest.lua index a4387a883..4fc3efda4 100644 --- a/fxmanifest.lua +++ b/fxmanifest.lua @@ -43,10 +43,6 @@ client_scripts { server_scripts { 'imports/callback/server.lua', 'imports/getFilesInDirectory/server.lua', - 'imports/spawnEntity/server.lua', - 'imports/createObject/server.lua', - 'imports/createPed/server.lua', - 'imports/createVehicle/server.lua', 'resource/**/server.lua', 'resource/**/server/*.lua', } diff --git a/imports/createObject/client.lua b/imports/createObject/client.lua deleted file mode 100644 index 89b5f83f0..000000000 --- a/imports/createObject/client.lua +++ /dev/null @@ -1,83 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - -if cache.game == 'redm' then - ---**RedM build** — calls the 9-argument `CREATE_OBJECT` native - ---(`modelHash, x, y, z, isNetwork, bScriptHostObj, dynamic, p7, p8`). - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param isNetwork? boolean Whether to create a network object. If false, the object exists only locally. - ---@param bScriptHostObj? boolean Pin the object to the script host in the R* network model. - ---@param dynamic? boolean Whether the object should be dynamic (physics-driven, can move). - ---@param p7? boolean Undocumented RedM parameter; passed as `false` if omitted. - ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. - ---@param heading? number Heading of the object - ---@param rotation? vector3 Rotation of the object; defaults to no rotation. - lib.createObject = function(model, coords, isNetwork, bScriptHostObj, dynamic, p7, p8, heading, rotation) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(bScriptHostObj == nil or type(bScriptHostObj) == 'boolean', 'Invalid bScriptHostObj flag provided, expected boolean') - assert(dynamic == nil or type(dynamic) == 'boolean', 'Invalid dynamic flag provided, expected boolean') - assert(p7 == nil or type(p7) == 'boolean', 'Invalid p7 flag provided, expected boolean') - assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') - - local object = lib.spawnEntity(model, function(modelHash) - return CreateObject(modelHash, coords.x, coords.y, coords.z, isNetwork or false, bScriptHostObj or false, dynamic or false, p7 or false, p8 or false) - end) - - if object == 0 then return 0 end - - if heading then - SetEntityHeading(object, heading + 0.0) - end - - if rotation then - SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) - end - - return object - end -else - ---**GTA5/FiveM build** — calls the 7-argument `CREATE_OBJECT` native - ---(`modelHash, x, y, z, isNetwork, netMissionEntity, doorFlag`). - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param isNetwork? boolean Whether to create a network object. If false, the object exists only locally. - ---@param netMissionEntity? boolean Pin the object to the script host in the R* network model. - ---@param doorFlag? boolean Set true to spawn door models in network mode. - ---@param heading? number Heading of the object - ---@param rotation? vector3 Rotation of the object; defaults to no rotation. - lib.createObject = function(model, coords, isNetwork, netMissionEntity, doorFlag, heading, rotation) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(netMissionEntity == nil or type(netMissionEntity) == 'boolean', 'Invalid netMissionEntity flag provided, expected boolean') - assert(doorFlag == nil or type(doorFlag) == 'boolean', 'Invalid doorFlag provided, expected boolean') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') - - local object = lib.spawnEntity(model, function(modelHash) - return CreateObject(modelHash, coords.x, coords.y, coords.z, isNetwork or false, netMissionEntity or false, doorFlag or false) - end) - - if object == 0 then return 0 end - - if heading then - SetEntityHeading(object, heading + 0.0) - end - - if rotation then - SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) - end - - return object - end -end - -return lib.createObject diff --git a/imports/createObject/server.lua b/imports/createObject/server.lua deleted file mode 100644 index 25f8f9e0d..000000000 --- a/imports/createObject/server.lua +++ /dev/null @@ -1,38 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----@param model string | number The model to spawn ----@param coords vector3 Spawn coordinate ----@param doorFlag? boolean Set true to spawn door models in network mode. ----@param heading? number Heading of the object ----@param rotation? vector3 Rotation of the object; defaults to no rotation. ----@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. -lib.createObject = function(model, coords, doorFlag, heading, rotation, orphanMode) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(doorFlag == nil or type(doorFlag) == 'boolean', 'Invalid doorFlag provided, expected boolean') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(rotation == nil or (rotation.x and rotation.y and rotation.z), 'Invalid rotation vector3 provided') - - local object = lib.spawnEntity('object', model, function(modelHash) - return CreateObject(modelHash, coords.x, coords.y, coords.z, true, true, doorFlag or false) - end, orphanMode) - - if object == 0 then return 0 end - - if heading then - SetEntityHeading(object, heading + 0.0) - end - - if rotation then - SetEntityRotation(object, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) - end - - return object -end - -return lib.createObject diff --git a/imports/createPed/client.lua b/imports/createPed/client.lua deleted file mode 100644 index 077d377b0..000000000 --- a/imports/createPed/client.lua +++ /dev/null @@ -1,58 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - -if cache.game == 'redm' then - ---**RedM build** — calls the 9-argument `CREATE_PED` native - ---(`modelHash, x, y, z, heading, isNetwork, bScriptHostPed, p7, p8`). - ---RedM has no `pedType` parameter. - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading of the ped - ---@param isNetwork? boolean Whether to create a network ped. If false, the ped exists only locally. - ---@param bScriptHostPed? boolean Pin the ped to the script host in the R* network model. - ---@param p7? boolean Undocumented RedM parameter; passed as `false` if omitted. - ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. - lib.createPed = function(model, coords, heading, isNetwork, bScriptHostPed, p7, p8) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(bScriptHostPed == nil or type(bScriptHostPed) == 'boolean', 'Invalid bScriptHostPed flag provided, expected boolean') - assert(p7 == nil or type(p7) == 'boolean', 'Invalid p7 flag provided, expected boolean') - assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') - - local headingValue = heading and heading + 0.0 or 0.0 - - return lib.spawnEntity(model, function(modelHash) - return CreatePed(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostPed or false, p7 or false, p8 or false) - end) - end -else - ---**GTA5/FiveM build** — calls the 8-argument `CREATE_PED` native - ---(`pedType, modelHash, x, y, z, heading, isNetwork, bScriptHostPed`). - ---@param pedType number AI behavior type. 4 = CIVMALE, 5 = CIVFEMALE, etc. - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading of the ped - ---@param isNetwork? boolean Whether to create a network ped. If false, the ped exists only locally. - ---@param bScriptHostPed? boolean Pin the ped to the script host in the R* network model. - lib.createPed = function(pedType, model, coords, heading, isNetwork, bScriptHostPed) - assert(type(pedType) == 'number', 'Invalid pedType provided, expected number') - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(bScriptHostPed == nil or type(bScriptHostPed) == 'boolean', 'Invalid bScriptHostPed flag provided, expected boolean') - - local headingValue = heading and heading + 0.0 or 0.0 - - return lib.spawnEntity(model, function(modelHash) - return CreatePed(pedType, modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostPed or false) - end) - end -end - -return lib.createPed diff --git a/imports/createPed/server.lua b/imports/createPed/server.lua deleted file mode 100644 index 03493e7e6..000000000 --- a/imports/createPed/server.lua +++ /dev/null @@ -1,26 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----@param pedType number AI behavior type. 4 = CIVMALE, 5 = CIVFEMALE, etc. Ignored on RedM (`Unused` per the native docs). ----@param model string | number The model to spawn ----@param coords vector3 Spawn coordinate ----@param heading? number Heading of the ped ----@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. -lib.createPed = function(pedType, model, coords, heading, orphanMode) - assert(type(pedType) == 'number', 'Invalid pedType provided, expected number') - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - - local headingValue = heading and heading + 0.0 or 0.0 - - return lib.spawnEntity('ped', model, function(modelHash) - return CreatePed(pedType, modelHash, coords.x, coords.y, coords.z, headingValue, true, true) - end, orphanMode) -end - -return lib.createPed diff --git a/imports/createVehicle/client.lua b/imports/createVehicle/client.lua deleted file mode 100644 index 0ae31e8fe..000000000 --- a/imports/createVehicle/client.lua +++ /dev/null @@ -1,82 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----@alias SeatPosition ----| -2 # SF_ANY ----| -1 # SF_FrontDriverSide ----| 0 # SF_FrontPassengerSide ----| 1 # SF_BackDriverSide ----| 2 # SF_BackPassengerSide ----| 3 # SF_AltFrontDriverSide ----| 4 # SF_AltFrontPassengerSide ----| 5 # SF_AltBackDriverSide ----| 6 # SF_AltBackPassengerSide - -if cache.game == 'redm' then - ---**RedM build** — calls the 9-argument `CREATE_VEHICLE` native - ---(`modelHash, x, y, z, heading, isNetwork, bScriptHostVeh, bDontAutoCreateDraftAnimals, p8`). - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading to face towards, in degrees. - ---@param isNetwork? boolean Whether to create a network vehicle. If false, the vehicle exists only locally. - ---@param bScriptHostVeh? boolean Pin the vehicle to the script host in the R* network model. - ---@param bDontAutoCreateDraftAnimals? boolean Skip automatic creation of draft animals (e.g. horses on a wagon). - ---@param p8? boolean Undocumented RedM parameter; passed as `false` if omitted. - lib.createVehicle = function(model, coords, heading, isNetwork, bScriptHostVeh, bDontAutoCreateDraftAnimals, p8) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(bScriptHostVeh == nil or type(bScriptHostVeh) == 'boolean', 'Invalid bScriptHostVeh flag provided, expected boolean') - assert(bDontAutoCreateDraftAnimals == nil or type(bDontAutoCreateDraftAnimals) == 'boolean', 'Invalid bDontAutoCreateDraftAnimals flag provided, expected boolean') - assert(p8 == nil or type(p8) == 'boolean', 'Invalid p8 flag provided, expected boolean') - - local headingValue = heading and heading + 0.0 or 0.0 - - return lib.spawnEntity(model, function(modelHash) - return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, bScriptHostVeh or false, bDontAutoCreateDraftAnimals or false, p8 or false) - end) - end -else - ---**GTA5/FiveM build** — calls the 7-argument `CREATE_VEHICLE` native - ---(`modelHash, x, y, z, heading, isNetwork, netMissionEntity`). - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading to face towards, in degrees. - ---@param isNetwork? boolean Whether to create a network vehicle. If false, the vehicle exists only locally. - ---@param netMissionEntity? boolean Pin the vehicle to the script host in the R* network model. - ---@param seat? SeatPosition The SeatPosition for any vehicle seat. - ---@param properties? table A table of vehicle properties to set on the vehicle after spawning. See `lib.setVehicleProperties` for more details. - lib.createVehicle = function(model, coords, heading, isNetwork, netMissionEntity, seat, properties) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(isNetwork == nil or type(isNetwork) == 'boolean', 'Invalid isNetwork flag provided, expected boolean') - assert(netMissionEntity == nil or type(netMissionEntity) == 'boolean', 'Invalid netMissionEntity flag provided, expected boolean') - assert(seat == nil or type(seat) == 'number', 'Invalid seat provided, expected number') - assert(properties == nil or type(properties) == 'table', 'Invalid properties provided, expected table') - - local headingValue = heading and heading + 0.0 or 0.0 - - local vehicle = lib.spawnEntity(model, function(modelHash) - return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, isNetwork or false, netMissionEntity or false) - end) - - if vehicle == 0 then return 0 end - - if seat then - TaskWarpPedIntoVehicle(cache.ped, vehicle, seat) - end - - if properties then - lib.setVehicleProperties(vehicle, properties) - end - - return vehicle - end -end - -return lib.createVehicle diff --git a/imports/createVehicle/server.lua b/imports/createVehicle/server.lua deleted file mode 100644 index 795651ddf..000000000 --- a/imports/createVehicle/server.lua +++ /dev/null @@ -1,77 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----Vehicle category for `CreateVehicleServerSetter` (GTA5 only). ----@alias VehicleType ----| 'automobile' ----| 'bike' ----| 'boat' ----| 'heli' ----| 'plane' ----| 'submarine' ----| 'trailer' ----| 'train' - -if cache.game == 'redm' then - ---**RedM build** — calls the CFX `CREATE_VEHICLE` server native - ---(`modelHash, x, y, z, heading, isNetwork, netMissionEntity`). - ---`vehicleType`, `properties`, and `seat` from the GTA5 wrapper are not - ---available on RedM (`setVehicleProperties` and `CreateVehicleServerSetter` - ---are GTA5-only). - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading to face towards, in degrees. - ---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. - lib.createVehicle = function(model, coords, heading, orphanMode) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - - local headingValue = heading and heading + 0.0 or 0.0 - - return lib.spawnEntity('vehicle', model, function(modelHash) - return CreateVehicle(modelHash, coords.x, coords.y, coords.z, headingValue, true, true) - end, orphanMode) - end -else - ---**GTA5/FiveM build** — calls `CreateVehicleServerSetter` with the supplied - ---`vehicleType`. Optionally applies vehicle properties and warps a player - ---into a seat after the entity exists. - ---@param model string | number The model to spawn - ---@param coords vector3 Spawn coordinate - ---@param heading? number Heading to face towards, in degrees. - ---@param vehicleType? VehicleType Vehicle category; defaults to `'automobile'`. - ---@param properties? table Properties applied via `lib.setVehicleProperties` after spawning. - ---@param seat? { source:number, seat:SeatPosition } If provided, the source will be warped into the specified seat after spawning. - ---@param orphanMode? EntityOrphanMode Server-side cleanup behavior for the entity. - lib.createVehicle = function(model, coords, heading, vehicleType, properties, seat, orphanMode) - assert(coords and coords.x and coords.y and coords.z, 'Invalid coordinates vector3 provided') - assert(heading == nil or type(heading) == 'number', 'Invalid heading provided, expected number') - assert(vehicleType == nil or type(vehicleType) == 'string', 'Invalid vehicleType provided, expected string') - assert(properties == nil or type(properties) == 'table', 'Invalid properties provided, expected table') - - local headingValue = heading and heading + 0.0 or 0.0 - - local vehicle = lib.spawnEntity('vehicle', model, function(modelHash) - return CreateVehicleServerSetter(modelHash, vehicleType or 'automobile', coords.x, coords.y, coords.z, headingValue) - end, orphanMode) - - if vehicle == 0 then return 0 end - - if seat then - TaskWarpPedIntoVehicle(cache.ped, vehicle, seat) - end - - if properties then - lib.setVehicleProperties(vehicle, properties) - end - - return vehicle - end -end - -return lib.createVehicle diff --git a/imports/entity/shared.lua b/imports/entity/shared.lua new file mode 100644 index 000000000..9c52e0634 --- /dev/null +++ b/imports/entity/shared.lua @@ -0,0 +1,270 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +-- Capture the global `Entity()` state-bag accessor before our local class shadows it. +local getEntityStateBag = Entity + +---Base class wrapping a CFX entity handle. Used directly via `lib.entity:new(handle)` +---to wrap any pre-existing entity, or as the parent of `lib.object`, `lib.ped`, and +---`lib.vehicle` for typed spawn wrappers. +---@class Entity : OxClass +---@field handle number Native entity handle. +---@field script string Resource that created or wrapped this entity. +local Entity = lib.class('Entity') + +local IS_SERVER = IsDuplicityVersion() + +---@param handle number +function Entity:constructor(handle) + local handleType = type(handle) + assert(handleType == 'number' and handle ~= 0, ('expected non-zero entity handle, got %s (%s)'):format(handleType, tostring(handle))) + + self.handle = handle + self.script = GetInvokingResource() or cache.resource +end + +function Entity:exists() + return DoesEntityExist(self.handle) +end + +function Entity:delete() + if self:exists() then + DeleteEntity(self.handle) + end +end + +---@return vector3 +function Entity:getCoords() + return GetEntityCoords(self.handle) +end + +---@param coords vector3 +---@param alive? boolean Unused by the game; debug-only assert flag. Default `false`. +---@param deadFlag? boolean Disable physics for dead peds as well. Default `false`. +---@param ragdollFlag? boolean Special flag used for ragdolling peds. Default `false`. +---@param clearArea? boolean Clear any entities in the target area. Default `false`. +function Entity:setCoords(coords, alive, deadFlag, ragdollFlag, clearArea) + SetEntityCoords(self.handle, coords.x, coords.y, coords.z, alive or false, deadFlag or false, ragdollFlag or false, clearArea or false) +end + +---@return number +function Entity:getHeading() + return GetEntityHeading(self.handle) +end + +---@param heading number +function Entity:setHeading(heading) + SetEntityHeading(self.handle, heading + 0.0) +end + +---@return vector3 +function Entity:getRotation() + return GetEntityRotation(self.handle, 2) +end + +---@param rotation vector3 +function Entity:setRotation(rotation) + SetEntityRotation(self.handle, rotation.x + 0.0, rotation.y + 0.0, rotation.z + 0.0, 2, true) +end + +---@return number +function Entity:getModel() + return GetEntityModel(self.handle) +end + +---Returns the entity's state bag. +function Entity:getState() + return getEntityStateBag(self.handle).state +end + +---Re-spawn the entity at new coords, preserving the original constructor data. +---Subclasses must provide a static `spawn(modelHash, data)` returning a new handle. +---@param coords? vector3 Defaults to the entity's current coords (or the original spawn coords). +---@param heading? number Defaults to the entity's current heading. +---@return number? handle New entity handle, or nil on failure. +function Entity:respawn(coords, heading) + local cls = getmetatable(self) + if type(cls.spawn) ~= 'function' then + error(('%s:respawn is not implemented (missing static `spawn`)'):format(cls.__name or 'Entity'), 2) + end + + local priv = self.private or {} + local exists = self:exists() + local modelHash = priv.modelHash or (exists and GetEntityModel(self.handle)) or nil + local fallbackCoords = exists and self:getCoords() or nil + local fallbackHeading = exists and self:getHeading() or nil + + coords = coords or fallbackCoords or (priv.spawnData and priv.spawnData.coords) + if not coords then return nil end + heading = heading or fallbackHeading or (priv.spawnData and priv.spawnData.heading) + + if not modelHash then return nil end + + if exists then DeleteEntity(self.handle) end + + local data = priv.spawnData and table.clone(priv.spawnData) or {} + data.coords = coords + data.heading = heading + + local newHandle = cls.spawn(modelHash, data) + if newHandle == 0 then return nil end + + self.handle = newHandle + + if heading then self:setHeading(heading) end + if data.rotation then self:setRotation(data.rotation) end + + if IS_SERVER and cache.game ~= 'redm' then + self:setOrphanMode(data.orphanMode or 2) + end + + -- Cache the latest spawn data in case it was mutated. + if self.private then self.private.spawnData = data end + + self:onAfterRespawn(data) + + return newHandle +end + +---@protected +---@param data table The cloned spawn data used for this respawn. +function Entity:onAfterRespawn(data) end + +if IS_SERVER then + local allowClientServerEntityCreation = GetConvarInt('ox:allowClientServerEntityCreation', 0) == 1 + + ---@return number networkId + function Entity:getNetworkId() + return NetworkGetNetworkIdFromEntity(self.handle) + end + + ---@param mode EntityOrphanMode + function Entity:setOrphanMode(mode) + if cache.game == 'redm' then + lib.print.warn('Entity:setOrphanMode is unavailable on RedM (no SetEntityOrphanMode native); ignoring call.') + return + end + + SetEntityOrphanMode(self.handle, mode) + end + + ---@protected + ---@param spawn fun(modelHash: number, data: table): number Native spawner returning the entity handle. + ---@param data table Spawn data; `data.model` may be a string or precomputed hash. + ---@param assetType string Label used in error messages (`'object'`, `'ped'`, `'vehicle'`). + ---@return number handle + ---@return number modelHash + function Entity.createServer(spawn, data, assetType) + local modelHash = type(data.model) == 'number' and data.model or joaat(data.model) --[[@as number]] + local handle = spawn(modelHash, data) + + if handle == 0 then + error(('failed to spawn %s %s'):format(assetType, data.model), 3) + end + + local ok, err = pcall(lib.waitFor, function() + if DoesEntityExist(handle) then return true end + end, ('%s %s did not materialize'):format(assetType, data.model), 5000) + + if not ok then + lib.print.error(err) + if DoesEntityExist(handle) then DeleteEntity(handle) end + error(('%s failed to spawn within timeout'):format(assetType), 3) + end + + return handle, modelHash + end + + ---Registers the client→server spawn proxy callback for a subclass. + ---When `ox:allowClientServerEntityCreation` is enabled, the callback constructs + ---@param cls table Subclass to instantiate (e.g. `ObjectServer`). + ---@param callbackName string Callback identifier, e.g. `'ox_lib:createObject'`. + ---@param assetType string Label used in warnings (`'object'`, `'ped'`, `'vehicle'`). + function Entity.registerCreateCallback(cls, callbackName, assetType) + lib.callback.register(callbackName, function(source, data) + if not allowClientServerEntityCreation then + lib.print.warn(('player %d attempted server-side %s spawn but convar is disabled'):format(source, assetType)) + return nil + end + + local ok, instance = pcall(cls.new, cls, data) + if not ok then + lib.print.error(instance) + return nil + end + + return instance:getNetworkId() + end) + end +else + local allowClientEntityCreation = GetConvarInt('ox:allowClientEntityCreation', 0) == 1 + local allowClientServerEntityCreation = GetConvarInt('ox:allowClientServerEntityCreation', 0) == 1 + + ---@return boolean + function Entity:isNetworked() + return NetworkGetEntityIsNetworked(self.handle) + end + + ---@return number? networkId nil if the entity is not networked. + function Entity:getNetworkId() + if not NetworkGetEntityIsNetworked(self.handle) then return nil end + return NetworkGetNetworkIdFromEntity(self.handle) + end + + ---@protected + ---Shared client spawn flow used by `lib.object`, `lib.ped`, and `lib.vehicle`. + ---@param spawn fun(modelHash: number, data: table): number Native spawner used for local creation. + ---@param data table Spawn data forwarded to the server callback or to `spawn`. + ---@param callbackName string Server callback identifier, e.g. `'ox_lib:createObject'`. + ---@param assetType string Label used in error messages (`'object'`, `'ped'`, `'vehicle'`). + ---@return number handle + ---@return number? modelHash + function Entity.createClient(spawn, data, callbackName, assetType) + local wantsNetwork = data.isNetwork == true + + if wantsNetwork and not allowClientEntityCreation then + error(('client-side networked %s creation is disabled (set `ox:allowClientEntityCreation`)'):format(assetType), 3) + end + + local useProxy = wantsNetwork and allowClientServerEntityCreation + local handle, modelHash + + if useProxy then + local netId = lib.callback.await(callbackName, false, data) + if not netId or netId == 0 then + error(('server refused or failed to spawn %s %s'):format(assetType, data.model), 3) + end + + local ok, syncedHandle = pcall(lib.waitFor, function() + local h = NetworkGetEntityFromNetworkId(netId) + if h ~= 0 and DoesEntityExist(h) then return h end + end, ('%s netId %s did not sync to client'):format(assetType, netId), 5000) + + if not ok then + lib.print.error(syncedHandle) + end + + handle = ok and syncedHandle or 0 + modelHash = handle ~= 0 and GetEntityModel(handle) or nil + else + modelHash = lib.requestModel(data.model, 10000) + handle = spawn(modelHash, data) + SetModelAsNoLongerNeeded(modelHash) + end + + if handle == 0 or not DoesEntityExist(handle) then + error(('failed to spawn %s %s'):format(assetType, data.model), 3) + end + + return handle, modelHash + end +end + +lib.entity = Entity + +return lib.entity diff --git a/imports/object/client.lua b/imports/object/client.lua new file mode 100644 index 000000000..589706ca5 --- /dev/null +++ b/imports/object/client.lua @@ -0,0 +1,61 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@class ObjectInitClient +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Applied via `SetEntityHeading` after spawn. +---@field rotation? vector3 Applied via `SetEntityRotation` after spawn (rotation order 2). +---@field isNetwork? boolean Whether to create a network object. Default `false`. +---@field netMissionEntity? boolean **GTA5 only.** Pin to script host. Default `false`. +---@field doorFlag? boolean **GTA5 only.** Set true to spawn door models in network mode. +---@field bScriptHostObj? boolean **RedM only.** Pin to script host. Default `false`. +---@field dynamic? boolean **RedM only.** Whether the object should be dynamic. +---@field p7? boolean **RedM only.** Undocumented. Default `false`. +---@field p8? boolean **RedM only.** Undocumented. Default `false`. + +---Client-side spawnable object. +---@class ObjectClient : Entity +local ObjectClient = lib.class('ObjectClient', lib.entity) + +---@param data ObjectInitClient +function ObjectClient:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createClient(ObjectClient.spawn, data, 'ox_lib:createObject', 'object') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash + + if data.heading then self:setHeading(data.heading) end + if data.rotation then self:setRotation(data.rotation) end +end + +---@protected +---Internal spawn helper used by both the constructor and `:respawn()`. +---@param modelHash number +---@param data ObjectInitClient +---@return number handle +function ObjectClient.spawn(modelHash, data) + if cache.game == 'redm' then + return CreateObject(modelHash, data.coords.x, data.coords.y, data.coords.z, + data.isNetwork or false, data.bScriptHostObj or false, + data.dynamic or false, data.p7 or false, data.p8 or false) + end + + return CreateObject(modelHash, data.coords.x, data.coords.y, data.coords.z, + data.isNetwork or false, data.netMissionEntity or false, data.doorFlag or false) +end + +lib.object = ObjectClient + +return lib.object diff --git a/imports/object/server.lua b/imports/object/server.lua new file mode 100644 index 000000000..43b4d2f9a --- /dev/null +++ b/imports/object/server.lua @@ -0,0 +1,65 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@class ObjectInitServer +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Applied via `SetEntityHeading` after spawn. +---@field rotation? vector3 Applied via `SetEntityRotation` after spawn (rotation order 2). +---@field orphanMode? EntityOrphanMode Server-side cleanup behavior. Default `2` (KeepEntity). +---@field doorFlag? boolean **GTA5 only.** Set true to spawn door models in network mode. +---@field dynamic? boolean **RedM only.** Whether the object should be dynamic (physics-driven). +---@field bScriptHostObj? boolean **RedM only.** Pin to script host. Defaults to `true`. +---@field p7? boolean **RedM only.** Undocumented. Default `false`. +---@field p8? boolean **RedM only.** Undocumented. Default `false`. + +---Server-side spawnable object. +---@class ObjectServer : Entity +local ObjectServer = lib.class('ObjectServer', lib.entity) + +---@param data ObjectInitServer +function ObjectServer:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createServer(ObjectServer.spawn, data, 'object') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash + + if data.heading then self:setHeading(data.heading) end + if data.rotation then self:setRotation(data.rotation) end + + if cache.game ~= 'redm' then + self:setOrphanMode(data.orphanMode or 2) + end +end + +---@protected +---Internal spawn helper used by both the constructor and `:respawn()`. +---@param modelHash number +---@param data ObjectInitServer +---@return number handle +function ObjectServer.spawn(modelHash, data) + if cache.game == 'redm' then + return CreateObject(modelHash, data.coords.x, data.coords.y, data.coords.z, + true, data.bScriptHostObj or false, data.dynamic or false, data.p7 or false, data.p8 or false) + end + + return CreateObject(modelHash, data.coords.x, data.coords.y, data.coords.z, true, true, data.doorFlag or false) +end + +lib.object = ObjectServer + +-- Client→server proxy (gated by `ox:allowClientServerEntityCreation`). +lib.entity.registerCreateCallback(ObjectServer, 'ox_lib:createObject', 'object') + +return lib.object diff --git a/imports/ped/client.lua b/imports/ped/client.lua new file mode 100644 index 000000000..a3c4b106a --- /dev/null +++ b/imports/ped/client.lua @@ -0,0 +1,63 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@class PedInitClient +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Heading the ped should face, in degrees. +---@field isNetwork? boolean Whether to create a network ped. Default `false`. +---@field bScriptHostPed? boolean Pin to script host. Default `false`. +---@field pedType? number **GTA5 only.** AI behavior type. 4 = CIVMALE, etc. Default 0. +---@field p7? boolean **RedM only.** Undocumented. Default `false`. +---@field p8? boolean **RedM only.** Undocumented. Default `false`. + +---Client-side spawnable ped. +---@class PedClient : Entity +local PedClient = lib.class('PedClient', lib.entity) + +---@param data PedInitClient +function PedClient:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createClient(PedClient.spawn, data, 'ox_lib:createPed', 'ped') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash +end + +---@protected +---@param modelHash number +---@param data PedInitClient +---@return number handle +function PedClient.spawn(modelHash, data) + local headingValue = data.heading and data.heading + 0.0 or 0.0 + + if cache.game == 'redm' then + return CreatePed(modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, + data.isNetwork or false, data.bScriptHostPed or false, data.p7 or false, data.p8 or false) + end + + return CreatePed(data.pedType or 0, modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, + data.isNetwork or false, data.bScriptHostPed or false) +end + +---Warp the ped into a vehicle seat. Client-only; uses `TaskWarpPedIntoVehicle`. +---@param vehicle number | Entity Vehicle handle or wrapper instance. +---@param seat? number Seat index. Default `-1` (driver). +function PedClient:warpInto(vehicle, seat) + local vehicleHandle = type(vehicle) == 'table' and vehicle.handle or vehicle + TaskWarpPedIntoVehicle(self.handle, vehicleHandle, seat or -1) +end + +lib.ped = PedClient + +return lib.ped diff --git a/imports/ped/server.lua b/imports/ped/server.lua new file mode 100644 index 000000000..d4b70ebc4 --- /dev/null +++ b/imports/ped/server.lua @@ -0,0 +1,66 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---@class PedInitServer +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Heading the ped should face, in degrees. +---@field pedType? number **GTA5 only.** AI behavior type. 4 = CIVMALE, 5 = CIVFEMALE, etc. Default 0. Ignored on RedM. +---@field orphanMode? EntityOrphanMode Server-side cleanup behavior. Default `2` (KeepEntity). + +---Server-side spawnable ped. +---@class PedServer : Entity +local PedServer = lib.class('PedServer', lib.entity) + +---@param data PedInitServer +function PedServer:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createServer(PedServer.spawn, data, 'ped') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash + + if cache.game ~= 'redm' then + self:setOrphanMode(data.orphanMode or 2) + end +end + +---@protected +---@param modelHash number +---@param data PedInitServer +---@return number handle +function PedServer.spawn(modelHash, data) + local headingValue = data.heading and data.heading + 0.0 or 0.0 + + if cache.game == 'redm' then + -- RedM CFX CreatePed: pedType is `Unused` per the native docs but still required as the first arg. + return CreatePed(0, modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, true, true) + end + + return CreatePed(data.pedType or 0, modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, true, true) +end + +---Place the ped into a vehicle seat. Uses the server-side `SetPedIntoVehicle` native +---(no `TaskWarpPedIntoVehicle` on the server). +---@param vehicle number | Entity Vehicle handle or wrapper instance. +---@param seat? number Seat index. Default `-1` (driver). +function PedServer:setIntoVehicle(vehicle, seat) + local vehicleHandle = type(vehicle) == 'table' and vehicle.handle or vehicle + SetPedIntoVehicle(self.handle, vehicleHandle, seat or -1) +end + +lib.ped = PedServer + +lib.entity.registerCreateCallback(PedServer, 'ox_lib:createPed', 'ped') + +return lib.ped diff --git a/imports/spawnEntity/client.lua b/imports/spawnEntity/client.lua deleted file mode 100644 index 7fb73abca..000000000 --- a/imports/spawnEntity/client.lua +++ /dev/null @@ -1,31 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----Load `model`, run `spawn` to create the entity, then release the model. ----Used internally by `lib.createObject`, `lib.createPed`, and `lib.createVehicle`. ----@async ----@param model string | number Model name or precomputed hash. ----@param spawn fun(modelHash: number): number Native spawner returning the entity handle. ----@param timeout? number Milliseconds to wait for the model to load. Defaults to 10000. ----@return number entity Entity handle, or 0 on failure. -function lib.spawnEntity(model, spawn, timeout) - assert(type(model) == 'string' or type(model) == 'number', 'Invalid model provided, expected string or number') - assert(type(spawn) == 'function', 'Invalid spawn provided, expected function') - assert(timeout == nil or type(timeout) == 'number', 'Invalid timeout provided, expected number') - - local modelHash = lib.requestModel(model, timeout or 10000) - local entity = spawn(modelHash) - - SetModelAsNoLongerNeeded(modelHash) - - if entity == 0 or not DoesEntityExist(entity) then return 0 end - - return entity -end - -return lib.spawnEntity diff --git a/imports/spawnEntity/server.lua b/imports/spawnEntity/server.lua deleted file mode 100644 index d7a1a9cc8..000000000 --- a/imports/spawnEntity/server.lua +++ /dev/null @@ -1,51 +0,0 @@ ---[[ - https://github.com/overextended/ox_lib - - This file is licensed under LGPL-3.0 or higher - - Copyright © 2025 Linden -]] - ----Server-side entity cleanup mode. Used by `SetEntityOrphanMode`. ----@alias EntityOrphanMode ----| 0 # DeleteWhenNotRelevant — default; deletes when no player is relevant. ----| 1 # DeleteOnOwnerDisconnect — deletes when the original owner disconnects. ----| 2 # KeepEntity — never deleted by server relevancy checks. - ----Spawn a server-side entity, wait for it to materialize, then apply an orphan mode. ----Used internally by `lib.createObject`, `lib.createPed`, and `lib.createVehicle`. ----@async ----@param assetType string Label used in error messages, e.g. 'object', 'ped', 'vehicle'. ----@param model string | number Model name or precomputed hash. ----@param spawn fun(modelHash: number): number Native spawner returning the entity handle. ----@param orphanMode? EntityOrphanMode Defaults to 2 (KeepEntity). ----@param timeout? number Milliseconds to wait for the entity to exist. Defaults to 5000. ----@return number entity Entity handle, or 0 on failure. -function lib.spawnEntity(assetType, model, spawn, orphanMode, timeout) - assert(type(model) == 'string' or type(model) == 'number', 'Invalid model provided, expected string or number') - assert(type(spawn) == 'function', 'Invalid spawn provided, expected function') - assert(orphanMode == nil or (type(orphanMode) == 'number' and orphanMode >= 0 and orphanMode <= 2), 'Invalid orphanMode provided, expected number between 0 and 2') - assert(timeout == nil or type(timeout) == 'number', 'Invalid timeout provided, expected number') - - local modelHash = type(model) == 'number' and model or joaat(model) - local entity = spawn(modelHash) - - if entity == 0 then return 0 end - - -- Spawning can lag in high-population sessions; wait until the entity - -- actually exists before continuing or giving up. - local success, err = pcall(lib.waitFor, function() - if DoesEntityExist(entity) then return true end - end, ('failed to spawn %s %s'):format(assetType, model), timeout or 5000) - - if not success then - lib.print.error(err) - return 0 - end - - SetEntityOrphanMode(entity, orphanMode or 2) - - return entity -end - -return lib.spawnEntity diff --git a/imports/vehicle/client.lua b/imports/vehicle/client.lua new file mode 100644 index 000000000..2fbd23ea3 --- /dev/null +++ b/imports/vehicle/client.lua @@ -0,0 +1,95 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---Seat index for vehicle natives. Accepts the `eSeatPosition` enum constants +---(`-2 = SF_ANY`, `-1 = driver`, `0 = front passenger`, …) or any raw integer. +---@alias SeatPosition eSeatPosition | number + +---@class VehicleInitClient +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Heading the vehicle should face, in degrees. +---@field isNetwork? boolean Whether to create a network vehicle. Default `false`. +---@field netMissionEntity? boolean **GTA5 only.** Pin to script host. Default `false`. +---@field bScriptHostVeh? boolean **RedM only.** Pin to script host. Default `false`. +---@field bDontAutoCreateDraftAnimals? boolean **RedM only.** Skip auto-creation of draft animals. Default `false`. +---@field p8? boolean **RedM only.** Undocumented. Default `false`. +---@field properties? table **GTA5 only.** Properties applied via `lib.setVehicleProperties` after spawning. + +---Client-side spawnable vehicle. +---@class VehicleClient : Entity +local VehicleClient = lib.class('VehicleClient', lib.entity) + +---@param data VehicleInitClient +function VehicleClient:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createClient(VehicleClient.spawn, data, 'ox_lib:createVehicle', 'vehicle') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash + + if data.properties and cache.game ~= 'redm' then + lib.setVehicleProperties(handle, data.properties) + end +end + +---@protected +---@param modelHash number +---@param data VehicleInitClient +---@return number handle +function VehicleClient.spawn(modelHash, data) + local headingValue = data.heading and data.heading + 0.0 or 0.0 + + if cache.game == 'redm' then + return CreateVehicle(modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, + data.isNetwork or false, data.bScriptHostVeh or false, + data.bDontAutoCreateDraftAnimals or false, data.p8 or false) + end + + return CreateVehicle(modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, data.isNetwork or false, data.netMissionEntity or false) +end + +---Apply vehicle properties via `lib.setVehicleProperties`. GTA5 only — no-op on RedM. +---@param properties table +function VehicleClient:setProperties(properties) + if cache.game == 'redm' then return end + lib.setVehicleProperties(self.handle, properties) + if self.private and self.private.spawnData then + self.private.spawnData.properties = properties + end +end + +---@protected +---Re-applies stored vehicle properties after the entity is re-spawned (GTA5 only). +---@param data table +function VehicleClient:onAfterRespawn(data) + if data.properties and cache.game ~= 'redm' then + lib.setVehicleProperties(self.handle, data.properties) + end +end + +---Warp a ped into one of this vehicle's seats. Client-only; uses `TaskWarpPedIntoVehicle`. +---@param ped number | Entity Ped handle or wrapper instance. +---@param seat? SeatPosition Seat index. Default `-1` (driver). +---@return boolean +function VehicleClient:warpPed(ped, seat) + local pedHandle = type(ped) == 'table' and ped.handle or ped --[[@as number]] + seat = seat or -1 --[[@as number]] + if not IsVehicleSeatFree(self.handle, seat) then return false end + TaskWarpPedIntoVehicle(pedHandle, self.handle, seat) + return true +end + +lib.vehicle = VehicleClient + +return lib.vehicle diff --git a/imports/vehicle/server.lua b/imports/vehicle/server.lua new file mode 100644 index 000000000..efffefe9a --- /dev/null +++ b/imports/vehicle/server.lua @@ -0,0 +1,100 @@ +--[[ + https://github.com/overextended/ox_lib + + This file is licensed under LGPL-3.0 or higher + + Copyright © 2025 Linden +]] + +---Vehicle category for `CreateVehicleServerSetter` (GTA5 only). +---@alias VehicleType +---| 'automobile' +---| 'bike' +---| 'boat' +---| 'heli' +---| 'plane' +---| 'submarine' +---| 'trailer' +---| 'train' + +---@class VehicleInitServer +---@field model string | number Model name or precomputed hash. +---@field coords vector3 Spawn coordinate. +---@field heading? number Heading the vehicle should face, in degrees. +---@field type? VehicleType **GTA5 only.** Vehicle category passed to `CreateVehicleServerSetter`. Default `'automobile'`. +---@field properties? table **GTA5 only.** Properties applied via `lib.setVehicleProperties` after spawning. +---@field orphanMode? EntityOrphanMode Server-side cleanup behavior. Default `2` (KeepEntity). + +---Server-side spawnable vehcle. +---@class VehicleServer : Entity +local VehicleServer = lib.class('VehicleServer', lib.entity) + +---@param data VehicleInitServer +function VehicleServer:constructor(data) + assert(type(data) == 'table', 'expected table init data') + assert(data.coords and data.coords.x and data.coords.y and data.coords.z, 'expected vector3 coords') + assert(type(data.model) == 'string' or type(data.model) == 'number', 'expected string or number model') + + local handle, modelHash = lib.entity.createServer(VehicleServer.spawn, data, 'vehicle') + + self:super(handle) + + self.private.spawnData = data + self.private.modelHash = modelHash + + if cache.game ~= 'redm' then + self:setOrphanMode(data.orphanMode or 2) + + if data.properties then + lib.setVehicleProperties(handle, data.properties) + end + end +end + +---@protected +---@param modelHash number +---@param data VehicleInitServer +---@return number handle +function VehicleServer.spawn(modelHash, data) + local headingValue = data.heading and data.heading + 0.0 or 0.0 + + if cache.game == 'redm' then + return CreateVehicle(modelHash, data.coords.x, data.coords.y, data.coords.z, headingValue, true, true) + end + + return CreateVehicleServerSetter(modelHash, data.type or 'automobile', data.coords.x, data.coords.y, data.coords.z, headingValue) +end + +---Place a ped into one of this vehicle's seats. Uses the server-side +---`SetPedIntoVehicle` native (no `TaskWarpPedIntoVehicle` on the server). +---@param ped number | Entity Ped handle or wrapper instance. +---@param seat? number Seat index. Default `-1` (driver). +function VehicleServer:placePed(ped, seat) + local pedHandle = type(ped) == 'table' and ped.handle or ped --[[@as number]] + SetPedIntoVehicle(pedHandle, self.handle, seat or -1) +end + +---Apply vehicle properties via `lib.setVehicleProperties`. GTA5 only — no-op on RedM. +---@param properties table +function VehicleServer:setProperties(properties) + if cache.game == 'redm' then return end + lib.setVehicleProperties(self.handle, properties) + if self.private and self.private.spawnData then + self.private.spawnData.properties = properties + end +end + +---@protected +---Re-applies stored vehicle properties after the entity is re-spawned (GTA5 only). +---@param data table +function VehicleServer:onAfterRespawn(data) + if data.properties and cache.game ~= 'redm' then + lib.setVehicleProperties(self.handle, data.properties) + end +end + +lib.vehicle = VehicleServer + +lib.entity.registerCreateCallback(VehicleServer, 'ox_lib:createVehicle', 'vehicle') + +return lib.vehicle