Skip to content

feat(server): full vehicle persistence #621

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9d87f03
feat: full vehicle persistence
CodexisPhantom Nov 13, 2024
f00aa1c
fix: vehicles spawning multiple times if 1 player
CodexisPhantom Nov 13, 2024
f6e2391
refactor: use onResourceStart event
CodexisPhantom Nov 15, 2024
50b3523
fix: players never refreshed
CodexisPhantom Nov 20, 2024
52605fd
Merge branch 'Qbox-project:main' into main
CodexisPhantom Nov 29, 2024
a5189ae
Merge branch 'Qbox-project:main' into main
CodexisPhantom Dec 1, 2024
72f0be4
Merge branch 'Qbox-project:main' into main
CodexisPhantom Dec 3, 2024
0cdc565
Merge branch 'Qbox-project:main' into main
CodexisPhantom Dec 5, 2024
4dc64af
Merge branch 'Qbox-project:main' into main
CodexisPhantom Dec 23, 2024
8454ba5
refactor: better persistence handling
CodexisPhantom Dec 23, 2024
c820fe8
fix: vehicles duplication
CodexisPhantom Dec 28, 2024
2b1868c
Merge branch 'Qbox-project:main' into main
CodexisPhantom Dec 29, 2024
d7f08cf
refactor: better persistence handling
CodexisPhantom Jan 3, 2025
7318687
Merge branch 'main' into main
CodexisPhantom Jan 3, 2025
943513e
fix: use correct export from qbx_vehiclekeys
CodexisPhantom Jan 6, 2025
3bfedb8
Merge branch 'Qbox-project:main' into main
CodexisPhantom Jan 10, 2025
7f5925c
Merge branch 'Qbox-project:main' into main
CodexisPhantom Jan 23, 2025
6982f28
fix: position not saved correctly
CodexisPhantom Jan 23, 2025
9366980
fix: save a vector4
CodexisPhantom Jan 23, 2025
2996d23
refactor: removed useless check
CodexisPhantom Jan 23, 2025
c302631
refactor: cleaned code
CodexisPhantom Jan 24, 2025
7905660
feat: save position on server restart
CodexisPhantom Jan 24, 2025
5618df4
fix: check entity exist
CodexisPhantom Jan 24, 2025
4fc475f
fix: vehicle position for plane and heli
CodexisPhantom Jan 26, 2025
d2c326b
refactor: cleaned code
CodexisPhantom Jan 26, 2025
8822364
Merge branch 'main' into main
CodexisPhantom Feb 7, 2025
ec3a7f3
feat: save vehicle health
CodexisPhantom Feb 19, 2025
0e5bf09
Merge branch 'main' into main
CodexisPhantom Feb 19, 2025
2cd059b
Merge branch 'main' into main
CodexisPhantom Feb 27, 2025
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
35 changes: 35 additions & 0 deletions client/vehicle-persistence.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local netId
local vehicle
local seat

