Skip to content

Instantly share code, notes, and snippets.

@0xDispatch
Last active December 12, 2024 13:40
Show Gist options
  • Save 0xDispatch/aec845db904efa7bcc2a795f3c978b92 to your computer and use it in GitHub Desktop.
Save 0xDispatch/aec845db904efa7bcc2a795f3c978b92 to your computer and use it in GitHub Desktop.
--[[
Note that this is just a concept, with large data or vehicles this approach may not be the best.
CREATE TABLE `player_vehicles` (
-- Unique identifier for each vehicle entry.
`id` int(11) NOT NULL,
-- Player's FiveM license (used for ownership verification)
`license` varchar(50) DEFAULT NULL,
-- Player's unique QB-Core identifier (used for ownership and queries)
`citizenid` varchar(11) DEFAULT NULL,
-- Vehicle model name (e.g., 'schafter2')
`vehicle` varchar(50) DEFAULT NULL,
-- Vehicle hash (numeric model hash stored as string, used for spawning)
`hash` varchar(50) DEFAULT NULL,
-- JSON string containing all vehicle modifications and properties
-- Used by QBCore.Functions.SetVehicleProperties to apply all vehicle customizations
`mods` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
-- Vehicle's license plate (it can be a unique identifier for the vehicle instance)
`plate` varchar(8) NOT NULL,
-- Alternate plate for police RP features (not used in our current code)
`fakeplate` varchar(8) DEFAULT NULL,
-- Current garage identifier where vehicle is stored (e.g., 'pillboxgarage')
`garage` varchar(50) DEFAULT NULL,
-- Current fuel level (0-100)
-- Used by Entity(veh).state.fuel in our code
`fuel` int(11) DEFAULT 100,
-- Engine health (0-1000)
-- Used by Entity(veh).state.engineHealth in our code
`engine` float DEFAULT 1000,
-- Body health (0-1000)
-- Used by Entity(veh).state.bodyHealth in our code
`body` float DEFAULT 1000,
-- Vehicle state:
-- 0 = out/in world
-- 1 = in garage
-- 2 = in impound
`state` int(11) DEFAULT 1,
-- Cost to retrieve vehicle from depot/impound
-- Used in PayDepotPrice event
`depotprice` int(11) NOT NULL DEFAULT 0,
-- Total distance driven
`drivingdistance` int(50) DEFAULT NULL,
-- JSON string for additional vehicle status info not quite sure about the usage
--
`status` text DEFAULT NULL,
-- Remaining balance on financed vehicle (for dealership scripts)
`balance` int(11) NOT NULL DEFAULT 0,
-- Payment amount per cycle for financed vehicles
`paymentamount` int(11) NOT NULL DEFAULT 0,
-- Number of payments remaining on finance
`paymentsleft` int(11) NOT NULL DEFAULT 0,
-- Unix timestamp for next finance payment
`financetime` int(11) NOT NULL DEFAULT 0,
-- JSON string storing last known coordinates
-- Format: {"x":221.8153839111328,"rx":-0.75409168004989,"z":30.021240234375,"y":-804.1450805664063,"rz":-114.45570373535156,"ry":-0.22070288658142}
-- Used by LoadSavedVehicles() to load vehicles where left
`position` text NOT NULL
-- JSON string storing last known coordinates
-- Format: {"x":221.8153839111328,"rx":-0.75409168004989,"z":30.021240234375,"y":-804.1450805664063,"rz":-114.45570373535156,"ry":-0.22070288658142}
-- This is same as position but its more like OwlV approach, it will be only set by /park
`spawn_position` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
--
-- Dumping data for table `player_vehicles`
--
INSERT INTO `player_vehicles` (`id`, `license`, `citizenid`, `vehicle`, `hash`, `mods`, `plate`, `fakeplate`, `garage`, `fuel`, `engine`, `body`, `state`, `depotprice`, `drivingdistance`, `status`, `balance`, `paymentamount`, `paymentsleft`, `financetime`, `position`, `spawn_position`) VALUES
(3, 'license:5368f1863d6d8f07e676da40d86a9ee571a15945', 'DPW41984', 'ardent', '159274291', '{\"modExhaust\":0,\"modEngineBlock\":-1,\"modCustomTiresF\":false,\"modVanityPlate\":-1,\"modTransmission\":3,\"modSuspension\":2,\"modKit49\":-1,\"modTrunk\":-1,\"plate\":\"6ID293RO\",\"modSmokeEnabled\":false,\"modArmor\":-1,\"liveryRoof\":-1,\"modAerials\":-1,\"modAPlate\":-1,\"dashboardColor\":93,\"modSideSkirt\":-1,\"modTank\":-1,\"wheelWidth\":0.51967996358871,\"modRoof\":-1,\"modFrontWheels\":201,\"fuelLevel\":80.22715170715243,\"wheels\":11,\"modSeats\":-1,\"extras\":[],\"modDashboard\":-1,\"interiorColor\":93,\"wheelColor\":11,\"modSteeringWheel\":-1,\"engineHealth\":1000.0592475178704,\"doorStatus\":{\"1\":false,\"2\":false,\"3\":false,\"4\":false,\"5\":false,\"0\":false},\"modDoorSpeaker\":-1,\"modGrille\":-1,\"modFender\":1,\"dirtLevel\":0.0,\"color2\":0,\"modPlateHolder\":-1,\"modSpeakers\":-1,\"modHydrolic\":-1,\"model\":159274291,\"modAirFilter\":-1,\"tankHealth\":4000.2369900714818,\"neonColor\":[255,0,255],\"modKit19\":-1,\"modTrimA\":-1,\"modFrame\":-1,\"modKit17\":-1,\"modCustomTiresR\":false,\"bodyHealth\":1000.0592475178704,\"modSpoilers\":2,\"modTurbo\":1,\"neonEnabled\":[false,false,false,false],\"modEngine\":3,\"pearlescentColor\":87,\"modXenon\":false,\"plateIndex\":5,\"modHood\":5,\"color1\":88,\"oilLevel\":6.35462587779425,\"tireBurstState\":{\"1\":false,\"2\":false,\"3\":false,\"4\":false,\"5\":false,\"0\":false},\"windowStatus\":{\"1\":true,\"2\":true,\"3\":true,\"4\":false,\"5\":false,\"6\":true,\"7\":true,\"0\":true},\"modDial\":-1,\"modHorns\":55,\"modLivery\":0,\"tireHealth\":{\"1\":1000.0,\"2\":1000.0,\"3\":1000.0,\"0\":1000.0},\"modStruts\":-1,\"wheelSize\":0.63397371768951,\"modRearBumper\":0,\"modKit21\":-1,\"modWindows\":-1,\"modOrnaments\":-1,\"modArchCover\":-1,\"windowTint\":1,\"tireBurstCompletely\":{\"1\":false,\"2\":false,\"3\":false,\"4\":false,\"5\":false,\"0\":false},\"modTrimB\":-1,\"modShifterLeavers\":-1,\"modBrakes\":2,\"modKit47\":-1,\"modBackWheels\":-1,\"modRightFender\":-1,\"tyreSmokeColor\":[255,255,255],\"modFrontBumper\":3,\"xenonColor\":255}', '6ID293RO', NULL, NULL, 100, 100, 100, 0, 0, NULL, '{\"clutch\":100,\"brakes\":100,\"axle\":100,\"fuel\":100,\"radiator\":100}', 0, 0, 0, 0, '', '{\"z\":30.021240234375,\"y\":-804.1714477539063,\"x\":221.8153839111328,\"rz\":-112.84789276123047,\"ry\":-0.27606293559074,\"rx\":-0.80089968442916}'),
--]]
--pd-vehicle_load server main.lua
local QBCore = exports['qb-core']:GetCoreObject()
local spawnedCount = 0
-- Function to delete all vehicles when resource restarts.
local function DeleteAllVehicles()
local vehicles = GetAllVehicles()
for _, vehicle in pairs(vehicles) do
DeleteEntity(vehicle)
end
end
-- Function to spawn saved vehicles at their last positions
local function LoadSavedVehicles()
local startTime = os.clock()
print('^3[Vehicle Load] Loading vehicles from last positions^7')
-- First delete any existing vehicles to prevent duplicates
DeleteAllVehicles()
local result = MySQL.query.await('SELECT * FROM player_vehicles WHERE (position IS NOT NULL AND position != "") OR (spawn_position IS NOT NULL AND spawn_position != "")')
if not result then return end
for _, vehicle in pairs(result) do
local positionData = nil
local success = false
-- Try to get position from last position first
if vehicle.position and vehicle.position ~= "" then
success, positionData = pcall(json.decode, vehicle.position)
end
-- If no last position, try spawn_position
if not success and vehicle.spawn_position and vehicle.spawn_position ~= "" then
success, positionData = pcall(json.decode, vehicle.spawn_position)
end
if success and positionData then
local vehType = QBCore.Shared.Vehicles[vehicle.vehicle] and QBCore.Shared.Vehicles[vehicle.vehicle].type or 'automobile'
local x, y, z = tonumber(positionData.x), tonumber(positionData.y), tonumber(positionData.z)
if x and y and z then
local veh = CreateVehicleServerSetter(joaat(vehicle.vehicle), vehType, x, y, z, 0.0)
-- Wait for vehicle creation
local timeout = 0
while not DoesEntityExist(veh) and timeout < 50 do
Wait(100)
timeout = timeout + 1
end
if DoesEntityExist(veh) then
-- Set rotation if available
if positionData.rx and positionData.ry and positionData.rz then
SetEntityRotation(veh, positionData.rx, positionData.ry, positionData.rz, 2, true)
end
SetVehicleNumberPlateText(veh, vehicle.plate)
-- Set vehicle properties
local success2, mods = pcall(json.decode, vehicle.mods)
if success2 and mods then
Entity(veh).state.vehicleProperties = mods
end
-- Set all vehicle stats at once
local engineHealth = tonumber(vehicle.engine)
local bodyHealth = tonumber(vehicle.body)
local fuelLevel = tonumber(vehicle.fuel)
Entity(veh).state.carStats = {
fuel = fuelLevel ~= nil and fuelLevel or 100,
engineHealth = engineHealth ~= nil and engineHealth or 1000,
bodyHealth = bodyHealth ~= nil and bodyHealth or 1000
}
-- Update database and tracking
MySQL.update('UPDATE player_vehicles SET state = 0, garage = NULL WHERE plate = ?', { vehicle.plate })
Entity(veh).state:set('isSpawned', true, true)
spawnedCount = spawnedCount + 1
else
print('^1[Vehicle Load] Failed to create vehicle: '..vehicle.plate..'^7')
end
end
end
end
local endTime = os.clock()
local timeTaken = (endTime - startTime) * 1000 -- Convert to milliseconds
print(string.format('^2[Vehicle Load] Spawned %d vehicles in %.2fms^7', spawnedCount, timeTaken))
end
-- Handler
AddEventHandler('onResourceStart', function(resource)
if resource == GetCurrentResourceName() then
Wait(100)
print('^3[Vehicle Load] Resource starting...^7')
if Config.AutoRespawn then
local startTime = os.clock()
-- First update vehicles without position data
MySQL.update('UPDATE player_vehicles SET state = 1 WHERE state = 0 AND (position IS NULL OR position = "")', {})
-- Then load vehicles with position data
LoadSavedVehicles()
local endTime = os.clock()
local totalTime = (endTime - startTime) * 1000
print(string.format('^2[Vehicle Load] Resource started in %.2fms^7', totalTime))
else
print('^3[Vehicle Load] AutoRespawn is disabled, updating depot prices...^7')
MySQL.update('UPDATE player_vehicles SET depotprice = 500 WHERE state = 0', {})
end
end
end)
-- Add the park command, anyone can use it on any car but it's just concept for now.
QBCore.Commands.Add('park', 'Save vehicle parking spot', {}, false, function(source)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local vehicle = GetVehiclePedIsIn(GetPlayerPed(src), true)
if vehicle == 0 then
TriggerClientEvent('QBCore:Notify', src, 'You are not in a vehicle.', 'error')
return
end
local plate = GetVehicleNumberPlateText(vehicle)
local coords = GetEntityCoords(vehicle)
local rotation = GetEntityRotation(vehicle)
local positionData = {
x = coords.x,
y = coords.y,
z = coords.z,
rx = rotation.x,
ry = rotation.y,
rz = rotation.z
}
MySQL.update('UPDATE player_vehicles SET spawn_position = ? WHERE plate = ? AND citizenid = ?',
{ json.encode(positionData), plate, Player.PlayerData.citizenid },
function(affectedRows)
if affectedRows > 0 then
TriggerClientEvent('QBCore:Notify', src, 'Parking spot saved!', 'success')
else
TriggerClientEvent('QBCore:Notify', src, 'Could not save parking spot', 'error')
end
end
)
end)
--[[
----------------------------------------
pd-vehicle_load client main.lua |
----------------------------------------
--]]
local QBCore = exports['qb-core']:GetCoreObject()
-- Handle vehicle properties
AddStateBagChangeHandler('vehicleProperties', nil, function(bagName, _, value)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 or not DoesEntityExist(entity) then return end
if value then
QBCore.Functions.SetVehicleProperties(entity, value)
end
end)
-- Handle all car stats (fuel, engine, body)
AddStateBagChangeHandler('carStats', nil, function(bagName, _, value)
local entity = GetEntityFromStateBagName(bagName)
if entity == 0 or not DoesEntityExist(entity) then return end
if value then
-- Set fuel
if value.fuel then
exports[Config.FuelResource]:SetFuel(entity, value.fuel)
end
-- Set engine health
if value.engineHealth then
SetVehicleEngineHealth(entity, value.engineHealth)
end
-- Set body health
if value.bodyHealth then
SetVehicleBodyHealth(entity, value.bodyHealth)
end
end
end)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment