forked from Simnation/Main
1179 lines
35 KiB
Lua
1179 lines
35 KiB
Lua
|
|
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)
|