diff --git a/client/vehicle-persistence.lua b/client/vehicle-persistence.lua index 790826a08..4f86a30af 100644 --- a/client/vehicle-persistence.lua +++ b/client/vehicle-persistence.lua @@ -1,10 +1,14 @@ -if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end +local enable = GetConvar('qbx:enableVehiclePersistence', 'false') == 'true' +local full = GetConvar('qbx:vehiclePersistenceType', 'semi') == 'full' + +if not enable then return end local cachedProps local netId local vehicle local seat +local zones = {} local watchedKeys = { 'bodyHealth', 'engineHealth', @@ -43,17 +47,37 @@ end local function sendPropsDiff() if not Entity(vehicle).state.persisted then return end + + if full then TriggerServerEvent('qbx_core:server:vehiclePositionChanged', netId) end + 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 @@ -71,4 +95,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) \ No newline at end of file diff --git a/config/server.lua b/config/server.lua index 21c903025..4e82f7ff0 100644 --- a/config/server.lua +++ b/config/server.lua @@ -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, diff --git a/server/vehicle-persistence.lua b/server/vehicle-persistence.lua index d25b4d160..f3f1d35ba 100644 --- a/server/vehicle-persistence.lua +++ b/server/vehicle-persistence.lua @@ -1,3 +1,6 @@ +local enable = GetConvar('qbx:enableVehiclePersistence', 'false') == 'true' +local full = GetConvar('qbx:vehiclePersistenceType', 'semi') == 'full' + ---A persisted vehicle will respawn when deleted. Only works for player owned vehicles. ---Vehicles spawned using lib are automatically persisted ---@param vehicle number @@ -15,7 +18,7 @@ end exports('DisablePersistence', DisablePersistence) -if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end +if not enable then return end assert(lib.checkDependency('qbx_vehicles', '1.4.1', true)) @@ -121,4 +124,150 @@ AddEventHandler('entityRemoved', function(entity) local passenger = passengers[i] SetPedIntoVehicle(passenger.ped, veh, passenger.seat) end +end) + +if not 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) \ No newline at end of file