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)
 | 
