-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: persistent player vehicles (#568)
* feat: persistent player vehicles * fix: linting * removed from manifest * fix: driver switching seats or exiting breaks loop only * feat: persistence tracking * use statebag instead of server table * fix(server/vehicle-persistence): typo * fix(server/vehicle-persistence): remove state only when entity exists * disabling by default * delete vehicles using internal function * deprecate qbx.deleteVehicle * revert: deprecating qbx.deleteVehicle * feat: warp passengers back into a vehicle on removal * use persistence exports instead of event and make them always available * camelCasing convar and defaulting to false --------- Co-authored-by: David Malchin <malchin459@gmail.com>
- Loading branch information
Showing
6 changed files
with
217 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end | ||
|
||
local cachedProps | ||
local netId | ||
local vehicle | ||
local seat | ||
|
||
local watchedKeys = { | ||
'bodyHealth', | ||
'engineHealth', | ||
'tankHealth', | ||
'fuelLevel', | ||
'oilLevel', | ||
'dirtLevel', | ||
'windows', | ||
'doors', | ||
'tyres', | ||
} | ||
|
||
---Calculates the difference in values of two tables for the watched keys. | ||
---If the second table does not have a value that the first table has, it will be marked 'deleted'. | ||
---@param tbl1 table | ||
---@param tbl2 table | ||
---@return table diff | ||
---@return boolean hasChanged if diff table is not empty | ||
local function calculateDiff(tbl1, tbl2) | ||
local diff = {} | ||
local hasChanged = false | ||
|
||
for i = 1, #watchedKeys do | ||
local key = watchedKeys[i] | ||
local val1 = tbl1[key] | ||
local val2 = tbl2[key] | ||
|
||
if val1 ~= val2 then | ||
diff[key] = val2 == nil and 'deleted' or val2 | ||
hasChanged = true | ||
end | ||
end | ||
|
||
return diff, hasChanged | ||
end | ||
|
||
local function sendPropsDiff() | ||
if not Entity(vehicle).state.persisted then return 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 | ||
|
||
lib.onCache('seat', function(newSeat) | ||
if newSeat == -1 then | ||
seat = -1 | ||
vehicle = cache.vehicle | ||
netId = NetworkGetNetworkIdFromEntity(vehicle) | ||
CreateThread(function() | ||
while seat == -1 do | ||
sendPropsDiff() | ||
Wait(10000) | ||
end | ||
end) | ||
elseif seat == -1 then | ||
seat = nil | ||
sendPropsDiff() | ||
vehicle = nil | ||
netId = nil | ||
end | ||
end) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
---A persisted vehicle will respawn when deleted. Only works for player owned vehicles. | ||
---Vehicles spawned using lib are automatically persisted | ||
---@param vehicle number | ||
local function enablePersistence(vehicle) | ||
Entity(vehicle).state:set('persisted', true, true) | ||
end | ||
|
||
exports('EnablePersistence', enablePersistence) | ||
|
||
---A vehicle without persistence will not respawn when deleted. | ||
---@param vehicle number | ||
function DisablePersistence(vehicle) | ||
Entity(vehicle).state:set('persisted', nil, true) | ||
end | ||
|
||
exports('DisablePersistence', DisablePersistence) | ||
|
||
if GetConvar('qbx:enableVehiclePersistence', 'false') == 'false' then return end | ||
|
||
assert(lib.checkDependency('qbx_vehicles', '1.4.1', true)) | ||
|
||
local function getVehicleId(vehicle) | ||
return Entity(vehicle).state.vehicleid or exports.qbx_vehicles:GetVehicleIdByPlate(GetVehicleNumberPlateText(vehicle)) | ||
end | ||
|
||
RegisterNetEvent('qbx_core:server:vehiclePropsChanged', function(netId, diff) | ||
local vehicle = NetworkGetEntityFromNetworkId(netId) | ||
|
||
local vehicleId = getVehicleId(vehicle) | ||
if not vehicleId then return end | ||
|
||
local props = exports.qbx_vehicles:GetPlayerVehicle(vehicleId)?.props | ||
if not props then return end | ||
|
||
if diff.bodyHealth then | ||
props.bodyHealth = GetVehicleBodyHealth(vehicle) | ||
end | ||
|
||
if diff.engineHealth then | ||
props.engineHealth = GetVehicleEngineHealth(vehicle) | ||
end | ||
|
||
if diff.tankHealth then | ||
props.tankHealth = GetVehiclePetrolTankHealth(vehicle) | ||
end | ||
|
||
if diff.fuelLevel then | ||
props.fuelLevel = diff.fuelLevel ~= 'deleted' and diff.fuelLevel or nil | ||
end | ||
|
||
if diff.oilLevel then | ||
props.oilLevel = diff.oilLevel ~= 'deleted' and diff.oilLevel or nil | ||
end | ||
|
||
if diff.dirtLevel then | ||
props.dirtLevel = GetVehicleDirtLevel(vehicle) | ||
end | ||
|
||
if diff.windows then | ||
props.windows = diff.windows ~= 'deleted' and diff.windows or nil | ||
end | ||
|
||
if diff.doors then | ||
props.doors = diff.doors ~= 'deleted' and diff.doors or nil | ||
end | ||
|
||
if diff.tyres then | ||
local damage = {} | ||
for i = 0, 7 do | ||
if IsVehicleTyreBurst(vehicle, i, false) then | ||
damage[i] = IsVehicleTyreBurst(vehicle, i, true) and 2 or 1 | ||
end | ||
end | ||
|
||
props.tyres = damage | ||
end | ||
|
||
exports.qbx_vehicles:SaveVehicle(vehicle, { | ||
props = props, | ||
}) | ||
end) | ||
|
||
local function getPedsInVehicleSeats(vehicle) | ||
local occupants = {} | ||
local occupantsI = 1 | ||
for i = -1, 7 do | ||
local ped = GetPedInVehicleSeat(vehicle, i) | ||
if ped ~= 0 then | ||
occupants[occupantsI] = { | ||
ped = ped, | ||
seat = i, | ||
} | ||
occupantsI += 1 | ||
end | ||
end | ||
return occupants | ||
end | ||
|
||
AddEventHandler('entityRemoved', function(entity) | ||
if not Entity(entity).state.persisted then return end | ||
local coords = GetEntityCoords(entity) | ||
local heading = GetEntityHeading(entity) | ||
local bucket = GetEntityRoutingBucket(entity) | ||
local passengers = getPedsInVehicleSeats(entity) | ||
|
||
local vehicleId = getVehicleId(entity) | ||
if not vehicleId then return end | ||
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), | ||
bucket = bucket, | ||
props = playerVehicle.props | ||
}) | ||
|
||
for i = 1, #passengers do | ||
local passenger = passengers[i] | ||
SetPedIntoVehicle(passenger.ped, veh, passenger.seat) | ||
end | ||
end) |