-- localise frequently used Lua globals local os_time, os_difftime, os_date, math_floor = os.time, os.difftime, os.date, math.floor -- localise frequently used natives local DoesEntityExist, GetPlayerRoutingBucket, GetEntityCoords, DeleteEntity, GetVehicleEngineHealth, GetEntityRoutingBucket = DoesEntityExist, GetPlayerRoutingBucket, GetEntityCoords, DeleteEntity, GetVehicleEngineHealth, GetEntityRoutingBucket -- task system local tasks = {} local function GetTime() local time = os_time() return { day = tonumber(os_date("%w", time)), hour = tonumber(os_date("%H", time)), minute = tonumber(os_date("%M", time)) } end local taskRunning = false local function StartTaskThread() if (taskRunning) then return end taskRunning = true local lastTime = GetTime() while (taskRunning) do Wait(1000) local time = GetTime() if (time.minute ~= lastTime.minute or time.hour ~= lastTime.hour or time.day ~= lastTime.day) then for i = 1, #tasks do if ((not tasks[i].day or tasks[i].day == time.day) and tasks[i].hour == time.hour and tasks[i].minute == time.minute) then tasks[i].Run() end end lastTime = time end end end local function AddTask(task, d, h, m) assert(task and type(task) == "function", "Parameter \"task\" must be a function!") assert(not d or type(d) == "number", "Parameter \"day\" must be a number!") assert(h and type(h) == "number", "Parameter \"hour\" must be a number!") assert(m and type(m) == "number", "Parameter \"minute\" must be a number!") tasks[#tasks + 1] = { day = d and math_floor(d), hour = math_floor(h), minute = math_floor(m), Run = task } if (not taskRunning) then CreateThread(StartTaskThread) end end -- get all players sorted by bucket local function GetPositionsOfAllPlayersByBucket() local playerPositions = {} local players = GetPlayers() for i = 1, #players do local ped = GetPlayerPed(players[i]) if (DoesEntityExist(ped)) then local bucket = GetPlayerRoutingBucket(players[i]) if (not playerPositions[bucket]) then playerPositions[bucket] = {} end playerPositions[bucket][#playerPositions[bucket] + 1] = GetEntityCoords(ped) end end return playerPositions end -- get closest position and distance from list local function GetClosestDistanceFromList(position, positionList) local closestDistance = 100000 for i = 1, positionList and #positionList or 0 do local tempDist = #(position - positionList[i]) if (tempDist < closestDistance) then closestDistance = tempDist end end return closestDistance end -- runs the whole cleanup process once function CleanupProcess() local timeDiff = 0 if (Cleanup.timeThreshold) then local currentTime = os_time() local threshold = math_floor(3600 * Cleanup.timeThreshold) timeDiff = os_difftime(currentTime, threshold) end local playerPositions = GetPositionsOfAllPlayersByBucket() local toDelete = {} for id, vehicleData in pairs(savedVehicles) do local position = DoesEntityExist(vehicleData.handle) and GetEntityCoords(vehicleData.handle) or vehicleData.position for i = 1, #Cleanup.ignoredZones do if (#(position - Cleanup.ignoredZones[i].position) < Cleanup.ignoredZones[i].radius) then goto cleanupDone -- continue end end for i = 1, #Cleanup.ignoredPlates do if (vehicleData.tuning[1]:find(Cleanup.ignoredPlates[i]:upper())) then goto cleanupDone -- continue end end for i = 1, #Cleanup.ignoredModels do if (vehicleData.model == Cleanup.ignoredModels[i]) then goto cleanupDone -- continue end end if (Cleanup.timeThreshold and vehicleData.lastUpdate < timeDiff) then toDelete[#toDelete + 1] = id TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "time") goto cleanupDone -- continue end if (Cleanup.engineThreshold and vehicleData.status[3] <= Cleanup.engineThreshold) then toDelete[#toDelete + 1] = id TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "engineHealth") goto cleanupDone -- continue end if (Cleanup.distanceThreshold) then local distance = GetClosestDistanceFromList(position, playerPositions[vehicleData.bucket]) if (distance > Cleanup.distanceThreshold) then toDelete[#toDelete + 1] = id TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "distance") goto cleanupDone -- continue end end for i = 1, #Cleanup.zones do if (#(position - Cleanup.zones[i].position) < Cleanup.zones[i].radius) then toDelete[#toDelete + 1] = id TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "zone_" .. i) goto cleanupDone -- continue end end ::cleanupDone:: end for i, id in ipairs(toDelete) do if (savedVehicles[id].handle and DoesEntityExist(savedVehicles[id].handle)) then DeleteEntity(savedVehicles[id].handle) LogDebug("Cleanup removed \"%s\" (\"%s\").", id, savedVehicles[id].tuning[1]) end if (Cleanup.storeVehicles) then StoreVehicle(savedVehicles[id].tuning[1], savedVehicles[id].handle) end savedVehicles[id] = nil spawnQueue[id] = nil end DeleteVehiclesFromDB(toDelete) local othersCount = 0 if (Cleanup.allVehicles) then local vehicles = GetAllVehicles() for i = 1, #vehicles do if (not DoesEntityExist(vehicles[i])) then goto cleanupOthersDone -- continue end local position = GetEntityCoords(vehicles[i]) for i = 1, #Cleanup.ignoredZones do if (#(position - Cleanup.ignoredZones[i].position) < Cleanup.ignoredZones[i].radius) then goto cleanupOthersDone -- continue end end if (#Cleanup.ignoredPlates > 0) then local plate = GetVehicleNumberPlateText(vehicles[i]) for i = 1, #Cleanup.ignoredPlates do if (plate:find(Cleanup.ignoredPlates[i]:upper())) then goto cleanupOthersDone -- continue end end end if (#Cleanup.ignoredModels > 0) then local model = GetEntityModel(vehicles[i]) for i = 1, #Cleanup.ignoredModels do if (model == Cleanup.ignoredModels[i]) then goto cleanupOthersDone -- continue end end end if (Cleanup.engineThreshold and GetVehicleEngineHealth(vehicles[i]) <= Cleanup.engineThreshold) then DeleteEntity(vehicles[i]) othersCount += 1 goto cleanupOthersDone -- continue end if (Cleanup.distanceThreshold) then local distance = GetClosestDistanceFromList(position, playerPositions[GetEntityRoutingBucket(vehicles[i])]) if (distance > Cleanup.distanceThreshold) then DeleteEntity(vehicles[i]) othersCount += 1 goto cleanupOthersDone -- continue end end for i = 1, #Cleanup.zones do if (#(position - Cleanup.zones[i].position) < Cleanup.zones[i].radius) then DeleteEntity(vehicles[i]) othersCount += 1 goto cleanupOthersDone -- continue end end ::cleanupOthersDone:: end end TriggerClientEvent("AP:showNotification", -1, Cleanup.deleteNotification) Log("Cleanup complete. Removed %s saved vehicles. Removed %s other vehicles.", #toDelete, othersCount) end -- add timed clean up tasks for i = 1, #Cleanup.times do local day = Cleanup.times[i].day local hour = Cleanup.times[i].hour local minute = Cleanup.times[i].minute AddTask(CleanupProcess, day, hour, minute) for j = 1, #Cleanup.notificationTimes do local d = day local h = hour local m = minute - Cleanup.notificationTimes[j] if (m < 0) then m += 60 h -= 1 if (h < 0) then h += 24 if (d) then d -= 1 if (d < 0) then d += 7 end end end end AddTask(function() TriggerClientEvent("AP:showNotification", -1, Cleanup.timeLeftNotification:format(Cleanup.notificationTimes[j])) end, d, h, m) end end