forked from Simnation/Main
1039 lines
37 KiB
Lua
1039 lines
37 KiB
Lua
local QBCore = exports['qb-core']:GetCoreObject()
|
|
local trackedVehicles = {}
|
|
local lastKnownCoords = {}
|
|
local garagePending = {} -- Fahrzeuge, die gerade in die Garage gestellt werden
|
|
local spawnedVehicles = {} -- Track recently spawned vehicles to prevent duplication
|
|
local vehicleOwnership = {} -- Cache vehicle ownership status
|
|
local vehicleExistsCallbacks = {} -- Callbacks for vehicle existence checks
|
|
|
|
-- Helper function to count table entries
|
|
function tableLength(T)
|
|
local count = 0
|
|
for _ in pairs(T) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
-- Debug Funktion
|
|
local function Debug(msg)
|
|
if Config.Debug then
|
|
print("[AntiDespawn] " .. msg)
|
|
end
|
|
end
|
|
|
|
-- Function to check if a vehicle is a temporary/special vehicle (like police cars, ambulances, etc.)
|
|
local function IsTemporaryVehicle(vehicle)
|
|
local plate = QBCore.Functions.GetPlate(vehicle)
|
|
|
|
-- Check for common temporary vehicle patterns
|
|
if not plate or plate == "" then return true end
|
|
|
|
-- Some servers use specific patterns for temporary vehicles
|
|
if string.match(plate, "^TEMP%d%d%d%d%d$") then return true end
|
|
if string.match(plate, "^[A-Z][A-Z][A-Z][A-Z]%d%d%d$") and GetEntityModel(vehicle) == GetHashKey("police") then return true end
|
|
|
|
-- Add more patterns as needed for your server
|
|
|
|
return false
|
|
end
|
|
|
|
-- Function to check if a vehicle might be a job vehicle
|
|
local function IsLikelyJobVehicle(vehicle)
|
|
local model = GetEntityModel(vehicle)
|
|
|
|
-- List of common job vehicle models
|
|
local jobVehicles = {
|
|
"police", "police2", "police3", "police4",
|
|
"ambulance", "firetruk",
|
|
"taxi",
|
|
"flatbed", "towtruck", "towtruck2",
|
|
-- Add more as needed
|
|
}
|
|
|
|
for _, jobVehicle in ipairs(jobVehicles) do
|
|
if model == GetHashKey(jobVehicle) then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Anti-Duplication: Check if a vehicle with this plate already exists in the world
|
|
local function DoesVehicleExistInWorld(plate)
|
|
local vehicles = GetGamePool('CVehicle')
|
|
local count = 0
|
|
|
|
for _, vehicle in pairs(vehicles) do
|
|
local vehPlate = QBCore.Functions.GetPlate(vehicle)
|
|
if vehPlate == plate then
|
|
count = count + 1
|
|
if count > 1 then
|
|
-- More than one vehicle with this plate exists!
|
|
Debug("DUPLICATION DETECTED: Multiple vehicles with plate " .. plate .. " exist!")
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Anti-Duplication: Check if vehicle was recently spawned
|
|
local function WasVehicleRecentlySpawned(plate)
|
|
if spawnedVehicles[plate] then
|
|
local timeSinceSpawn = GetGameTimer() - spawnedVehicles[plate]
|
|
if timeSinceSpawn < 60000 then -- 60 seconds cooldown
|
|
Debug("Anti-Dupe: Vehicle " .. plate .. " was recently spawned (" .. math.floor(timeSinceSpawn/1000) .. " seconds ago)")
|
|
return true
|
|
else
|
|
-- Reset the timer if it's been more than 60 seconds
|
|
spawnedVehicles[plate] = nil
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-- Anti-Duplication: Mark vehicle as recently spawned
|
|
local function MarkVehicleAsSpawned(plate)
|
|
spawnedVehicles[plate] = GetGameTimer()
|
|
Debug("Anti-Dupe: Marked vehicle " .. plate .. " as recently spawned")
|
|
|
|
-- Clean up old entries every 5 minutes
|
|
SetTimeout(300000, function()
|
|
for p, time in pairs(spawnedVehicles) do
|
|
if GetGameTimer() - time > 300000 then -- 5 minutes
|
|
spawnedVehicles[p] = nil
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Function to check if player owns the vehicle with caching
|
|
local function DoesPlayerOwnVehicle(plate)
|
|
-- Check cache first
|
|
if vehicleOwnership[plate] ~= nil then
|
|
-- Cache expires after 5 minutes
|
|
local timeSinceCheck = GetGameTimer() - vehicleOwnership[plate].timestamp
|
|
if timeSinceCheck < 300000 then -- 5 minutes
|
|
return vehicleOwnership[plate].owned
|
|
end
|
|
end
|
|
|
|
local playerData = QBCore.Functions.GetPlayerData()
|
|
if not playerData then return false end
|
|
|
|
-- Trigger server event to check ownership and wait for response
|
|
local isOwned = nil
|
|
|
|
-- Request ownership check from server
|
|
TriggerServerEvent('antidespawn:server:checkVehicleOwnership', plate)
|
|
|
|
-- Register one-time event handler for the response
|
|
local eventHandler = AddEventHandler('antidespawn:client:vehicleOwnershipResult', function(result, checkPlate)
|
|
if plate == checkPlate then
|
|
isOwned = result
|
|
-- Cache the result
|
|
vehicleOwnership[plate] = {
|
|
owned = result,
|
|
timestamp = GetGameTimer()
|
|
}
|
|
end
|
|
end)
|
|
|
|
-- Wait for response with timeout
|
|
local timeout = 0
|
|
while isOwned == nil and timeout < 50 do
|
|
Wait(10)
|
|
timeout = timeout + 1
|
|
end
|
|
|
|
-- Remove event handler
|
|
RemoveEventHandler(eventHandler)
|
|
|
|
return isOwned == true
|
|
end
|
|
|
|
-- Function to check if a vehicle exists in the database
|
|
local function CheckVehicleExists(plate, callback)
|
|
vehicleExistsCallbacks[plate] = callback
|
|
TriggerServerEvent('antidespawn:server:checkVehicleExists', plate)
|
|
|
|
-- Set a timeout to clean up the callback if no response is received
|
|
SetTimeout(5000, function()
|
|
if vehicleExistsCallbacks[plate] then
|
|
vehicleExistsCallbacks[plate](false) -- Assume it doesn't exist if no response
|
|
vehicleExistsCallbacks[plate] = nil
|
|
end
|
|
end)
|
|
end
|
|
|
|
-- Register the event handler for vehicle existence check
|
|
RegisterNetEvent('antidespawn:client:vehicleExistsResult', function(exists, plate)
|
|
if vehicleExistsCallbacks[plate] then
|
|
vehicleExistsCallbacks[plate](exists)
|
|
vehicleExistsCallbacks[plate] = nil
|
|
end
|
|
end)
|
|
|
|
-- Funktion um zu prüfen ob Fahrzeugklasse erlaubt ist
|
|
local function IsVehicleClassAllowed(vehicle)
|
|
local vehicleClass = GetVehicleClass(vehicle)
|
|
|
|
-- Prüfe Blacklist
|
|
for _, blacklistedClass in pairs(Config.BlacklistedVehicleClasses) do
|
|
if vehicleClass == blacklistedClass then
|
|
return false
|
|
end
|
|
end
|
|
|
|
-- Prüfe Whitelist
|
|
for _, allowedClass in pairs(Config.AllowedVehicleClasses) do
|
|
if vehicleClass == allowedClass then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
-- Extrem starke Anti-Despawn Funktion
|
|
local function PreventDespawn(vehicle)
|
|
if not DoesEntityExist(vehicle) then return false end
|
|
|
|
-- Grundlegende Persistenz
|
|
SetEntityAsMissionEntity(vehicle, true, true)
|
|
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
|
|
SetVehicleNeedsToBeHotwired(vehicle, false)
|
|
|
|
-- Zusätzliche Flags
|
|
SetEntityLoadCollisionFlag(vehicle, true)
|
|
SetVehicleIsStolen(vehicle, false)
|
|
SetVehicleIsWanted(vehicle, false)
|
|
|
|
-- Verhindere dass das Fahrzeug als "abandoned" markiert wird
|
|
if DecorIsRegisteredAsType("IgnoredByQuickSave", 2) then
|
|
DecorSetBool(vehicle, "IgnoredByQuickSave", false)
|
|
end
|
|
|
|
-- Setze Fahrzeug auf Boden
|
|
SetVehicleOnGroundProperly(vehicle)
|
|
|
|
-- Verhindere dass das Fahrzeug gelöscht wird
|
|
NetworkRegisterEntityAsNetworked(vehicle)
|
|
local netID = NetworkGetNetworkIdFromEntity(vehicle)
|
|
SetNetworkIdExistsOnAllMachines(netID, true)
|
|
SetNetworkIdCanMigrate(netID, true)
|
|
|
|
return true
|
|
end
|
|
|
|
-- Funktion um Fahrzeugmods zu erhalten
|
|
local function GetVehicleMods(vehicle)
|
|
local mods = {}
|
|
|
|
-- Basis Mods
|
|
for i = 0, 49 do
|
|
mods[tostring(i)] = GetVehicleMod(vehicle, i)
|
|
end
|
|
|
|
-- Extras
|
|
mods.extras = {}
|
|
for i = 1, 12 do
|
|
if DoesExtraExist(vehicle, i) then
|
|
mods.extras[tostring(i)] = IsVehicleExtraTurnedOn(vehicle, i)
|
|
end
|
|
end
|
|
|
|
-- Farben
|
|
local primaryColor, secondaryColor = GetVehicleColours(vehicle)
|
|
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
|
|
|
|
mods.colors = {
|
|
primary = primaryColor,
|
|
secondary = secondaryColor,
|
|
pearlescent = pearlescentColor,
|
|
wheels = wheelColor
|
|
}
|
|
|
|
-- Custom Farben
|
|
local hasCustomPrimaryColor = GetIsVehiclePrimaryColourCustom(vehicle)
|
|
if hasCustomPrimaryColor then
|
|
local r, g, b = GetVehicleCustomPrimaryColour(vehicle)
|
|
mods.customPrimaryColor = {r = r, g = g, b = b}
|
|
end
|
|
|
|
local hasCustomSecondaryColor = GetIsVehicleSecondaryColourCustom(vehicle)
|
|
if hasCustomSecondaryColor then
|
|
local r, g, b = GetVehicleCustomSecondaryColour(vehicle)
|
|
mods.customSecondaryColor = {r = r, g = g, b = b}
|
|
end
|
|
|
|
-- Neon
|
|
mods.neon = {
|
|
left = IsVehicleNeonLightEnabled(vehicle, 0),
|
|
right = IsVehicleNeonLightEnabled(vehicle, 1),
|
|
front = IsVehicleNeonLightEnabled(vehicle, 2),
|
|
back = IsVehicleNeonLightEnabled(vehicle, 3)
|
|
}
|
|
|
|
local r, g, b = GetVehicleNeonLightsColour(vehicle)
|
|
mods.neonColor = {r = r, g = g, b = b}
|
|
|
|
-- Xenon
|
|
mods.xenonColor = GetVehicleXenonLightsColour(vehicle)
|
|
mods.xenonEnabled = IsToggleModOn(vehicle, 22)
|
|
|
|
-- Livery
|
|
mods.livery = GetVehicleLivery(vehicle)
|
|
|
|
-- Fenster Tint
|
|
mods.windowTint = GetVehicleWindowTint(vehicle)
|
|
|
|
-- Rad Typ
|
|
mods.wheelType = GetVehicleWheelType(vehicle)
|
|
|
|
-- Rauch Farbe
|
|
local r, g, b = GetVehicleTyreSmokeColor(vehicle)
|
|
mods.tyreSmokeColor = {r = r, g = g, b = b}
|
|
|
|
-- Dashboard & Interior Farbe
|
|
mods.dashboardColor = GetVehicleDashboardColour(vehicle)
|
|
mods.interiorColor = GetVehicleInteriorColour(vehicle)
|
|
|
|
-- Toggles
|
|
mods.bulletProofTires = not GetVehicleTyresCanBurst(vehicle)
|
|
mods.turbo = IsToggleModOn(vehicle, 18)
|
|
mods.xeonHeadlights = IsToggleModOn(vehicle, 22)
|
|
|
|
return mods
|
|
end
|
|
|
|
-- Funktion um Fahrzeugmods zu setzen
|
|
local function SetVehicleMods(vehicle, mods)
|
|
if not mods then return end
|
|
|
|
-- Setze Modkit
|
|
SetVehicleModKit(vehicle, 0)
|
|
|
|
-- Rad Typ zuerst setzen
|
|
if mods.wheelType then
|
|
SetVehicleWheelType(vehicle, mods.wheelType)
|
|
end
|
|
|
|
-- Basis Mods
|
|
for i = 0, 49 do
|
|
if mods[tostring(i)] ~= nil then
|
|
SetVehicleMod(vehicle, i, mods[tostring(i)], false)
|
|
end
|
|
end
|
|
|
|
-- Extras
|
|
if mods.extras then
|
|
for i = 1, 12 do
|
|
if mods.extras[tostring(i)] ~= nil then
|
|
SetVehicleExtra(vehicle, i, not mods.extras[tostring(i)])
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Farben
|
|
if mods.colors then
|
|
SetVehicleColours(vehicle, mods.colors.primary or 0, mods.colors.secondary or 0)
|
|
SetVehicleExtraColours(vehicle, mods.colors.pearlescent or 0, mods.colors.wheels or 0)
|
|
end
|
|
|
|
-- Custom Farben
|
|
if mods.customPrimaryColor then
|
|
SetVehicleCustomPrimaryColour(vehicle, mods.customPrimaryColor.r, mods.customPrimaryColor.g, mods.customPrimaryColor.b)
|
|
end
|
|
|
|
if mods.customSecondaryColor then
|
|
SetVehicleCustomSecondaryColour(vehicle, mods.customSecondaryColor.r, mods.customSecondaryColor.g, mods.customSecondaryColor.b)
|
|
end
|
|
|
|
-- Neon
|
|
if mods.neon then
|
|
SetVehicleNeonLightEnabled(vehicle, 0, mods.neon.left or false)
|
|
SetVehicleNeonLightEnabled(vehicle, 1, mods.neon.right or false)
|
|
SetVehicleNeonLightEnabled(vehicle, 2, mods.neon.front or false)
|
|
SetVehicleNeonLightEnabled(vehicle, 3, mods.neon.back or false)
|
|
end
|
|
|
|
if mods.neonColor then
|
|
SetVehicleNeonLightsColour(vehicle, mods.neonColor.r, mods.neonColor.g, mods.neonColor.b)
|
|
end
|
|
|
|
-- Xenon
|
|
if mods.xenonEnabled then
|
|
ToggleVehicleMod(vehicle, 22, true)
|
|
if mods.xenonColor then
|
|
SetVehicleXenonLightsColour(vehicle, mods.xenonColor)
|
|
end
|
|
end
|
|
|
|
-- Livery
|
|
if mods.livery then
|
|
SetVehicleLivery(vehicle, mods.livery)
|
|
end
|
|
|
|
-- Fenster Tint
|
|
if mods.windowTint then
|
|
SetVehicleWindowTint(vehicle, mods.windowTint)
|
|
end
|
|
|
|
-- Rauch Farbe
|
|
if mods.tyreSmokeColor then
|
|
ToggleVehicleMod(vehicle, 20, true) -- Aktiviere Rauch
|
|
SetVehicleTyreSmokeColor(vehicle, mods.tyreSmokeColor.r, mods.tyreSmokeColor.g, mods.tyreSmokeColor.b)
|
|
end
|
|
|
|
-- Dashboard & Interior Farbe
|
|
if mods.dashboardColor then
|
|
SetVehicleDashboardColour(vehicle, mods.dashboardColor)
|
|
end
|
|
|
|
if mods.interiorColor then
|
|
SetVehicleInteriorColour(vehicle, mods.interiorColor)
|
|
end
|
|
|
|
-- Toggles
|
|
if mods.bulletProofTires ~= nil then
|
|
SetVehicleTyresCanBurst(vehicle, not mods.bulletProofTires)
|
|
end
|
|
|
|
if mods.turbo ~= nil then
|
|
ToggleVehicleMod(vehicle, 18, mods.turbo)
|
|
end
|
|
|
|
-- Setze Felgen nochmal explizit
|
|
if mods["23"] ~= nil then -- Vorderräder
|
|
SetVehicleMod(vehicle, 23, mods["23"], false)
|
|
end
|
|
|
|
if mods["24"] ~= nil then -- Hinterräder
|
|
SetVehicleMod(vehicle, 24, mods["24"], false)
|
|
end
|
|
end
|
|
|
|
-- Hilfsfunktion um Fahrzeug anhand Kennzeichen zu finden
|
|
function GetVehicleByPlate(plate)
|
|
local vehicles = GetGamePool('CVehicle')
|
|
for _, vehicle in pairs(vehicles) do
|
|
if QBCore.Functions.GetPlate(vehicle) == plate then
|
|
return vehicle
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- Event Handler für Fahrzeug betreten (nur Fahrersitz)
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(1000)
|
|
|
|
local playerPed = PlayerPedId()
|
|
local currentVehicle = GetVehiclePedIsIn(playerPed, false)
|
|
|
|
-- Spieler ist als Fahrer in ein Fahrzeug eingestiegen
|
|
if currentVehicle ~= 0 then
|
|
-- Prüfe ob Spieler auf Fahrersitz ist
|
|
local driver = GetPedInVehicleSeat(currentVehicle, -1)
|
|
|
|
-- Nur wenn Spieler der Fahrer ist (Seat -1)
|
|
if driver == playerPed and IsVehicleClassAllowed(currentVehicle) then
|
|
local plate = QBCore.Functions.GetPlate(currentVehicle)
|
|
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not plate or plate == "" or string.len(plate) < 2 then
|
|
Debug("Skipping vehicle with invalid plate")
|
|
goto continue
|
|
end
|
|
|
|
-- Skip temporary vehicles
|
|
if IsTemporaryVehicle(currentVehicle) then
|
|
Debug("Skipping temporary vehicle")
|
|
goto continue
|
|
end
|
|
|
|
-- Skip likely job vehicles
|
|
if IsLikelyJobVehicle(currentVehicle) then
|
|
Debug("Skipping likely job vehicle")
|
|
goto continue
|
|
end
|
|
|
|
-- Skip recently spawned vehicles
|
|
if WasVehicleRecentlySpawned(plate) then
|
|
Debug("Skipping recently spawned vehicle: " .. plate)
|
|
goto continue
|
|
end
|
|
|
|
-- Anti-Duplication: Check if this plate already exists multiple times
|
|
if DoesVehicleExistInWorld(plate) then
|
|
Debug("Anti-Dupe: Detected duplicate vehicle with plate " .. plate .. ", not tracking")
|
|
goto continue
|
|
end
|
|
|
|
-- Check if this vehicle is already being tracked
|
|
if not trackedVehicles[plate] and not garagePending[plate] then
|
|
-- First verify this vehicle exists in the database
|
|
CheckVehicleExists(plate, function(exists)
|
|
if not exists then
|
|
Debug("Vehicle not in database, not tracking: " .. plate)
|
|
return
|
|
end
|
|
|
|
-- Track all vehicles that exist in the database
|
|
trackedVehicles[plate] = currentVehicle
|
|
|
|
-- Speichere letzte bekannte Position
|
|
lastKnownCoords[plate] = GetEntityCoords(currentVehicle)
|
|
|
|
-- Sofort starke Despawn-Verhinderung
|
|
PreventDespawn(currentVehicle)
|
|
|
|
Debug("Fahrzeug wird nun getrackt: " .. plate)
|
|
|
|
-- Hole Fahrzeugmods
|
|
local vehicleMods = GetVehicleMods(currentVehicle)
|
|
|
|
-- Registriere Fahrzeug beim Server
|
|
local vehicleCoords = GetEntityCoords(currentVehicle)
|
|
local vehicleHeading = GetEntityHeading(currentVehicle)
|
|
local vehicleModel = GetEntityModel(currentVehicle)
|
|
|
|
TriggerServerEvent('antidespawn:server:registerVehicle', plate, vehicleModel, vehicleCoords, vehicleHeading, vehicleMods)
|
|
end)
|
|
end
|
|
end
|
|
end
|
|
::continue::
|
|
end
|
|
end)
|
|
|
|
-- Kontinuierliche Despawn-Verhinderung für alle getrackten Fahrzeuge
|
|
CreateThread(function()
|
|
while true do
|
|
Wait(5000) -- Alle 5 Sekunden
|
|
|
|
local playerPed = PlayerPedId()
|
|
local playerPos = GetEntityCoords(playerPed)
|
|
|
|
for plate, vehicle in pairs(trackedVehicles) do
|
|
-- Anti-Duplication: Check if multiple vehicles with this plate exist
|
|
if DoesVehicleExistInWorld(plate) then
|
|
Debug("Anti-Dupe: Detected duplicate during tracking for plate " .. plate .. ", removing from tracking")
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
TriggerServerEvent('antidespawn:server:removeVehicle', plate)
|
|
goto continue
|
|
end
|
|
|
|
-- Prüfe ob Fahrzeug gerade in die Garage gestellt wird
|
|
if garagePending[plate] then
|
|
Debug("Fahrzeug wird gerade in Garage gestellt, entferne aus Tracking: " .. plate)
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
TriggerServerEvent('antidespawn:server:removeVehicle', plate)
|
|
elseif DoesEntityExist(vehicle) then
|
|
-- Check distance to player
|
|
local vehiclePos = GetEntityCoords(vehicle)
|
|
local distance = #(playerPos - vehiclePos)
|
|
|
|
if distance > 500.0 then -- 500 units = about 500 meters
|
|
Debug("Fahrzeug zu weit entfernt, entferne aus Tracking: " .. plate)
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
TriggerServerEvent('antidespawn:server:removeVehicle', plate)
|
|
else
|
|
PreventDespawn(vehicle)
|
|
|
|
-- Aktualisiere letzte bekannte Position
|
|
lastKnownCoords[plate] = vehiclePos
|
|
|
|
-- Hole Fahrzeugmods
|
|
local vehicleMods = GetVehicleMods(vehicle)
|
|
|
|
-- Aktualisiere Position
|
|
local vehicleHeading = GetEntityHeading(vehicle)
|
|
|
|
TriggerServerEvent('antidespawn:server:updateVehicle', plate, vehiclePos, vehicleHeading, vehicleMods)
|
|
|
|
Debug("Aktualisiere Fahrzeug: " .. plate)
|
|
end
|
|
else
|
|
Debug("Fahrzeug existiert nicht mehr: " .. plate)
|
|
|
|
-- Anti-Duplication: Check if vehicle was recently spawned before respawning
|
|
if WasVehicleRecentlySpawned(plate) then
|
|
Debug("Anti-Dupe: Not respawning recently spawned vehicle: " .. plate)
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
goto continue
|
|
end
|
|
|
|
-- Store the plate and coords for use in the callback
|
|
local currentPlate = plate
|
|
local currentCoords = lastKnownCoords[plate]
|
|
|
|
-- Verify this vehicle still exists in the database before respawning
|
|
CheckVehicleExists(plate, function(exists)
|
|
if not exists then
|
|
Debug("Vehicle no longer in database, not respawning: " .. currentPlate)
|
|
trackedVehicles[currentPlate] = nil
|
|
lastKnownCoords[currentPlate] = nil
|
|
return
|
|
end
|
|
|
|
-- Versuche Fahrzeug wiederherzustellen, aber nur wenn es nicht in die Garage gestellt wird
|
|
if currentCoords and not garagePending[currentPlate] then
|
|
Debug("Versuche Fahrzeug wiederherzustellen: " .. currentPlate)
|
|
TriggerServerEvent('antidespawn:server:respawnVehicle', currentPlate)
|
|
|
|
-- Entferne aus lokaler Tracking-Liste, wird nach Respawn wieder hinzugefügt
|
|
trackedVehicles[currentPlate] = nil
|
|
lastKnownCoords[currentPlate] = nil
|
|
else
|
|
-- Entferne aus Tracking
|
|
trackedVehicles[currentPlate] = nil
|
|
lastKnownCoords[currentPlate] = nil
|
|
end
|
|
end)
|
|
|
|
-- Remove from tracking immediately to avoid processing it again while waiting for the callback
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
end
|
|
::continue::
|
|
end
|
|
end
|
|
end)
|
|
|
|
|
|
-- Lade Fahrzeuge beim Spawn
|
|
RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function()
|
|
Debug("Spieler geladen, warte vor dem Laden der Fahrzeuge...")
|
|
-- Längere Wartezeit, um sicherzustellen, dass alles geladen ist
|
|
Wait(15000)
|
|
TriggerServerEvent('antidespawn:server:loadVehicles')
|
|
end)
|
|
|
|
-- Automatisches Laden beim Resource Start
|
|
CreateThread(function()
|
|
-- Längere Wartezeit beim Serverstart
|
|
Wait(20000)
|
|
|
|
-- Prüfe ob Spieler eingeloggt ist
|
|
local playerData = QBCore.Functions.GetPlayerData()
|
|
if playerData and playerData.citizenid then
|
|
Debug("Resource gestartet, lade Fahrzeuge...")
|
|
TriggerServerEvent('antidespawn:server:loadVehicles')
|
|
else
|
|
-- Warte auf Login, wenn Spieler noch nicht eingeloggt ist
|
|
Debug("Warte auf Spieler-Login...")
|
|
while true do
|
|
Wait(5000)
|
|
playerData = QBCore.Functions.GetPlayerData()
|
|
if playerData and playerData.citizenid then
|
|
Debug("Spieler jetzt eingeloggt, lade Fahrzeuge...")
|
|
TriggerServerEvent('antidespawn:server:loadVehicles')
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Spawne ein Fahrzeug
|
|
RegisterNetEvent('antidespawn:client:spawnVehicle', function(data)
|
|
Debug("Spawne Fahrzeug: " .. data.plate)
|
|
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not data.plate or data.plate == "" or string.len(data.plate) < 2 then
|
|
Debug("Skipping spawn of vehicle with invalid plate")
|
|
return
|
|
end
|
|
|
|
-- Anti-Duplication: Check if vehicle was recently spawned
|
|
if WasVehicleRecentlySpawned(data.plate) then
|
|
Debug("Anti-Dupe: Blocking respawn of recently spawned vehicle: " .. data.plate)
|
|
return
|
|
end
|
|
|
|
-- Prüfe ob Fahrzeug bereits existiert
|
|
local existingVehicle = GetVehicleByPlate(data.plate)
|
|
if existingVehicle then
|
|
Debug("Fahrzeug existiert bereits: " .. data.plate)
|
|
trackedVehicles[data.plate] = existingVehicle
|
|
lastKnownCoords[data.plate] = GetEntityCoords(existingVehicle)
|
|
PreventDespawn(existingVehicle)
|
|
return
|
|
end
|
|
|
|
-- Prüfe ob Fahrzeug gerade in die Garage gestellt wird
|
|
if garagePending[data.plate] then
|
|
Debug("Fahrzeug wird gerade in Garage gestellt, nicht spawnen: " .. data.plate)
|
|
return
|
|
end
|
|
|
|
-- Verify this vehicle exists in the database before spawning
|
|
CheckVehicleExists(data.plate, function(exists)
|
|
if not exists then
|
|
Debug("Vehicle not in database, not spawning: " .. data.plate)
|
|
return
|
|
end
|
|
|
|
-- Konvertiere Modell zu Hash wenn nötig
|
|
local modelHash = data.model
|
|
if type(modelHash) == "string" then
|
|
-- Versuche den String als Hash zu interpretieren
|
|
if tonumber(modelHash) then
|
|
modelHash = tonumber(modelHash)
|
|
else
|
|
-- Versuche den String als Modellnamen zu interpretieren
|
|
modelHash = GetHashKey(modelHash)
|
|
end
|
|
end
|
|
|
|
Debug("Versuche Modell zu laden: " .. tostring(modelHash))
|
|
|
|
-- Prüfe ob Modell existiert
|
|
if not IsModelInCdimage(modelHash) then
|
|
Debug("Modell existiert nicht in CD Image: " .. tostring(modelHash))
|
|
return
|
|
end
|
|
|
|
RequestModel(modelHash)
|
|
local timeout = 0
|
|
while not HasModelLoaded(modelHash) and timeout < 100 do
|
|
Wait(100)
|
|
timeout = timeout + 1
|
|
Debug("Warte auf Modell: " .. tostring(timeout) .. "/100")
|
|
end
|
|
|
|
if HasModelLoaded(modelHash) then
|
|
Debug("Modell geladen, erstelle Fahrzeug...")
|
|
|
|
-- Anti-Duplication: Final check before spawning
|
|
if GetVehicleByPlate(data.plate) then
|
|
Debug("Anti-Dupe: Vehicle with plate " .. data.plate .. " appeared during model loading, aborting spawn")
|
|
SetModelAsNoLongerNeeded(modelHash)
|
|
return
|
|
end
|
|
|
|
-- Verwende CREATE_AUTOMOBILE für bessere Persistenz
|
|
local vehicle
|
|
|
|
if Citizen and Citizen.InvokeNative then
|
|
-- OneSync Methode
|
|
Debug("Verwende OneSync Methode")
|
|
vehicle = Citizen.InvokeNative(0xAF35D0D2583051B0, modelHash, data.coords.x, data.coords.y, data.coords.z, data.heading, true, true)
|
|
else
|
|
-- Fallback
|
|
Debug("Verwende Fallback Methode")
|
|
vehicle = CreateVehicle(modelHash, data.coords.x, data.coords.y, data.coords.z, data.heading, true, false)
|
|
end
|
|
|
|
if DoesEntityExist(vehicle) then
|
|
-- Anti-Duplication: Mark as recently spawned
|
|
MarkVehicleAsSpawned(data.plate)
|
|
|
|
-- Warte bis Fahrzeug vollständig geladen ist
|
|
Wait(500)
|
|
|
|
-- Setze Kennzeichen
|
|
SetVehicleNumberPlateText(vehicle, data.plate)
|
|
|
|
-- Setze Mods
|
|
if data.mods then
|
|
Debug("Setze Fahrzeugmods...")
|
|
SetVehicleMods(vehicle, data.mods)
|
|
end
|
|
|
|
-- Setze Fuel
|
|
if GetResourceState(Config.FuelSystem) == 'started' then
|
|
exports[Config.FuelSystem]:SetFuel(vehicle, data.fuel or 100)
|
|
end
|
|
|
|
-- Verhindere Despawn
|
|
PreventDespawn(vehicle)
|
|
|
|
-- Füge zu getrackten Fahrzeugen hinzu
|
|
trackedVehicles[data.plate] = vehicle
|
|
lastKnownCoords[data.plate] = GetEntityCoords(vehicle)
|
|
|
|
-- Registriere beim Server
|
|
TriggerServerEvent('antidespawn:server:registerVehicle', data.plate, modelHash, GetEntityCoords(vehicle), GetEntityHeading(vehicle), GetVehicleMods(vehicle))
|
|
|
|
Debug("Fahrzeug erfolgreich gespawnt: " .. data.plate)
|
|
else
|
|
Debug("Fehler beim Spawnen des Fahrzeugs: " .. data.plate)
|
|
end
|
|
|
|
SetModelAsNoLongerNeeded(modelHash)
|
|
else
|
|
Debug("Modell konnte nicht geladen werden: " .. data.plate .. " (Hash: " .. tostring(modelHash) .. ")")
|
|
end
|
|
end)
|
|
end)
|
|
|
|
|
|
-- Event für Garage Store (wird ausgelöst, wenn der Spieler ein Fahrzeug in die Garage stellen will)
|
|
RegisterNetEvent('jg-advancedgarages:client:store-vehicle', function(garageId, garageVehicleType)
|
|
local playerPed = PlayerPedId()
|
|
local vehicle = GetVehiclePedIsIn(playerPed, false)
|
|
|
|
if vehicle ~= 0 then
|
|
local plate = QBCore.Functions.GetPlate(vehicle)
|
|
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not plate or plate == "" or string.len(plate) < 2 then
|
|
Debug("Skipping garage storage for vehicle with invalid plate")
|
|
return
|
|
end
|
|
|
|
-- Markiere Fahrzeug als "wird in Garage gestellt"
|
|
garagePending[plate] = true
|
|
SetTimeout(10000, function()
|
|
if garagePending[plate] then
|
|
Debug("Garage storage timeout for vehicle: " .. plate)
|
|
garagePending[plate] = nil
|
|
end
|
|
end)
|
|
-- Entferne aus Tracking
|
|
if trackedVehicles[plate] then
|
|
Debug("Fahrzeug wird in Garage gestellt, entferne aus Tracking: " .. plate)
|
|
trackedVehicles[plate] = nil
|
|
lastKnownCoords[plate] = nil
|
|
TriggerServerEvent('antidespawn:server:removeVehicle', plate)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- jg-advanced-garage Events
|
|
RegisterNetEvent('jg-advancedgarages:client:vehicle-stored', function(data)
|
|
if data and data.plate then
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not data.plate or data.plate == "" or string.len(data.plate) < 2 then
|
|
Debug("Skipping garage storage completion for vehicle with invalid plate")
|
|
return
|
|
end
|
|
|
|
-- Markiere Fahrzeug als "in Garage"
|
|
garagePending[data.plate] = nil
|
|
|
|
-- Entferne aus Tracking
|
|
if trackedVehicles[data.plate] then
|
|
trackedVehicles[data.plate] = nil
|
|
lastKnownCoords[data.plate] = nil
|
|
end
|
|
|
|
-- Entferne aus Datenbank
|
|
TriggerServerEvent('antidespawn:server:removeVehicle', data.plate)
|
|
|
|
Debug("Fahrzeug in Garage gespeichert, aus DB entfernt: " .. data.plate)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('jg-advancedgarages:client:vehicle-spawned', function(data)
|
|
if data and data.plate then
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not data.plate or data.plate == "" or string.len(data.plate) < 2 then
|
|
Debug("Skipping garage spawn completion for vehicle with invalid plate")
|
|
return
|
|
end
|
|
|
|
-- Entferne Markierung "in Garage"
|
|
garagePending[data.plate] = nil
|
|
|
|
Debug("Fahrzeug aus Garage gespawnt: " .. data.plate)
|
|
|
|
-- Warte kurz bis das Fahrzeug vollständig gespawnt ist
|
|
Wait(1000)
|
|
|
|
-- Finde das Fahrzeug
|
|
local vehicle = GetVehicleByPlate(data.plate)
|
|
if vehicle then
|
|
-- Füge zu getrackten Fahrzeugen hinzu
|
|
trackedVehicles[data.plate] = vehicle
|
|
lastKnownCoords[data.plate] = GetEntityCoords(vehicle)
|
|
|
|
-- Verhindere Despawn
|
|
PreventDespawn(vehicle)
|
|
|
|
Debug("Fahrzeug aus Garage zum Tracking hinzugefügt: " .. data.plate)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Öffnen der Garage
|
|
RegisterNetEvent('jg-advancedgarages:client:open-garage', function(garageId, vehicleType, spawnCoords)
|
|
Debug("Garage geöffnet: " .. garageId)
|
|
|
|
-- No need to mark vehicles as potentially being stored here
|
|
-- Let the actual store-vehicle event handle this
|
|
end)
|
|
|
|
-- Manuelle Lade-Funktion
|
|
RegisterCommand('loadvehicles', function()
|
|
Debug("Manuelles Laden der Fahrzeuge...")
|
|
TriggerServerEvent('antidespawn:server:loadVehicles')
|
|
end, false)
|
|
|
|
-- Debug Command
|
|
RegisterCommand('fixvehicle', function()
|
|
local playerPed = PlayerPedId()
|
|
local vehicle = GetVehiclePedIsIn(playerPed, false)
|
|
|
|
if vehicle ~= 0 then
|
|
local plate = QBCore.Functions.GetPlate(vehicle)
|
|
|
|
-- Skip vehicles with empty or invalid plates
|
|
if not plate or plate == "" or string.len(plate) < 2 then
|
|
Debug("Cannot fix vehicle with invalid plate")
|
|
return
|
|
end
|
|
|
|
-- Skip temporary vehicles
|
|
if IsTemporaryVehicle(vehicle) then
|
|
Debug("Cannot fix temporary vehicle")
|
|
return
|
|
end
|
|
|
|
-- Skip likely job vehicles
|
|
if IsLikelyJobVehicle(vehicle) then
|
|
Debug("Cannot fix likely job vehicle")
|
|
return
|
|
end
|
|
|
|
-- Anti-Duplication: Check if this plate already exists multiple times
|
|
if DoesVehicleExistInWorld(plate) then
|
|
Debug("Anti-Dupe: Detected duplicate vehicle with plate " .. plate .. ", not fixing")
|
|
return
|
|
end
|
|
|
|
-- Prüfe ob Fahrzeug gerade in die Garage gestellt wird
|
|
if not garagePending[plate] then
|
|
-- Verify this vehicle exists in the database
|
|
CheckVehicleExists(plate, function(exists)
|
|
if not exists then
|
|
Debug("Vehicle not in database, cannot fix: " .. plate)
|
|
return
|
|
end
|
|
|
|
PreventDespawn(vehicle)
|
|
trackedVehicles[plate] = vehicle
|
|
lastKnownCoords[plate] = GetEntityCoords(vehicle)
|
|
|
|
-- Registriere Fahrzeug beim Server
|
|
local vehicleCoords = GetEntityCoords(vehicle)
|
|
local vehicleHeading = GetEntityHeading(vehicle)
|
|
local vehicleModel = GetEntityModel(vehicle)
|
|
local vehicleMods = GetVehicleMods(vehicle)
|
|
|
|
TriggerServerEvent('antidespawn:server:registerVehicle', plate, vehicleModel, vehicleCoords, vehicleHeading, vehicleMods)
|
|
|
|
Debug("Anti-Despawn für Fahrzeug aktiviert: " .. plate)
|
|
end)
|
|
else
|
|
Debug("Fahrzeug wird gerade in Garage gestellt, kann nicht fixiert werden: " .. plate)
|
|
end
|
|
else
|
|
Debug("Du musst in einem Fahrzeug sitzen!")
|
|
end
|
|
end, false)
|
|
|
|
-- Server-side event handler for ownership verification
|
|
RegisterNetEvent('antidespawn:client:vehicleOwnershipResult', function(result, plate)
|
|
-- This event is handled by the DoesPlayerOwnVehicle function
|
|
end)
|
|
|
|
-- Clean up resources when script stops
|
|
AddEventHandler('onResourceStop', function(resourceName)
|
|
if resourceName == GetCurrentResourceName() then
|
|
Debug("Resource stopping, clearing all data")
|
|
trackedVehicles = {}
|
|
lastKnownCoords = {}
|
|
garagePending = {}
|
|
spawnedVehicles = {}
|
|
vehicleOwnership = {}
|
|
vehicleExistsCallbacks = {}
|
|
end
|
|
end)
|
|
|
|
-- Debug command to check vehicle duplication
|
|
RegisterCommand('checkdupes', function()
|
|
local vehicles = GetGamePool('CVehicle')
|
|
local plates = {}
|
|
local dupes = {}
|
|
|
|
for _, vehicle in pairs(vehicles) do
|
|
local plate = QBCore.Functions.GetPlate(vehicle)
|
|
if plate and plate ~= "" then
|
|
if plates[plate] then
|
|
if not dupes[plate] then
|
|
dupes[plate] = 2
|
|
else
|
|
dupes[plate] = dupes[plate] + 1
|
|
end
|
|
else
|
|
plates[plate] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local dupeCount = 0
|
|
for plate, count in pairs(dupes) do
|
|
Debug("Duplicate found: " .. plate .. " (Count: " .. count .. ")")
|
|
dupeCount = dupeCount + 1
|
|
end
|
|
|
|
if dupeCount == 0 then
|
|
Debug("No duplicate vehicles found")
|
|
else
|
|
Debug("Found " .. dupeCount .. " duplicate vehicles")
|
|
end
|
|
end, false)
|
|
|
|
-- Debug command to clear recently spawned vehicles list
|
|
RegisterCommand('clearspawned', function()
|
|
spawnedVehicles = {}
|
|
Debug("Cleared recently spawned vehicles list")
|
|
end, false)
|
|
|
|
-- Debug command to clear ownership cache
|
|
RegisterCommand('clearownership', function()
|
|
vehicleOwnership = {}
|
|
Debug("Cleared vehicle ownership cache")
|
|
end, false)
|
|
|
|
-- Debug command to list all tracked vehicles
|
|
RegisterCommand('listtracked', function()
|
|
local count = 0
|
|
Debug("Currently tracked vehicles:")
|
|
for plate, _ in pairs(trackedVehicles) do
|
|
Debug("- " .. plate)
|
|
count = count + 1
|
|
end
|
|
Debug("Total tracked vehicles: " .. count)
|
|
end, false)
|
|
|
|
-- Debug command to delete duplicate vehicles
|
|
RegisterCommand('deletedupes', function()
|
|
local vehicles = GetGamePool('CVehicle')
|
|
local plates = {}
|
|
local deleted = 0
|
|
|
|
for _, vehicle in pairs(vehicles) do
|
|
local plate = QBCore.Functions.GetPlate(vehicle)
|
|
if plate and plate ~= "" then
|
|
if plates[plate] then
|
|
DeleteEntity(vehicle)
|
|
deleted = deleted + 1
|
|
Debug("Deleted duplicate vehicle with plate: " .. plate)
|
|
else
|
|
plates[plate] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
Debug("Deleted " .. deleted .. " duplicate vehicles")
|
|
end, false)
|