Skip to content
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

feat(server): full vehicle persistence #621

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
22 changes: 22 additions & 0 deletions client/vehicle-persistence.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end

local zones
local cachedProps
local netId
local vehicle
Expand Down Expand Up @@ -71,4 +72,25 @@ 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)
for i = 1, #vehicles do
local data = vehicles[i]
zones[data.id] = lib.points.new({
distance = 75.0,
coords = data.coords,
onEnter = function()
TriggerServerEvent('qbx_core:server:spawnVehicle', data.id, data.coords)
end
})
end
end)

RegisterNetEvent('qbx_core:client:removeVehZone', function(id)
if zones[id] then
zones[id]:remove()
zones[id] = nil
end
end)
59 changes: 58 additions & 1 deletion server/vehicle-persistence.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
local persistence = GetConvarInt('qbx:enableVehiclePersistence', 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking change. Either keep the convar as a string an overload the meaning (right now values are 'true' or 'false. We could add 'full' as well. OR add a new true/false convar which controls whether vehicles are persisted between restarts.

Copy link
Member

@D4isDAVID D4isDAVID Dec 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, values "true" and "false" are interpreted as 1 and 0 respectively by GetConvarInt. But at this point this can actually be replaced by the new GetConvarBool I believe, which is available since artifact 10543 — we require 10731.

print('Vehicle persistence mode ' .. persistence)

Comment on lines +2 to +3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete

---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 +18,7 @@ end

exports('DisablePersistence', DisablePersistence)

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

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

Expand All @@ -29,6 +32,7 @@ RegisterNetEvent('qbx_core:server:vehiclePropsChanged', function(netId, diff)
local vehicleId = getVehicleId(vehicle)
if not vehicleId then return end

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

Expand Down Expand Up @@ -68,8 +72,15 @@ RegisterNetEvent('qbx_core:server:vehiclePropsChanged', function(netId, diff)
props.tyres = diff.tyres ~= 'deleted' and diff.tyres or nil
end

if persistence == 2 then
local entityCoords = GetEntityCoords(vehicle)
local entityHeading = GetEntityHeading(vehicle)
coords = vec4(entityCoords.x, entityCoords.y, entityCoords.z, entityHeading)
end

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

Expand Down Expand Up @@ -121,4 +132,50 @@ AddEventHandler('entityRemoved', function(entity)
local passenger = passengers[i]
SetPedIntoVehicle(passenger.ped, veh, passenger.seat)
end
end)

if persistence == 1 then return end

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

local function spawnVehicle(coords, id, model, props)
local _, veh = qbx.spawnVehicle({
spawnSource = vector4(coords.x, coords.y, coords.z, coords.w),
model = model,
props = props
})
exports.qbx_core:EnablePersistence(veh)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qbx.spawnVehicle already sets this.

Entity(veh).state:set('vehicleid', id, false)
SetVehicleDoorsLocked(veh, 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use qbx_vehiclekeys for this, as setting it directly bypasses checks and such that qbx_vehiclekeys does. Best to make it a config function of core so that people can input other key systems they might be running

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I see, but there's no export in qbx_vehiclekeys to lock a car ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll have to create one. Just extract the implementation of qb-vehiclekeys:server:setVehLockState into an export.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey I see, I'll create a pr in qbx_vehiclekeys then.

TriggerClientEvent('qbx_core:client:removeVehZone', -1, id)
end

lib.callback.register('qbx_core:server:getVehiclesToSpawn', function()
local vehicles = {}
local query = 'SELECT id, plate, coords FROM player_vehicles WHERE state = 0'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +161 to +163
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than being queried by every player. This data should be queried on server startup and cached. Then, as vehicles get spawned, the cache gets smaller and smaller.

local results = MySQL.query.await(query)
for _, data in pairs(results) do
local coords = json.decode(data.coords)
if coords and not checkVehicleExist(data.plate) then
vehicles[#vehicles + 1] = {
id = data.id,
coords = coords,
}
end
end
return vehicles
end)

RegisterNetEvent('qbx_core:server:spawnVehicle', function(id, coords)
local vehicle = exports.qbx_vehicles:GetPlayerVehicle(id)
if not vehicle then return end
spawnVehicle(coords, id, vehicle.modelName, vehicle.props)
Comment on lines +177 to +180
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an exploitable event. Needs a server side check. Once a cache exists, you can use it to verify that the id vehicle exists in cache near the specified coords

end)
Loading