local SPAWN_TIMEOUT = 10000 local SPAWN_DISTANCE = 200 -- optimize Lua functions local math_floor = math.floor local table_concat = table.concat local os_time, os_difftime, os_nanotime = os.time, os.difftime, os.nanotime local json_encode, json_decode = json.encode, json.decode -- optimize natives local GetAllVehicles, GetGameTimer, DoesEntityExist, CreateVehicleServerSetter, DeleteEntity, NetworkGetEntityOwner, NetworkGetNetworkIdFromEntity, NetworkGetEntityFromNetworkId, GetEntityRoutingBucket, SetEntityRoutingBucket, SetEntityOrphanMode, GetEntityCoords, GetEntityRotation, GetVehicleDoorLockStatus, GetEntityType, GetVehicleType, GetVehicleNumberPlateText, IsEntityPositionFrozen, GetPlayerIdentifierByType, GetEntityFromStateBagName, GetInvokingResource = GetAllVehicles, GetGameTimer, DoesEntityExist, CreateVehicleServerSetter, DeleteEntity, NetworkGetEntityOwner, NetworkGetNetworkIdFromEntity, NetworkGetEntityFromNetworkId, GetEntityRoutingBucket, SetEntityRoutingBucket, SetEntityOrphanMode, GetEntityCoords, GetEntityRotation, GetVehicleDoorLockStatus, GetEntityType, GetVehicleType, GetVehicleNumberPlateText, IsEntityPositionFrozen, GetPlayerIdentifierByType, GetEntityFromStateBagName, GetInvokingResource -- list of all detected state bags on vehicle entities local stateBagList = {} -- list of vehicles that fail to spawn and how often it happened local failedSpawnList = {} local Ox = GetResourceState("ox_core") == "started" and exports["ox_core"] or nil -- script start up CreateThread(function() CreateAndReadFromStorage() if (Cleanup.onScriptStart) then CleanupProcess() end StartMainLoop() end) -- get all vehicles from the database function CreateAndReadFromStorage() Storage.Create() local rows = Storage.GetAllVehicles() Log("Found %s saved vehicles in database.", #rows) local loadedVehicles = GetLoadedVehiclesWithId(GetAllVehicles()) for i, row in ipairs(rows) do local tuning = json_decode(row.tuning) if (not tuning) then LogDebug("Vehicle \"%s\" has no tuning defined, skipping.", row.id) goto skipEntry end if (not tuning[1] or tuning[1]:len() < 8) then LogDebug("Vehicle \"%s\" has an invalid plate defined, skipping.", row.id) goto skipEntry end if (not row.model) then LogDebug("Vehicle \"%s\" (\"%s\") has no model defined, skipping.", row.id, tuning[1]) goto skipEntry end if (not row.type) then LogDebug("Vehicle \"%s\" (\"%s\") has no type defined, skipping.", row.id, tuning[1]) goto skipEntry end local status = json_decode(row.status) if (not status) then LogDebug("Vehicle \"%s\" (\"%s\") has no status defined, skipping.", row.id, tuning[1]) goto skipEntry end local position = vector3(row.posX, row.posY, row.posZ) if (#(vector3(0,0,0) - position) < 1.0) then LogDebug("Vehicle \"%s\" (\"%s\") detected at origin, skipping.", row.id, tuning[1]) goto skipEntry end savedVehicles[row.id] = { handle = nil, model = row.model, type = row.type, status = status, tuning = tuning, extraValues = json_decode(row.extraValues), stateBags = ChangeTablesToVector(json_decode(row.stateBags)), bucket = row.bucket, --attachedTo = json_decode(row.attachedTo), --attachedVehicles = {}, position = position, rotation = vector3(row.rotX, row.rotY, row.rotZ), lastUpdate = row.lastUpdate, initialPlayer = row.initialPlayer, lastPlayer = row.lastPlayer, spawning = false } if (loadedVehicles[row.id]) then -- if vehicle exists, add handle savedVehicles[row.id].handle = loadedVehicles[row.id] LogDebug("Found vehicle \"%s\" (\"%s\") at %s", row.id, tuning[1], RoundVector3(GetEntityCoords(savedVehicles[row.id].handle), 2)) else -- ... otherwise add to spawn queue spawnQueue[row.id] = true end -- add state bags to list for bagName, _ in pairs(savedVehicles[row.id].stateBags) do if (not stateBagList[bagName]) then stateBagList[bagName] = true end end ::skipEntry:: end -- handle attached vehicles --for id, vehicleData in pairs(savedVehicles) do -- if (vehicleData.attachedTo[1]) then -- table_insert(savedVehicles[vehicleData.attachedTo[1]].attachedVehicles, { id, vehicleData.attachedTo[2], vehicleData.attachedTo[3] }) -- end --end end -- main loop for spawning and updating vehicles function StartMainLoop() CreateThread(function() while (true) do Wait(5000) local players = GetPlayers() if (#players > 0) then local playerPedsWithHandlers = GetAllPlayerPedsWithHandles(players) local playerPeds = GetAllPlayerPeds() SpawnVehicles(players, playerPedsWithHandlers) UpdateVehicles(players, playerPedsWithHandlers, playerPeds) end end end) end -- try spawning vehicles function SpawnVehicles(players, playerPedsWithHandlers) -- check if any vehicle needs respawning for id, _ in pairs(spawnQueue) do if (not savedVehicles[id].spawnTime and savedVehicles[id].handle == nil and GetClosestPlayer(savedVehicles[id].position, SPAWN_DISTANCE, players, playerPedsWithHandlers, savedVehicles[id].bucket)) then -- vehicle not found, spawn it when player is close CreateThread(function() SpawnVehicle(id, savedVehicles[id]) end) end end end -- update vehicles function UpdateVehicles(players, playerPedsWithHandlers, playerPeds) for id, vehicleData in pairs(savedVehicles) do local exists = vehicleData.handle and DoesEntityExist(vehicleData.handle) if (exists and GetClosestPlayer(vehicleData.position, 150.0, players, playerPedsWithHandlers, vehicleData.bucket) and NetworkGetEntityOwner(vehicleData.handle) ~= -1) then TryUpdateVehicle(id, vehicleData, playerPeds) elseif (not exists and not spawnQueue[id]) then spawnQueue[id] = true end end end -- handle removal of entities if not from AP itself AddEventHandler("entityRemoved", function(entity) if (GetEntityType(entity) ~= 2) then return end local position = GetEntityCoords(entity) local rotation = GetEntityRotation(entity) local id = Entity(entity)?.state?.ap_id if (not id or not savedVehicles[id]) then return end savedVehicles[id].position = position savedVehicles[id].rotation = rotation savedVehicles[id].handle = nil savedVehicles[id].spawnTime = nil spawnQueue[id] = true UpdateVehicleInDB(id, savedVehicles[id], "entityRemoved event") end) local function IsInBounds(value, min, max) return value >= min and value <= max end -- check if update is necessary and update function TryUpdateVehicle(id, vehicleData, playerPeds, ignorePlayerInside) local vehicle = vehicleData.handle if ((not ignorePlayerInside and IsAnyPlayerInsideVehicle(vehicle, playerPeds)) or Entity(vehicle)?.state?.ap_trailer) then return end if (GetVehicleNumberPlateText(vehicle) ~= vehicleData.tuning[1]) then LogDebug("Faulty data on vehicle \"%s\" (\"%s\"). Respawning with correct data.", id, vehicleData.tuning[1]) DeleteEntity(vehicle) savedVehicles[id].handle = nil spawnQueue[id] = true return end if (forceUnfreezeVehicles and not vehicleData.isFrozen and IsEntityPositionFrozen(vehicle)) then FreezeEntityPosition(vehicle, false) LogDebug("Unfreezing vehicle \"%s\" (\"%s\")", id, vehicleData.tuning[1]) end local newPos = RoundVector3(GetEntityCoords(vehicle), 2) local newRot = RoundVector3(GetEntityRotation(vehicle), 2) local newLockStatus = GetVehicleDoorLockStatus(vehicle) --local attachedToEntity = GetEntityAttachedTo(vehicle) local status = vehicleData.status local posChange = not vehicleData.extraValues.boatAnchor and #(vehicleData.position - newPos) > 1.0 local rotChange = GetRotationDifference(vehicleData.rotation, newRot) > 15.0 local lockChange = newLockStatus ~= status[7] and not ((newLockStatus == 0 and status[7] == 1) or (newLockStatus == 1 and status[7] == 0)) --local attachmentChange = attachedToEntity ~= vehicleData.attachedTo[1] --if (attachmentChange and attachedToEntity and DoesEntityExist(attachedToEntity)) then -- local attachedTo_Id = Entity(attachedToEntity).state.ap_id -- if (attachedTo_Id and savedVehicles[attachedTo_Id]) then -- if (not savedVehicles[attachedTo_Id].attachedVehicles) then -- savedVehicles[attachedTo_Id].attachedVehicles = {} -- end -- -- table_insert(savedVehicles[attachedTo_Id].attachedVehicles, { id, newPos, newRot }) -- end --end if (posChange or rotChange or lockChange or attachmentChange) then local reasons = { "Server side:" } if (posChange) then reasons[#reasons + 1] = (" - Position from %s to %s"):format(vehicleData.position, newPos) end if (rotChange) then reasons[#reasons + 1] = (" - Rotation from %s to %s"):format(vehicleData.rotation, newRot) end if (lockChange) then reasons[#reasons + 1] = (" - Door lock state from %s to %s"):format(status[7], newLockStatus) end --if (attachmentChange) then reasons[#reasons + 1] = ("Attached from: \"%s\" to \"%s\""):format(vehicleData.attachedTo, attachedToEntity) end vehicleData.position = newPos vehicleData.rotation = newRot vehicleData.status[7] = newLockStatus --vehicleData.attachedTo = attachedToEntity vehicleData.lastUpdate = os_time() UpdateVehicleInDB(id, vehicleData, table_concat(reasons, "\n")) end end local function AddVehicleToFailedSpawns(id) if (not failedSpawnList[id]) then failedSpawnList[id] = 1 else failedSpawnList[id] += 1 end if (failedSpawnList[id] >= 5) then failedSpawnList[id] = nil LogWarning("There seems to be an issue spawning vehicle \"%s\" (\"%s\")", id, savedVehicles[id].tuning[1]) end end -- spawn a vehicle from its data function SpawnVehicle(id, vehicleData) if (vehicleData.model == nil) then return end LogDebug("Creating vehicle \"%s\" (\"%s\") at %s", id, vehicleData.tuning[1], vehicleData.position) vehicleData.spawnTime = os_nanotime() local vehicle = nil if (Ox and vehicleData.extraValues.oxId) then vehicle = Ox:SpawnVehicle(vehicleData.extraValues.oxId, vehicleData.position, vehicleData.rotation.z)?.entity if (not vehicle) then LogDebug("OxVehicle %s does not exist anymore. Deleting from data.", vehicleData.extraValues.oxId) DeleteVehiclesFromDB(id) savedVehicles[id] = nil spawnQueue[id] = nil return end else vehicle = CreateVehicleServerSetter(vehicleData.model, vehicleData.type, vehicleData.position.x, vehicleData.position.y, vehicleData.position.z, vehicleData.rotation.z) end local timer = GetGameTimer() if (not WaitUntilVehicleExists(vehicle, 5000)) then LogDebug("Vehicle didn't exist after spawning \"%s\" (\"%s\")", id, vehicleData.tuning[1]) vehicleData.spawnTime = nil AddVehicleToFailedSpawns(id) return end if (not WaitUntilVehicleHasPlateData(vehicle, 5000)) then LogDebug("No plate set while spawning \"%s\" (\"%s\")", id, vehicleData.tuning[1]) if (DoesEntityExist(vehicle)) then DeleteEntity(vehicle) end vehicleData.spawnTime = nil AddVehicleToFailedSpawns(id) return end if (vehicleData.bucket) then SetEntityRoutingBucket(vehicle, vehicleData.bucket) elseif (DEFAULT_BUCKET ~= 0) then SetEntityRoutingBucket(vehicle, DEFAULT_BUCKET) end LogDebug("Setting properties and state bags for vehicle \"%s\" (\"%s\")", id, vehicleData.tuning[1]) -- apply state bags local state = Entity(vehicle).state state.ap_id = id state.ap_data = { vehicleData.tuning, vehicleData.status, vehicleData.extraValues, vehicleData.position, vehicleData.rotation }--, vehicleData.attachedTo } state.ap_spawned = false for bagName, bagData in pairs(vehicleData.stateBags) do state:set(bagName, bagData, true) if (not stateBagList[bagName]) then stateBagList[bagName] = true end end vehicleData.handle = vehicle spawnQueue[id] = nil local plate = savedVehicles[id].tuning[1] local endTime = GetGameTimer() + SPAWN_TIMEOUT while (GetGameTimer() < endTime) do Wait(0) if (not savedVehicles[id]) then LogDebug("Data was deleted before vehicle \"%s\" (\"%s\") was fully created!", id, plate) break end if (not DoesEntityExist(vehicle)) then LogDebug("Vehicle \"%s\" (\"%s\") was removed during creation process!", id, plate) break end if (GetVehicleNumberPlateText(vehicle) == plate and savedVehicles[id].spawnTime) then LogDebug("Vehicle creation was successful for \"%s\" (\"%s\")! Took %.2fms", id, plate, (os_nanotime() - savedVehicles[id].spawnTime) * 0.000001) savedVehicles[id].spawnTime = nil Entity(vehicle).state.ap_data = nil TriggerEvent("AP:vehicleSpawned", savedVehicles[id].handle) return end end LogDebug("Failed setting properties for vehicle \"%s\" (\"%s\")", id, plate) AddVehicleToFailedSpawns(id) if (DoesEntityExist(vehicle)) then DeleteEntity(vehicle) end if (savedVehicles[id]) then savedVehicles[id].handle = nil spawnQueue[id] = true end end -- triggered from client side to either update or insert a vehicle RegisterNetEvent("AP:updateVehicle", function(networkId, model, tuning, status, extraValues, reason) local src = source if (not networkId or type(networkId) ~= "number") then LogDebug("Tried to save vehicle with invalid \"networkId\"!") return end if (not model or type(model) ~= "number") then LogDebug("Tried to save vehicle with invalid \"model\"!") return end if (not tuning or type(tuning) ~= "table") then LogDebug("Tried to save vehicle with invalid \"tuning\" data!") return end if (not tuning[1] or type(tuning[1]) ~= "string" or tuning[1]:len() ~= 8) then LogDebug("Tried to save vehicle with invalid \"plate\"!") return end if (not status or type(status) ~= "table") then LogDebug("Tried to save vehicle with invalid \"status\" data!") return end if (not extraValues or type(extraValues) ~= "table") then LogDebug("Tried to save vehicle with invalid \"extraValues\" data!") return end local vehicle = NetworkGetEntityFromNetworkId(networkId) if (not DoesEntityExist(vehicle)) then LogDebug("Tried to save entity that does not exist on server side (yet)!") return end if (saveOnlyOwnedVehicles and not reason:find("Resource") and not IsOwnedVehicle(tuning[1], vehicle)) then LogDebug("Tried to save unowned vehicle!") return end local bucket = GetEntityRoutingBucket(vehicle) if (not multiBucketSupport and bucket ~= DEFAULT_BUCKET) then LogDebug("Tried to save vehicle from non-default routing bucket %s!", bucket) return end local id = Entity(vehicle).state.ap_id if (id and savedVehicles[id]) then local oxId = savedVehicles[id].extraValues["oxId"] -- already exists savedVehicles[id].status = status savedVehicles[id].tuning = tuning savedVehicles[id].extraValues = extraValues savedVehicles[id].stateBags = GetVehicleStateBags(vehicle) savedVehicles[id].bucket = bucket savedVehicles[id].position = RoundVector3(GetEntityCoords(vehicle), 2) savedVehicles[id].rotation = RoundVector3(GetEntityRotation(vehicle), 2) savedVehicles[id].lastUpdate = os_time() savedVehicles[id].lastPlayer = GetPlayerIdentifierByType(src, "license") if (oxId) then savedVehicles[id].extraValues["oxId"] = oxId end UpdateVehicleInDB(id, savedVehicles[id], reason) else -- does not exist if (preventDuplicateVehicles) then local oldId = GetVehicleIdentifierUsingPlate(tuning[1]) if (oldId) then DeleteVehicleUsingIdentifier(oldId) end end -- enable persistence (for client spawned vehicles) SetEntityOrphanMode(vehicle, 2) id = GetNewVehicleIdentifier() Entity(vehicle).state.ap_id = id local playerIdentifier = GetPlayerIdentifierByType(src, "license") if (Ox) then extraValues["oxId"] = Ox:GetVehicle(vehicle).id end savedVehicles[id] = { handle = vehicle, model = model, type = GetVehicleType(vehicle), status = status, tuning = tuning, extraValues = extraValues, stateBags = GetVehicleStateBags(vehicle), bucket = bucket, position = RoundVector3(GetEntityCoords(vehicle), 2), rotation = RoundVector3(GetEntityRotation(vehicle), 2), lastUpdate = os_time(), initialPlayer = playerIdentifier, lastPlayer = playerIdentifier, spawning = false } InsertVehicleInDB(id, savedVehicles[id], reason) end end) -- insert vehicle into database function InsertVehicleInDB(id, vehicleData, reason) assert(id ~= nil and type(id) == "string", "Parameter \"id\" must be a string!") LogDebug("Inserting new vehicle \"%s\" (\"%s\") (Reason: %s)", id, vehicleData.tuning[1], reason) Storage.InsertVehicle({ id, vehicleData.model, vehicleData.type, json_encode(vehicleData.status), json_encode(vehicleData.tuning), json_encode(vehicleData.extraValues), json_encode(vehicleData.stateBags), vehicleData.bucket, vehicleData.position.x, vehicleData.position.y, vehicleData.position.z, vehicleData.rotation.x, vehicleData.rotation.y, vehicleData.rotation.z, vehicleData.lastUpdate, vehicleData.initialPlayer, vehicleData.lastPlayer }) end -- update vehicle in database function UpdateVehicleInDB(id, vehicleData, reason) assert(id ~= nil and type(id) == "string", "Parameter \"id\" must be a string!") LogDebug("Updating vehicle \"%s\" (\"%s\") (Reason: %s)", id, vehicleData.tuning[1], reason) Storage.UpdateVehicle({ json_encode(vehicleData.status), json_encode(vehicleData.tuning), json_encode(vehicleData.extraValues), json_encode(vehicleData.stateBags), vehicleData.bucket, --json_encode(vehicleData.attachedTo), vehicleData.position.x, vehicleData.position.y, vehicleData.position.z, vehicleData.rotation.x, vehicleData.rotation.y, vehicleData.rotation.z, vehicleData.lastUpdate, vehicleData.lastPlayer, id }) end -- delete vehicle(s) from database function DeleteVehiclesFromDB(...) local idList = {...} if (type(idList[1]) == "table") then idList = idList[1] end if (#idList == 0) then return end local str = json_encode(idList) str = str:sub(2, str:len() - 1) Storage.DeleteByIds(str) end -- delete vehicles that are still being spawned before actually stopping the resource AddEventHandler("onResourceStop", function(name) if (name ~= GetCurrentResourceName()) then return end for id, vehicleData in pairs(savedVehicles) do if (vehicleData.spawnTime and DoesEntityExist(vehicleData.handle)) then LogDebug("Deleted vehicle \"%s\" because it was still spawning", id) DeleteEntity(vehicleData.handle) end end end) local function DeleteVehicleUsingData(identifier, networkId, plate, keepInWorld, resourceName) if (identifier == nil and (networkId == nil or networkId == 0) and plate == nil) then LogWarning("Tried to delete vehicle without \"id\", \"netId\" and \"plate\"! (Resource: \"%s\")", resourceName) return false end if (identifier and DeleteVehicleUsingIdentifier(identifier, keepInWorld)) then LogDebug("Deleting vehicle (id \"%s\"; Resource: \"%s\")", identifier, resourceName) return true end if (networkId and DeleteVehicleUsingNetworkId(networkId, keepInWorld)) then LogDebug("Deleting vehicle (netId \"%s\"; Resource: \"%s\")", networkId, resourceName) return true end if (plate and DeleteVehicleUsingPlate(plate, keepInWorld)) then LogDebug("Deleting vehicle (plate \"%s\"; Resource: \"%s\")", plate, resourceName) return true end LogDebug("Deleting vehicle failed (id \"%s\", netId \"%s\", plate \"%s\"; Resource: \"%s\")", identifier, networkId, plate, resourceName) return false end exports("DeleteVehicleUsingData", function(identifier, networkId, plate, keepInWorld) return DeleteVehicleUsingData(identifier, networkId, plate, keepInWorld, GetInvokingResource()) end) local function DeleteVehicle(vehicle, keepInWorld, resourceName) if (not DoesEntityExist(vehicle)) then LogWarning("Tried to delete vehicle that does not exist! (Entity \"%s\"; Resource: \"%s\")", vehicle, resourceName) return false end return DeleteVehicleUsingData(Entity(vehicle).state.ap_id, NetworkGetNetworkIdFromEntity(vehicle), GetVehicleNumberPlateText(vehicle), keepInWorld, resourceName) end exports("DeleteVehicle", function(vehicle, keepInWorld) return DeleteVehicle(vehicle, keepInWorld, GetInvokingResource()) end) -- delete vehicle from client side using identifier, network id or plate RegisterNetEvent("AP:deleteVehicle", function(identifier, networkId, plate, keepInWorld, resourceName) DeleteVehicleUsingData(identifier, networkId, plate, keepInWorld, resourceName) end) -- delete vehicle using identifier function DeleteVehicleUsingIdentifier(id, keepInWorld) if (not savedVehicles[id]) then return false end if (not keepInWorld and savedVehicles[id].handle and DoesEntityExist(savedVehicles[id].handle)) then DeleteEntity(savedVehicles[id].handle) end local result, error = pcall(DeleteVehiclesFromDB, id) if (not result) then LogError("Error occured while calling \"DeleteVehiclesFromDB\" inside \"DeleteVehicleUsingIdentifier\"!") LogError("Full error: %s", error) end savedVehicles[id] = nil spawnQueue[id] = nil return true end -- delete vehicle using network id function DeleteVehicleUsingNetworkId(networkId, keepInWorld) local vehicle = NetworkGetEntityFromNetworkId(networkId) if (not DoesEntityExist(vehicle)) then return false end local id = Entity(vehicle)?.state?.ap_id if (id and savedVehicles[id]) then if (not keepInWorld) then DeleteEntity(vehicle) end DeleteVehiclesFromDB(id) savedVehicles[id] = nil spawnQueue[id] = nil return true end if (not keepInWorld) then DeleteEntity(vehicle) end return true end -- delete vehicle using plate function DeleteVehicleUsingPlate(plate, keepInWorld) for id, vehicleData in pairs(savedVehicles) do if (vehicleData.tuning[1] == plate or Trim(vehicleData.tuning[1]) == plate) then if (not keepInWorld and vehicleData.handle and DoesEntityExist(vehicleData.handle)) then DeleteEntity(vehicleData.handle) end DeleteVehiclesFromDB(id) savedVehicles[id] = nil spawnQueue[id] = nil return true end end if (not keepInWorld) then local vehicle = TryGetLoadedVehicleFromPlate(plate, GetAllVehicles()) if (vehicle and DoesEntityExist(vehicle)) then DeleteEntity(vehicle) return true end end return false end -- update a vehicles state bags in database function UpdateVehicleStateBagsInDB(id, stateBags) assert(id ~= nil and type(id) == "string", "Parameter \"id\" must be a string!") LogDebug("Updating state bags of vehicle \"%s\" (\"%s\") in database", id, savedVehicles[id].tuning[1]) Storage.UpdateStateBags({ json_encode(stateBags), id }) end function GetVehicleStateBags(vehicle) local stateBags = {} local vehicleStateBags = Entity(vehicle).state for bagName, _ in pairs(stateBagList) do if (vehicleStateBags[bagName]) then stateBags[bagName] = vehicleStateBags[bagName] end end return stateBags end -- state bag change handler for detecting changes on a vehicle AddStateBagChangeHandler(nil, nil, function(bagName, key, value, _unused, replicated) if (key:find("ap_")) then return end if (ignoreStateBags) then for i = 1, #ignoreStateBags do if (key:find(ignoreStateBags[i])) then return end end end local entity = GetEntityFromStateBagName(bagName) if (entity == 0 or GetEntityType(entity) ~= 2) then return end if (not stateBagList[key]) then stateBagList[key] = true end local id = Entity(entity).state.ap_id if (not id or not savedVehicles[id]) then return end if (IsAnyPlayerInsideVehicle(entity, GetAllPlayerPeds())) then return end if (savedVehicles[id].stateBags[key] == nil or not TableEquals(savedVehicles[id].stateBags[key], value)) then -- new state bag OR update savedVehicles[id].stateBags[key] = value if (preventStateBagAutoUpdate) then for i = 1, #preventStateBagAutoUpdate do if (key:find(preventStateBagAutoUpdate[i])) then return end end end UpdateVehicleStateBagsInDB(id, savedVehicles[id].stateBags) LogDebug(" Reason: Updating entity state bag \"%s\" for \"%s\". Value: %s", key, id, value) end end) -- forces a vehicle to update to the database function ForceVehicleUpdateInDB(id) if (not id or not savedVehicles[id]) then return end savedVehicles[id].lastUpdate = os_time() UpdateVehicleInDB(id, savedVehicles[id], "Resource forced: \"" .. GetInvokingResource() .. "\"") end exports("ForceVehicleUpdateInDB", ForceVehicleUpdateInDB) -- DEPRECATED: ensures a state bag is saved on the vehicle function EnsureStateBag() LogWarning("Executing the \"EnsureStateBag\" export is no longer necessary. Remove it from \"" .. GetInvokingResource() .. "\"!") return false end exports("EnsureStateBag", EnsureStateBag) -- returns a vehicle handle from a given state bag value function GetVehicleFromStateBagValue(key, value) for id, vehicleData in pairs(savedVehicles) do if (TableEquals(vehicleData.stateBags[key], value)) then return vehicleData.handle end end return nil end exports("GetVehicleFromStateBagValue", GetVehicleFromStateBagValue) -- returns all saved state bags from a vehicle function GetStateBagsFromVehicle(vehicle) for id, vehicleData in pairs(savedVehicles) do if (vehicleData.handle == vehicle) then return vehicleData.stateBags end end return nil end exports("GetStateBagsFromVehicle", GetStateBagsFromVehicle) -- returns all saved state bags from a vehicle with plate X function GetStateBagsFromPlate(plate) for id, vehicleData in pairs(savedVehicles) do if (vehicleData.tuning[1] == plate) then return vehicleData.stateBags end end return nil end exports("GetStateBagsFromPlate", GetStateBagsFromPlate) -- returns all of AP's vehicle data of a specific Vehicle function GetVehicleData(vehicle) if (not DoesEntityExist(vehicle)) then return nil end local id = Entity(vehicle).state.ap_id if (not id) then return nil end return savedVehicles[id] end exports("GetVehicleData", GetVehicleData) -- returns all of AP's saved vehicle data of a specific Vehicle function GetVehicleTuningFromData(vehicle) if (not DoesEntityExist(vehicle)) then return nil end local id = Entity(vehicle).state.ap_id if (not id) then return nil end return savedVehicles[id]?.tuning end exports("GetVehicleTuningFromData", GetVehicleTuningFromData) -- getting a vehicle position using its plate local function GetVehiclePosition(plate, resourceName) if (plate == nil or type(plate) ~= "string") then LogError("Parameter \"plate\" must be a string! (Export: \"GetVehiclePosition\"; Resource: \"%s\")", resourceName) return end LogDebug("Position request for \"%s\" (Resource: \"%s\")", plate, resourceName) plate = plate:upper() for id, vehicleData in pairs(savedVehicles) do if (vehicleData.tuning and (plate == vehicleData.tuning[1] or plate == Trim(vehicleData.tuning[1]))) then return vehicleData.handle and DoesEntityExist(vehicleData.handle) and GetEntityCoords(vehicleData.handle) or vehicleData.position end end local vehicles = GetAllVehicles() for i = 1, #vehicles do if (DoesEntityExist(vehicles[i])) then local vehPlate = GetVehicleNumberPlateText(vehicles[i]) if (plate == vehPlate or plate == Trim(vehPlate)) then return GetEntityCoords(vehicles[i]) end end end return nil end exports("GetVehiclePosition", function(plate) return GetVehiclePosition(plate, GetInvokingResource()) end) -- getting vehicle positions using more than one plate local function GetVehiclePositions(plates, resourceName) if (plates == nil or type(plates) ~= "table") then LogError("Parameter \"plates\" must be a table! (Export: \"GetVehiclePositions\"; Resource: \"%s\")", resourceName) return {} end for i = 1, #plates do if (plates[i] == nil or type(plates[i]) ~= "string") then LogError("Parameter \"plate\" (at index %s) must be a string! (Export: \"GetVehiclePositions\"; Resource: \"%s\")", i, resourceName) return {} end plates[i] = plates[i]:upper() end LogDebug("Position request for \"%s\" (Resource: \"%s\")", table_concat(plates, "\", \""), resourceName) local platePositions = {} -- check all loaded vehicles first local vehicles = GetAllVehicles() for i = 1, #vehicles do if (DoesEntityExist(vehicles[i])) then local vehPlate = GetVehicleNumberPlateText(vehicles[i]) local trimmedVehPlate = Trim(vehPlate) for j = 1, #plates do if (plates[j] == vehPlate or plates[j] == trimmedVehPlate) then platePositions[ plates[j] ] = GetEntityCoords(vehicles[i]) break end end end end -- then search missing vehicles in APs saved vehicles for i = 1, #plates do if (platePositions[ plates[i] ] == nil) then for id, vehicleData in pairs(savedVehicles) do local trimmedVehPlate = Trim(vehicleData.tuning[1]) if (vehicleData.tuning and (plates[i] == vehicleData.tuning[1] or plates[i] == trimmedVehPlate)) then platePositions[ plates[i] ] = vehicleData.position break end end end end return platePositions end exports("GetVehiclePositions", function(plates) return GetVehiclePositions(plates, GetInvokingResource()) end) -- callbacks for client side getting of vehicle position(s) local CB = exports["kimi_callbacks"] CB:Register("AP:getVehiclePosition", function(source, plate, resourceName) return GetVehiclePosition(plate, resourceName) end) CB:Register("AP:getVehiclePositions", function(source, plates, resourceName) return GetVehiclePositions(plates, resourceName) end) -- command to delete ALL vehicles from the database table. Needs to be executed twice for security reason. local deleteSavedVehicles = false RegisterCommand("deleteSavedVehicles", function(source, args, raw) if (deleteSavedVehicles) then Storage.DeleteAllVehicles() savedVehicles = {} spawnQueue = {} Log("Deleted all vehicles from the vehicle_parking table.") else Log("Are you sure that you want to delete all vehicles from the parking list?\nIf yes, execute the command a second time!") end deleteSavedVehicles = not deleteSavedVehicles end, true) -- command to delete ALL vehicles from the database table. Needs to be executed twice for security reason. local deleteAndStore = false RegisterCommand("deleteandstore", function(source, args, raw) if (deleteAndStore) then local ids = {} local playerPeds = GetAllPlayerPeds() for id, vehicleData in pairs(savedVehicles) do if (vehicleData.handle and DoesEntityExist(vehicleData.handle)) then if (IsAnyPlayerInsideVehicle(vehicleData.handle, playerPeds)) then goto skipVeh end DeleteEntity(vehicleData.handle) end StoreVehicle(vehicleData.tuning[1], vehicleData.handle) savedVehicles[id] = nil spawnQueue[id] = nil ids[#ids + 1] = id ::skipVeh:: end DeleteVehiclesFromDB(ids) Log("Deleted all vehicles from the vehicle_parking table.") else Log("Are you sure that you want to delete all vehicles from the parking list and store them?\nIf yes, execute the command a second time!") end deleteAndStore = not deleteAndStore end, true) RegisterCommand("apdv", function(src, args, raw) local id = args[1] if (not id) then LogError("First argument needs to be a plate or identifier!") return end local success = false -- interpret first param as id or plate if (id:len() == 16) then success = DeleteVehicleUsingData(id, nil, nil, false, "Command \"apdv\"") else success = DeleteVehicleUsingData(nil, nil, id, false, "Command \"apdv\"") end if (success) then Log("Vehicle \"%s\" deleted through command \"apdv\".", id) else Log("Vehicle \"%s\" could not be deleted through command \"apdv\".", id) end end, true) RegisterCommand("apbring", function(playerId, args, raw) if (playerId == 0) then LogError("Command \"apbring\" can only be executed from client side!") return end local id = args[1] if (not id) then LogError("Command \"apbring\": First argument needs to be a plate or identifier!") return end -- check first arg if it is a plate if (id:len() == 8) then id = GetVehicleIdentifierUsingPlate(id) end if (not id or not savedVehicles[id]) then LogError("Command \"apbring\": Vehicle could not be found!") return end local ped = GetPlayerPed(playerId) if (not DoesEntityExist(ped)) then LogError("Command \"apbring\": Could not find ped to spawn vehicle at!") return end if (savedVehicles[id].handle and DoesEntityExist(savedVehicles[id].handle)) then DeleteEntity(savedVehicles[id].handle) Wait(100) end savedVehicles[id].handle = nil savedVehicles[id].position = GetEntityCoords(ped) + vector3(2.0, 3.0, 0.0) savedVehicles[id].rotation = vector3(0.0, 0.0, 0.0) Log("Vehicle \"%s\" teleported through command \"apbring\".", id) end, true) function UpdatePlate(networkId, newPlate, oldPlate) if (networkId == nil) then LogError("\"networkId\" was nil while trying to update a plate!") return end if (newPlate == nil or newPlate:len() > 8) then LogError("\"newPlate\" was nil or too long while trying to update a plate!") return end -- format plates newPlate = Trim(newPlate:upper()) if (oldPlate) then oldPlate = Trim(oldPlate:upper()) end -- change plate on vehicle local vehicle = NetworkGetEntityFromNetworkId(networkId) if (DoesEntityExist(vehicle)) then SetVehicleNumberPlateText(vehicle, newPlate) local found = false while (not found) do Wait(0) found = Trim(GetVehicleNumberPlateText(vehicle)) == newPlate end newPlate = GetVehicleNumberPlateText(vehicle) end -- search for plate for id, vehicleData in pairs(savedVehicles) do if (vehicle == vehicleData.handle) then local old = vehicleData.tuning[1] vehicleData.tuning[1] = newPlate UpdateVehicleInDB(id, vehicleData, "\"UpdatePlate\" export") return end end -- search for plate by using oldPlate if (oldPlate) then newPlate = FillPlateWithSpaces(newPlate) for id, vehicleData in pairs(vehicles) do if (Trim(vehicleData.tuning[1]) == oldPlate) then vehicleData.tuning[1] = newPlate UpdateVehicleInDB(id, vehicleData, "\"UpdatePlate\" export") return end end end LogDebug("No vehicle found to change plate to \"%s\"", newPlate) end exports("UpdatePlate", UpdatePlate) RegisterNetEvent("AP:updatePlate", function(networkId, newPlate) UpdatePlate(networkId, newPlate) end) RegisterNetEvent("AP:enteredTrailer", function(trailerNetId) local trailer = NetworkGetEntityFromNetworkId(trailerNetId) if (not DoesEntityExist(trailer)) then return end Entity(trailer).state:set("ap_trailer", true, true) end) RegisterNetEvent("AP:leftTrailer", function(trailerNetId) local trailer = NetworkGetEntityFromNetworkId(trailerNetId) if (not DoesEntityExist(trailer)) then return end Entity(trailer).state:set("ap_trailer", nil, true) end) -- export and event for force freezing vehicles if (forceUnfreezeVehicles) then local function FreezeVehicle(vehicle, freeze) local id = Entity(vehicle)?.state?.ap_id if (id and savedVehicles[id]) then savedVehicles[id].isFrozen = freeze end FreezeEntityPosition(vehicle, freeze) end exports("FreezeVehicle", FreezeVehicle) RegisterNetEvent("AP:freezeVehicle", function(networkId, freeze) local vehicle = NetworkGetEntityFromNetworkId(networkId) if (not DoesEntityExist(vehicle)) then return end FreezeVehicle(vehicle, freeze) end) end AddEventHandler("onEntityBucketChange", function(entity, newBucket, oldBucket) if (GetEntityType(entity) ~= 2) then return end local id = Entity(entity).state.ap_id if (not id or not savedVehicles[id]) then return end if (savedVehicles[id].bucket == newBucket) then return end savedVehicles[id].bucket = newBucket LogDebug("Updating vehicle \"%s\" (\"%s\") (Reason: %s)", id, savedVehicles[id].tuning[1], ("Routing bucket from %s to %s"):format(oldBucket, newBucket)) Storage.UpdateBucket(newBucket, id) end) RegisterNetEvent("AP:onVehicleDamage", function(networkId, status) local vehicle = NetworkGetEntityFromNetworkId(networkId) if (not DoesEntityExist(vehicle)) then return end local id = Entity(vehicle).state.ap_id if (not id or not savedVehicles[id]) then return end savedVehicles[id].status = status LogDebug("Updating status for vehicle \"%s\" (\"%s\") (Reason: %s)", id, savedVehicles[id].tuning[1], "Vehicle damage") Storage.UpdateStatus(json_encode(status), id) end)