Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6b6667b
Merge pull request #1539 from Kenshiin13/spawn-vehicle
Kenshiin13 Dec 14, 2024
8ea8b4a
feat(es_extended/server/classes/vehicle): add vehicle class
Kenshiin13 Dec 20, 2024
3d16c4f
feat(es_extended/server/functions): add exposed vehicle class functions
Kenshiin13 Dec 20, 2024
5c44b5c
refactor(es_extended/server/functions): move ESX.GetExtendedVehicleFr…
Kenshiin13 Dec 20, 2024
520446a
refactor(es_extended/server/classes/vehicle): only select relevant da…
Kenshiin13 Dec 20, 2024
68508b9
fix(es_extended/server/classes/vehicle): remove unused function getProps
Kenshiin13 Dec 20, 2024
04ef04e
fix(es_extended/server/classes/vehicle): properly parse vehicleProps
Kenshiin13 Dec 21, 2024
4e6e37e
fix(es_extended/server/classes/vehicle): fix vehicle model extraction
Kenshiin13 Dec 21, 2024
9d7c2ed
Merge pull request #1553 from Mycroft-Studios/better-vehicle-spawning
Kenshiin13 Dec 16, 2024
7341b37
fix(es_extended/server/classes/vehicle): fix plate race condition
Kenshiin13 Dec 21, 2024
2babbdd
fix(es_extended/server/classes/vehicle): fix plate not updating in db
Kenshiin13 Dec 21, 2024
734c5ec
fix(es_extended/server/classes/vehicle): confirm updates to db before…
Kenshiin13 Dec 21, 2024
88ad287
refactor(es_extended/server/classes/vehicle): better variable name & …
Kenshiin13 Dec 21, 2024
5656505
refactor(es_extended/server/functions): move GetExtendedVehicleFromPl…
Kenshiin13 Dec 21, 2024
ea668af
fix(es_extended/server/classes/vehicle): properly object to new vehic…
Kenshiin13 Dec 21, 2024
203dc9a
fix(es_extended/server/classes/vehicle): invalidate vehicle on failed…
Kenshiin13 Dec 21, 2024
0fe26d2
fix(es_extended/server/classes/vehicle): add owner check in db query
Kenshiin13 Dec 21, 2024
efc9117
feat(es_extended/server/classes/vehicle): add support for setting gar…
Kenshiin13 Dec 21, 2024
9c7d7ea
refactor(es_extended/server/classes/vehicle): better variable naming
Kenshiin13 Dec 21, 2024
ac7aa3f
refactor(es_extended/server/classes/vehicle): rename classes
Kenshiin13 Dec 22, 2024
9635d47
fix(es_extended/server/classes/vehicle): properly annotate vehicleDat…
Kenshiin13 Dec 22, 2024
738e0ea
refactor(es_extended/server/classes/vehicle): rename xVehicle to vehi…
Kenshiin13 Dec 22, 2024
a812285
refactor(es_extended/server/classes/vehicle): refactory query
Kenshiin13 Dec 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions [core]/es_extended/client/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,14 @@ end
---@param vehicleModel integer | string The vehicle to spawn
---@param coords table | vector3 The coords to spawn the vehicle at
---@param heading number The heading of the vehicle
---@param cb? function The callback function
---@param cb? fun(vehicle: number) The callback function
---@param networked? boolean Whether the vehicle should be networked
---@return nil
---@return number? vehicle
function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
if cb and not ESX.IsFunctionReference(cb) then
error("Invalid callback function")
end

local model = type(vehicleModel) == "number" and vehicleModel or joaat(vehicleModel)
local vector = type(coords) == "vector3" and coords or vec(coords.x, coords.y, coords.z)
local isNetworked = networked == nil or networked
Expand All @@ -549,8 +553,15 @@ function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
return error(("Resource ^5%s^1 Tried to spawn vehicle on the client but the position is too far away (Out of onesync range)."):format(executingResource))
end

