local QBCore = exports['qb-core']:GetCoreObject() local vehicles = {} local activeSpawns = {} -- Track active spawn requests to prevent duplicates -- Debug Funktion local function Debug(msg) if Config.Debug then print("[AntiDespawn] " .. msg) end end -- Erstelle Tabelle bei Serverstart CreateThread(function() -- Prüfe ob die Tabelle existiert MySQL.query("SHOW TABLES LIKE 'vehicle_antidespawn'", {}, function(result) if result and #result > 0 then -- Tabelle existiert, prüfe ob das mods-Feld existiert MySQL.query("SHOW COLUMNS FROM vehicle_antidespawn LIKE 'mods'", {}, function(columns) if columns and #columns == 0 then -- mods-Feld existiert nicht, füge es hinzu Debug("Füge mods-Feld zur Tabelle hinzu...") MySQL.query("ALTER TABLE vehicle_antidespawn ADD COLUMN mods LONGTEXT DEFAULT NULL", {}) end end) -- Prüfe ob das owner-Feld existiert MySQL.query("SHOW COLUMNS FROM vehicle_antidespawn LIKE 'owner'", {}, function(columns) if columns and #columns == 0 then -- owner-Feld existiert nicht, füge es hinzu Debug("Füge owner-Feld zur Tabelle hinzu...") MySQL.query("ALTER TABLE vehicle_antidespawn ADD COLUMN owner VARCHAR(50) DEFAULT NULL", {}) end end) else -- Tabelle existiert nicht, erstelle sie Debug("Erstelle Datenbank-Tabelle...") MySQL.query([[ CREATE TABLE IF NOT EXISTS `vehicle_antidespawn` ( `id` int(11) NOT NULL AUTO_INCREMENT, `plate` varchar(50) NOT NULL, `model` varchar(50) NOT NULL, `coords` longtext NOT NULL, `heading` float NOT NULL, `fuel` int(11) DEFAULT 100, `mods` longtext DEFAULT NULL, `owner` varchar(50) DEFAULT NULL, `last_updated` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `plate` (`plate`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ]]) end end) Debug("Datenbank initialisiert") -- Warte kurz, bis die Tabelle aktualisiert wurde Wait(1000) -- Lade alle Fahrzeuge aus der Datenbank MySQL.query("SELECT * FROM vehicle_antidespawn", {}, function(results) if results and #results > 0 then Debug("Lade " .. #results .. " Fahrzeuge aus der Datenbank") for _, vehicle in pairs(results) do vehicles[vehicle.plate] = { model = vehicle.model, coords = json.decode(vehicle.coords), heading = vehicle.heading, fuel = vehicle.fuel, mods = vehicle.mods and json.decode(vehicle.mods) or nil, owner = vehicle.owner, last_updated = vehicle.last_updated } end end end) end) -- Check if a vehicle exists in the database RegisterNetEvent('antidespawn:server:checkVehicleExists', function(plate) local src = source -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, false, plate) return end MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) local exists = result and #result > 0 TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, exists, plate) end) end) -- Check if a player owns a vehicle RegisterNetEvent('antidespawn:server:checkVehicleOwnership', function(plate) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, false, plate) return end -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, false, plate) return end MySQL.query('SELECT * FROM player_vehicles WHERE plate = ? AND citizenid = ?', {plate, Player.PlayerData.citizenid}, function(result) local isOwned = result and #result > 0 TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, isOwned, plate) end) end) -- Register a vehicle (track all vehicles, regardless of ownership) RegisterNetEvent('antidespawn:server:registerVehicle', function(plate, model, coords, heading, mods) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then Debug("Skipping vehicle with invalid plate") return end -- Check if vehicle exists in player_vehicles (any player) MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) if not result or #result == 0 then Debug("Vehicle not found in database: " .. plate) return end -- Check if vehicle is in garage local inGarage = false local ownerId = nil for _, veh in ipairs(result) do if veh.state == 1 then inGarage = true end ownerId = veh.citizenid -- Store the owner ID end if inGarage then Debug("Fahrzeug ist in der Garage, nicht registrieren: " .. plate) -- Remove from Anti-Despawn database if present if vehicles[plate] then vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) Debug("Fahrzeug aus Anti-Despawn entfernt: " .. plate) end return end -- Continue with registration as before vehicles[plate] = { model = model, coords = coords, heading = heading, fuel = 100, mods = mods, owner = ownerId, last_updated = os.time() } MySQL.query("INSERT INTO vehicle_antidespawn (plate, model, coords, heading, fuel, mods, owner) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE coords = VALUES(coords), heading = VALUES(heading), mods = VALUES(mods), owner = VALUES(owner), last_updated = CURRENT_TIMESTAMP", { plate, tostring(model), json.encode(coords), heading, 100, json.encode(mods), ownerId }) Debug("Fahrzeug registriert: " .. plate .. " (Modell: " .. tostring(model) .. ", Besitzer: " .. tostring(ownerId) .. ")") end) end) -- Update a vehicle RegisterNetEvent('antidespawn:server:updateVehicle', function(plate, coords, heading, mods) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end if not vehicles[plate] then return end -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then Debug("Skipping update for vehicle with invalid plate") return end -- Check if vehicle exists in player_vehicles (any player) MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) if not result or #result == 0 then Debug("Vehicle not found in database: " .. plate) -- Remove from tracking as it no longer exists in the database vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) return end -- Check if vehicle is in garage local inGarage = false for _, veh in ipairs(result) do if veh.state == 1 then inGarage = true break end end if inGarage then Debug("Fahrzeug ist in der Garage, entferne aus Tracking: " .. plate) vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", { plate }) return end vehicles[plate].coords = coords vehicles[plate].heading = heading vehicles[plate].mods = mods vehicles[plate].last_updated = os.time() MySQL.query("UPDATE vehicle_antidespawn SET coords = ?, heading = ?, mods = ?, last_updated = CURRENT_TIMESTAMP WHERE plate = ?", { json.encode(coords), heading, json.encode(mods), plate }) Debug("Fahrzeug aktualisiert: " .. plate) end) end) -- Remove a vehicle RegisterNetEvent('antidespawn:server:removeVehicle', function(plate) local src = source if not vehicles[plate] then return end -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then Debug("Skipping removal for vehicle with invalid plate") return end vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", { plate }) Debug("Fahrzeug entfernt: " .. plate) end) -- Respawn a vehicle (allow respawning any tracked vehicle) RegisterNetEvent('antidespawn:server:respawnVehicle', function(plate) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then Debug("Skipping respawn for vehicle with invalid plate") return end -- Anti-Duplication: Check if there's already an active spawn request for this plate if activeSpawns[plate] then Debug("Anti-Dupe: Already processing spawn request for: " .. plate) return end -- Mark as active spawn activeSpawns[plate] = true -- Set a timeout to clear the active spawn status SetTimeout(10000, function() activeSpawns[plate] = nil end) -- Check if vehicle exists in database (any player) MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) if not result or #result == 0 then Debug("Vehicle not found in database: " .. plate) activeSpawns[plate] = nil return end if not vehicles[plate] then Debug("Fahrzeug nicht in Datenbank: " .. plate) activeSpawns[plate] = nil return end -- Check if vehicle is in garage local inGarage = false for _, veh in ipairs(result) do if veh.state == 1 then inGarage = true break end end if inGarage then Debug("Fahrzeug ist in der Garage, nicht respawnen: " .. plate) -- Remove from Anti-Despawn database vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) activeSpawns[plate] = nil return end -- Send spawn event back to client TriggerClientEvent('antidespawn:client:spawnVehicle', src, { plate = plate, model = vehicles[plate].model, coords = vehicles[plate].coords, heading = vehicles[plate].heading, fuel = vehicles[plate].fuel, mods = vehicles[plate].mods }) Debug("Fahrzeug Respawn angefordert: " .. plate .. " (Besitzer: " .. tostring(vehicles[plate].owner) .. ")") end) end) -- Load vehicles for a player (load all vehicles in range, not just owned ones) RegisterNetEvent('antidespawn:server:loadVehicles', function() local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then Debug("Spieler nicht gefunden") return end Debug("Lade Fahrzeuge für Spieler: " .. Player.PlayerData.citizenid) local playerCoords = GetEntityCoords(GetPlayerPed(src)) local loadedCount = 0 local vehiclesToLoad = {} -- Load all vehicles in the database, not just owned ones for plate, vehicle in pairs(vehicles) do -- Skip vehicles with empty or invalid plates if not plate or plate == "" or string.len(plate) < 2 then Debug("Skipping load for vehicle with invalid plate") vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) goto continue end -- Check if vehicle is in garage by querying the database MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) if not result or #result == 0 then Debug("Fahrzeug existiert nicht in player_vehicles: " .. plate) -- Entferne aus Anti-Despawn Datenbank vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) return end -- Check if vehicle is in garage local inGarage = false for _, veh in ipairs(result) do if veh.state == 1 then inGarage = true break end end if inGarage then Debug("Fahrzeug ist in der Garage, nicht laden: " .. plate) -- Entferne aus Anti-Despawn Datenbank vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) return end -- Lade nur Fahrzeuge in der Nähe des Spielers local distance = #(playerCoords - vector3(vehicle.coords.x, vehicle.coords.y, vehicle.coords.z)) if distance < 100.0 then -- Stelle sicher, dass das Modell als Zahl gespeichert ist local model = vehicle.model if type(model) == "string" then model = tonumber(model) or model end table.insert(vehiclesToLoad, { plate = plate, model = model, coords = vehicle.coords, heading = vehicle.heading, fuel = vehicle.fuel, mods = vehicle.mods }) loadedCount = loadedCount + 1 end end) ::continue:: end -- Warte kurz und lade dann die Fahrzeuge SetTimeout(3000, function() for _, vehicleData in ipairs(vehiclesToLoad) do -- Anti-Duplication: Check if there's already an active spawn request for this plate if not activeSpawns[vehicleData.plate] then activeSpawns[vehicleData.plate] = true -- Set a timeout to clear the active spawn status SetTimeout(10000, function() activeSpawns[vehicleData.plate] = nil end) TriggerClientEvent('antidespawn:client:spawnVehicle', src, vehicleData) Debug("Fahrzeug für Spieler geladen: " .. vehicleData.plate) else Debug("Anti-Dupe: Already processing spawn request for: " .. vehicleData.plate) end end Debug("Fahrzeugladung abgeschlossen. " .. loadedCount .. " Fahrzeuge geladen.") end) end) -- Cleanup alte Einträge (älter als 24 Stunden) CreateThread(function() while true do Wait(3600000) -- 1 Stunde MySQL.query("DELETE FROM vehicle_antidespawn WHERE last_updated < DATE_SUB(NOW(), INTERVAL 24 HOUR)") Debug("Alte Fahrzeugeinträge bereinigt") end end) -- Registriere jg-advancedgarages Events RegisterNetEvent('jg-advancedgarages:server:vehicle-stored', function(data) if data and data.plate then Debug("Fahrzeug in Garage gespeichert: " .. data.plate) -- Entferne aus Anti-Despawn Datenbank if vehicles[data.plate] then vehicles[data.plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", { data.plate }) Debug("Fahrzeug aus Anti-Despawn entfernt: " .. data.plate) end end end) RegisterNetEvent('jg-advancedgarages:server:vehicle-spawned', function(data) if data and data.plate then Debug("Fahrzeug aus Garage gespawnt: " .. data.plate) -- Entferne aus Anti-Despawn Datenbank, da es jetzt von der Garage verwaltet wird if vehicles[data.plate] then vehicles[data.plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", { data.plate }) Debug("Fahrzeug aus Anti-Despawn entfernt: " .. data.plate) end end end) -- Befehl zum Anzeigen aller gespeicherten Fahrzeuge RegisterCommand('listvehicles', function(source, args, rawCommand) if source == 0 then -- Nur über Konsole ausführbar Debug("Gespeicherte Fahrzeuge:") local count = 0 for plate, vehicle in pairs(vehicles) do Debug(plate .. " - Modell: " .. tostring(vehicle.model) .. " - Position: " .. tostring(vehicle.coords.x) .. ", " .. tostring(vehicle.coords.y) .. ", " .. tostring(vehicle.coords.z) .. " - Besitzer: " .. tostring(vehicle.owner)) count = count + 1 end Debug("Insgesamt " .. count .. " Fahrzeuge gespeichert.") end end, true) -- Befehl zum Prüfen des Garage-Status eines Fahrzeugs RegisterCommand('checkgarage', function(source, args, rawCommand) if source == 0 and args[1] then -- Nur über Konsole ausführbar local plate = args[1] MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) if result and #result > 0 then for _, veh in ipairs(result) do Debug("Fahrzeug " .. plate .. " - State: " .. veh.state .. " - Owner: " .. veh.citizenid) end else Debug("Fahrzeug " .. plate .. " nicht in player_vehicles gefunden.") end end) end end, true) -- Befehl zum manuellen Entfernen eines Fahrzeugs RegisterCommand('removevehicle', function(source, args, rawCommand) if source == 0 and args[1] then -- Nur über Konsole ausführbar local plate = args[1] if vehicles[plate] then vehicles[plate] = nil MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) Debug("Fahrzeug " .. plate .. " aus Anti-Despawn entfernt.") else Debug("Fahrzeug " .. plate .. " nicht in Anti-Despawn gefunden.") end end end, true) -- Befehl zum Bereinigen der Datenbank RegisterCommand('clearvehicles', function(source, args, rawCommand) if source == 0 then -- Nur über Konsole ausführbar local count = 0 for plate, vehicle in pairs(vehicles) do local model = vehicle.model -- Prüfe ob das Modell gültig ist if type(model) == "string" and not tonumber(model) then -- Ungültiges Modell, entferne aus Datenbank MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate}) vehicles[plate] = nil count = count + 1 Debug("Ungültiges Modell entfernt: " .. plate .. " (Modell: " .. tostring(model) .. ")") end end Debug("Bereinigung abgeschlossen. " .. count .. " Fahrzeuge entfernt.") end end, true) -- Befehl zum Leeren der Datenbank RegisterCommand('clearalldespawn', function(source, args, rawCommand) if source == 0 then -- Nur über Konsole ausführbar MySQL.query("DELETE FROM vehicle_antidespawn", {}) vehicles = {} Debug("Alle Fahrzeuge aus der Datenbank entfernt.") end end, true) -- Debug command to check active spawns RegisterCommand('activespawns', function(source, args, rawCommand) if source == 0 then -- Nur über Konsole ausführbar local count = 0 for plate, _ in pairs(activeSpawns) do Debug("Active spawn: " .. plate) count = count + 1 end Debug("Total active spawns: " .. count) end end, true) -- Check if a vehicle exists in the database RegisterNetEvent('antidespawn:server:checkVehicleExists', function(plate, callback) local src = source MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result) local exists = result and #result > 0 TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, exists, plate) end) end) -- Client callback for vehicle existence check RegisterNetEvent('antidespawn:client:vehicleExistsResult', function(exists, plate) -- This event will be handled by the callback system end) -- Clean up when resource stops AddEventHandler('onResourceStop', function(resourceName) if resourceName == GetCurrentResourceName() then Debug("Resource stopping, clearing all data") vehicles = {} activeSpawns = {} end end)