local zones = {}
local watchedKeys = {
'bodyHealth',
'engineHealth',
Expand Down Expand Up @@ -43,17 +44,37 @@ end

local function sendPropsDiff()
if not Entity(vehicle).state.persisted then return end

TriggerServerEvent('qbx_core:server:vehiclePositionChanged', netId)

local newProps = lib.getVehicleProperties(vehicle)
if not cachedProps then
cachedProps = newProps
return
end

local diff, hasChanged = calculateDiff(cachedProps, newProps)
cachedProps = newProps
if not hasChanged then return end

TriggerServerEvent('qbx_core:server:vehiclePropsChanged', netId, diff)
end

---@param vehicles table
local function createVehicleZones(vehicles)
for id, coords in pairs(vehicles) do
if not zones[id] then
zones[id] = lib.points.new({
distance = 75.0,
coords = coords,
onEnter = function()
TriggerServerEvent('qbx_core:server:spawnVehicle', id, coords)
end
})
end
end
end

lib.onCache('seat', function(newSeat)
if newSeat == -1 then
seat = -1
Expand All @@ -71,4 +92,18 @@ lib.onCache('seat', function(newSeat)
vehicle = nil
netId = nil
end
end)

AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
local vehicles = lib.callback.await('qbx_core:server:getVehiclesToSpawn', 2500)
if not vehicles then return end

createVehicleZones(vehicles)
end)

RegisterNetEvent('qbx_core:client:removeVehZone', function(id)
if not zones[id] then return end

zones[id]:remove()
zones[id] = nil
end)
8 changes: 8 additions & 0 deletions config/server.lua
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,18 @@ return {
role = {} -- Role to tag for high priority logs. Roles use <@%roleid> and users/channels are <@userid/channelid>
},

persistence = {
lockState = 'lock', -- 'lock' : vehicle will be locked when spawned, 'unlock' : vehicle will be unlocked when spawned
},

giveVehicleKeys = function(src, plate, vehicle)
return exports.qbx_vehiclekeys:GiveKeys(src, vehicle)
end,

setVehicleLock = function(vehicle, state)
exports.qbx_vehiclekeys:SetLockState(vehicle, state)
end,

getSocietyAccount = function(accountName)
return exports['Renewed-Banking']:getAccountMoney(accountName)
end,
Expand Down
159 changes: 151 additions & 8 deletions server/vehicle-persistence.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
local persistence = GetConvar('qbx:enableVehiclePersistence', 'false')

---A persisted vehicle will respawn when deleted. Only works for player owned vehicles.
---Vehicles spawned using lib are automatically persisted
---@param vehicle number
Expand All @@ -15,7 +17,7 @@ end

exports('DisablePersistence', DisablePersistence)

if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end
if persistence == 'false' then return end

assert(lib.checkDependency('qbx_vehicles', '1.4.1', true))

Expand Down Expand Up @@ -69,7 +71,7 @@ RegisterNetEvent('qbx_core:server:vehiclePropsChanged', function(netId, diff)
end

exports.qbx_vehicles:SaveVehicle(vehicle, {
props = props,
props = props
})
end)

Expand Down Expand Up @@ -102,11 +104,6 @@ AddEventHandler('entityRemoved', function(entity)

local playerVehicle = exports.qbx_vehicles:GetPlayerVehicle(vehicleId)

if DoesEntityExist(entity) then
Entity(entity).state:set('persisted', nil, true)
DeleteVehicle(entity)
end

local _, veh = qbx.spawnVehicle({
model = playerVehicle.props.model,
spawnSource = vec4(coords.x, coords.y, coords.z, heading),
Expand All @@ -121,4 +118,150 @@ AddEventHandler('entityRemoved', function(entity)
local passenger = passengers[i]
SetPedIntoVehicle(passenger.ped, veh, passenger.seat)
end
end)
end)

if persistence == 'full' then return end

local cachedVehicles = {}
local config = require 'config.server'

---@param plate string
---@return boolean
local function isVehicleSpawned(plate)
local vehicles = GetGamePool('CVehicle')

for i = 1, #vehicles do
local vehicle = vehicles[i]
if qbx.getVehiclePlate(vehicle) == plate then
return true
end
end

return false
end

--- Save the vehicle position to the database
---@param vehicle number
---@param coords vector3
---@param heading number
local function saveVehicle(vehicle, coords, heading)
local vehicleId = getVehicleId(vehicle)
if not vehicleId then return end

local props = exports.qbx_vehicles:GetPlayerVehicle(vehicleId)?.props
if not props then return end

local type = GetVehicleType(vehicle)

props.bodyHealth = GetVehicleBodyHealth(vehicle)
props.engineHealth = GetVehicleEngineHealth(vehicle)
props.tankHealth = GetVehiclePetrolTankHealth(vehicle)
props.dirtLevel = GetVehicleDirtLevel(vehicle)

if type == 'heli' or type == 'plane' then
coords = vec3(coords.x, coords.y, coords.z + 1.0)
end

exports.qbx_vehicles:SaveVehicle(vehicle, {
props = props,
coords = vec4(coords.x, coords.y, coords.z, heading)
})
end

--- Save all vehicle positions to the database
local function saveAllVehicle()
local vehicles = GetGamePool('CVehicle')
for i = 1, #vehicles do
local vehicle = vehicles[i]
if DoesEntityExist(vehicle) and Entity(vehicle).state.persisted then
saveVehicle(vehicle, GetEntityCoords(vehicle), GetEntityHeading(vehicle))
end
end
end

---@param coords vector4
---@param id number
---@param model string
---@param props table
local function spawnVehicle(coords, id, model, props)
if not coords or not id or not model or not props then return end

local _, veh = qbx.spawnVehicle({
spawnSource = vec4(coords.x, coords.y, coords.z, coords.w),
model = model,
props = props
})

cachedVehicles[id] = nil
Entity(veh).state:set('vehicleid', id, false)
TriggerClientEvent('qbx_core:client:removeVehZone', -1, id)
config.setVehicleLock(veh, config.persistence.lockState)
end

lib.callback.register('qbx_core:server:getVehiclesToSpawn', function()
return cachedVehicles
end)

AddEventHandler('onResourceStart', function(resourceName)
if resourceName ~= 'qbx_vehicles' then return end

local vehicles = exports.qbx_vehicles:GetPlayerVehicles({ states = 0 })
if not vehicles then return end

for i = 1, #vehicles do
local vehicle = vehicles[i]
if vehicle.coords and vehicle.props and vehicle.props.plate and not isVehicleSpawned(vehicle.props.plate) then
cachedVehicles[vehicle.id] = vehicle.coords
end
end
end)

AddEventHandler('onResourceStop', function(resourceName)
if resourceName ~= cache.resource then return end

saveAllVehicle()
end)

AddEventHandler('txAdmin:events:scheduledRestart', function(eventData)
if eventData.secondsRemaining ~= 60 then return end

saveAllVehicle()
end)

RegisterNetEvent('qbx_core:server:spawnVehicle', function(id, coords)
if not id or not coords then return end

local cachedCoords = cachedVehicles[id]
if not cachedCoords or
cachedCoords.x ~= coords.x or
cachedCoords.y ~= coords.y or
cachedCoords.z ~= coords.z or
cachedCoords.w ~= coords.w then
return
end

local vehicle = exports.qbx_vehicles:GetPlayerVehicle(id)
if not vehicle or not vehicle.modelName or not vehicle.props then return end

spawnVehicle(coords, id, vehicle.modelName, vehicle.props)
end)

RegisterNetEvent('qbx_core:server:vehiclePositionChanged', function(netId)
local src = source

local ped = GetPlayerPed(src)
local vehicle = NetworkGetEntityFromNetworkId(netId)

local vehicleId = getVehicleId(vehicle)
if not vehicleId then return end

local pedCoords = GetEntityCoords(ped)
local vehicleCoords = GetEntityCoords(vehicle)
local vehicleHeading = GetEntityHeading(vehicle)

if #(pedCoords - vehicleCoords) > 10.0 then
return
end

saveVehicle(vehicle, vehicleCoords, vehicleHeading)
end)
Loading