local promise = not cb and promise.new()
CreateThread(function()
ESX.Streaming.RequestModel(model)
local modelHash = ESX.Streaming.RequestModel(model)
if not modelHash then
if promise then
return promise:reject(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
end
error(("Tried to spawn invalid vehicle - ^5%s^7!"):format(model))
end

local vehicle = CreateVehicle(model, vector.x, vector.y, vector.z, heading, isNetworked, true)

Expand All @@ -569,10 +580,16 @@ function ESX.Game.SpawnVehicle(vehicleModel, coords, heading, cb, networked)
Wait(0)
end

if cb then
if promise then
promise:resolve(vehicle)
elseif cb then
cb(vehicle)
end
end)

if promise then
return Citizen.Await(promise)
end
end

---@param vehicle integer The vehicle to spawn
Expand Down
1 change: 1 addition & 0 deletions [core]/es_extended/fxmanifest.lua
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ server_scripts {
'server/common.lua',
'server/modules/callback.lua',
'server/classes/player.lua',
'server/classes/vehicle.lua',
'server/classes/overrides/*.lua',
'server/functions.lua',
'server/modules/onesync.lua',
Expand Down
237 changes: 237 additions & 0 deletions [core]/es_extended/server/classes/vehicle.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
---@class CVehicleData
---@field plate string
---@field netId number
---@field entity number
---@field modelHash number
---@field owner string

---@class CExtendedVehicle
---@field plate string
---@field isValid fun(self:CExtendedVehicle):boolean
---@field new fun(owner:string, plate:string, coords:vector4): CExtendedVehicle?
---@field getFromPlate fun(plate:string):CExtendedVehicle?
---@field getPlate fun(self:CExtendedVehicle):string?
---@field getNetId fun(self:CExtendedVehicle):number?
---@field getEntity fun(self:CExtendedVehicle):number?
---@field getModelHash fun(self:CExtendedVehicle):number?
---@field getOwner fun(self:CExtendedVehicle):string?
---@field setPlate fun(self:CExtendedVehicle, newPlate:string):boolean
---@field setProps fun(self:CExtendedVehicle, newProps:table):boolean
---@field setOwner fun(self:CExtendedVehicle, newOwner:string):boolean
---@field delete fun(self:CExtendedVehicle, garageName:string?, isImpound:boolean?):nil
Core.vehicleClass = {
plate = "",
new = function(owner, plate, coords)
assert(type(owner) == "string", "Expected 'owner' to be a string")
assert(type(plate) == "string", "Expected 'plate' to be a string")
assert(type(coords) == "vector4", "Expected 'coords' to be a vector4")

local xVehicle = Core.vehicleClass.getFromPlate(plate)
if xVehicle then
return xVehicle
end

local vehicleProps = MySQL.scalar.await("SELECT `vehicle` FROM `owned_vehicles` WHERE `stored` = true AND `owner` = ? AND `plate` = ? LIMIT 1", { owner, plate })
if not vehicleProps then
return
end
vehicleProps = json.decode(vehicleProps)

if type(vehicleProps.model) ~= "number" then
vehicleProps.model = joaat(vehicleProps.model)
end

local netId = ESX.OneSync.SpawnVehicle(vehicleProps.model, coords.xyz, coords.w, vehicleProps)
if not netId then
return
end

local entity = NetworkGetEntityFromNetworkId(netId)
if entity <= 0 then
return
end
Entity(entity).state:set("owner", owner, false)
Entity(entity).state:set("plate", plate, false)

---@type CVehicleData
local vehicleData = {
plate = plate,
entity = entity,
netId = netId,
modelHash = vehicleProps.model,
owner = owner,
}
Core.vehicles[plate] = vehicleData

MySQL.update.await("UPDATE `owned_vehicles` SET `stored` = false WHERE `owner` = ? AND `plate` = ?", { owner, plate })

local obj = table.clone(Core.vehicleClass)
obj.plate = plate
TriggerEvent("esx:createdExtendedVehicle", obj)

return obj
end,
getFromPlate = function(plate)
assert(type(plate) == "string", "Expected 'plate' to be a string")

if Core.vehicles[plate] then
local obj = table.clone(Core.vehicleClass)
obj.plate = plate

if obj:isValid() then
return obj
end
end
end,
isValid = function(self)
local vehicleData = Core.vehicles[self.plate]
if not vehicleData then
return false
end

local entity = NetworkGetEntityFromNetworkId(vehicleData.netId)
if entity <= 0 or Entity(entity).state.owner ~= vehicleData.owner or Entity(entity).state.plate ~= vehicleData.plate then
self:delete()
return false
end

vehicleData.entity = entity

return true
end,
getNetId = function(self)
if not self:isValid() then
return
end

return Core.vehicles[self.plate].netId
end,
getEntity = function(self)
if not self:isValid() then
return
end

return Core.vehicles[self.plate].entity
end,
getPlate = function(self)
if not self:isValid() then
return
end

return Core.vehicles[self.plate].plate
end,
getModelHash = function(self)
if not self:isValid() then
return
end

return Core.vehicles[self.plate].modelHash
end,
getOwner = function(self)
if not self:isValid() then
return
end

return Core.vehicles[self.plate].owner
end,
setPlate = function(self, newPlate)
if not self:isValid() then
return false
end
assert(type(newPlate) == "string", "Expected 'plate' to be a string")

local vehicleData = Core.vehicles[self.plate]
local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `plate` = ? WHERE `plate` = ? AND `owner` = ?", { newPlate, vehicleData.plate, vehicleData.owner })
if affectedRows <= 0 then
self:delete()
return false
end

Entity(vehicleData.entity).state:set("plate", newPlate, false)
SetVehicleNumberPlateText(vehicleData.entity, newPlate)

local oldPlate = vehicleData.plate
vehicleData.plate = newPlate
Core.vehicles[newPlate] = table.clone(vehicleData)
Core.vehicles[oldPlate] = nil

TriggerEvent("esx:changedExtendedVehiclePlate", vehicleData.plate, oldPlate)
Wait(0)

return true
end,
setProps = function(self, newProps)
if not self:isValid() then
return false
end
assert(type(newProps) == "table", "Expected 'props' to be a table")

local vehicleData = Core.vehicles[self.plate]
local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `vehicle` = ? WHERE `plate` = ? AND `owner` = ?", json.encode(newProps), vehicleData.plate, vehicleData.owner)
if affectedRows <= 0 then
self:delete()
return false
end

Entity(vehicleData.entity).state:set("VehicleProperties", newProps, true)

return true
end,
setOwner = function(self, newOwner)
if not self:isValid() then
return false
end
assert(type(newOwner) == "string", "Expected 'owner' to be a string")

local vehicleData = Core.vehicles[self.plate]
if vehicleData.owner == newOwner then
return true
end

local affectedRows = MySQL.update.await("UPDATE `owned_vehicles` SET `owner` = ? WHERE owner = ? AND `plate` = ?", { newOwner, vehicleData.owner, vehicleData.plate })
if affectedRows <= 0 then
self:delete()
return false
end

Entity(vehicleData.entity).state:set("owner", newOwner, false)
vehicleData.owner = newOwner

return true
end,
delete = function(self, garageName, isImpound)
if type(garageName) ~= "string" then
garageName = nil
end
if type(isImpound) ~= "boolean" then
isImpound = false
end

local vehicleData = Core.vehicles[self.plate]
if not vehicleData then
return
end

local entity = NetworkGetEntityFromNetworkId(vehicleData.netId)
if entity >= 0 and Entity(entity).state.owner == vehicleData.owner then
DeleteEntity(vehicleData.entity)
end

local query = "UPDATE `owned_vehicles` SET `stored` = true WHERE `plate` = ? AND `owner` = ?"
local queryParams = { vehicleData.plate, vehicleData.owner }
if garageName then
if isImpound then
query = "UPDATE `owned_vehicles` SET `stored` = true, `parking` = NULL, `pound` = ? WHERE `plate` = ? AND `owner` = ?"
else
query = "UPDATE `owned_vehicles` SET `stored` = true, `pound` = NULL, `parking` = ? WHERE `plate` = ? AND `owner` = ?"
end

queryParams = { garageName, vehicleData.plate, vehicleData.owner }
end

MySQL.update.await(query, queryParams)
TriggerEvent("esx:deletedExtendedVehicle", self)

Core.vehicles[self.plate] = nil
end,
}
2 changes: 2 additions & 0 deletions [core]/es_extended/server/common.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Core.PlayerFunctionOverrides = {}
Core.DatabaseConnected = false
Core.playersByIdentifier = {}

---@type table<string, CVehicleData>
Core.vehicles = {}
Core.vehicleTypesByModel = {}

RegisterNetEvent("esx:onPlayerSpawn", function()
Expand Down
14 changes: 14 additions & 0 deletions [core]/es_extended/server/functions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -615,3 +615,17 @@ function Core.IsPlayerAdmin(playerId)
local xPlayer = ESX.Players[playerId]
return (xPlayer and Config.AdminGroups[xPlayer.group] and true) or false
end

---@param owner string
---@param plate string
---@param coords vector4
---@return CExtendedVehicle?
function ESX.CreateExtendedVehicle(owner, plate, coords)
return Core.vehicleClass.new(owner, plate, coords)
end

---@param plate string
---@return CExtendedVehicle?
function ESX.GetExtendedVehicleFromPlate(plate)
return Core.vehicleClass.getFromPlate(plate)
end
Loading
Loading