local QBCore = exports['qb-core']:GetCoreObject() local isMenuOpen = false local currentLicenseData = nil -- Debug-Funktion local function debugPrint(message) if Config.Debug then print("^2[License-System Client] " .. message .. "^7") end end -- Hilfsfunktionen local function getClosestPlayer() local players = GetActivePlayers() local closestDistance = -1 local closestPlayer = -1 local ped = PlayerPedId() local coords = GetEntityCoords(ped) for i = 1, #players do local target = GetPlayerPed(players[i]) if target ~= ped then local targetCoords = GetEntityCoords(target) local distance = #(coords - targetCoords) if closestDistance == -1 or closestDistance > distance then closestPlayer = GetPlayerServerId(players[i]) closestDistance = distance end end end return closestPlayer, closestDistance end -- URL-Validierung local function isValidUrl(url) if not url or url == "" then return true end -- Optional return string.match(url, Config.Validation.url_pattern) ~= nil end -- Datum-Validierung local function isValidDate(date) if not date or date == "" then return false end if not string.match(date, Config.Validation.date_pattern) then return false end local day, month, year = date:match("(%d+)%.(%d+)%.(%d+)") day, month, year = tonumber(day), tonumber(month), tonumber(year) if not day or not month or not year then return false end if day < 1 or day > 31 then return false end if month < 1 or month > 12 then return false end if year < 1900 or year > 2100 then return false end return true end -- Bild-URL validieren local function validateImageUrl(url, callback) if not url or url == "" then callback(true, Config.UI.default_avatar) return end if not isValidUrl(url) then callback(false, "Ungültige URL") return end -- Dateiformat prüfen local extension = string.lower(url:match("%.([^%.]+)$") or "") local isValidFormat = false for _, format in ipairs(Config.UI.allowed_image_formats) do if extension == format then isValidFormat = true break end end if not isValidFormat then callback(false, "Ungültiges Bildformat. Erlaubt: " .. table.concat(Config.UI.allowed_image_formats, ", ")) return end callback(true, url) end -- Erweiterte Lizenz-Erstellung mit benutzerdefinierten Feldern local function openCustomLicenseCreation(targetId, licenseType) debugPrint("Öffne erweiterte Lizenz-Erstellung für: " .. licenseType) local config = Config.LicenseTypes[licenseType] if not config then QBCore.Functions.Notify("Unbekannter Lizenztyp!", "error") return end -- NUI-Daten vorbereiten local formData = { licenseType = licenseType, config = config, targetId = targetId, validation = Config.Validation, ui = Config.UI } debugPrint("Sende Daten an NUI: " .. json.encode(formData)) -- NUI öffnen SetNuiFocus(true, true) SendNUIMessage({ action = "openCustomLicenseForm", data = formData }) isMenuOpen = true end -- Standard-Lizenz-Erstellung (ohne benutzerdefinierte Felder) local function openStandardLicenseCreation(targetId, licenseType) debugPrint("Öffne Standard-Lizenz-Erstellung für: " .. licenseType) local config = Config.LicenseTypes[licenseType] if not config then QBCore.Functions.Notify("Unbekannter Lizenztyp!", "error") return end local classes = {} if config.classes then for classKey, classLabel in pairs(config.classes) do table.insert(classes, {key = classKey, label = classLabel}) end end -- Standard-Erstellung (für Lizenzen ohne custom_fields) TriggerServerEvent('license-system:server:issueLicense', targetId, licenseType, classes) end -- Hauptmenü öffnen local function openMainMenu() debugPrint("Öffne Hauptmenü") local targetId, distance = getClosestPlayer() local menuData = { licenseTypes = Config.LicenseTypes, targetId = targetId, targetDistance = distance, hasTarget = targetId ~= -1 and distance <= 3.0 } SetNuiFocus(true, true) SendNUIMessage({ action = "openMainMenu", data = menuData }) isMenuOpen = true end -- NUI-Callbacks RegisterNUICallback('closeMenu', function(data, cb) debugPrint("Schließe Menü") SetNuiFocus(false, false) isMenuOpen = false cb('ok') end) RegisterNUICallback('requestLicense', function(data, cb) debugPrint("Lizenz angefordert für Spieler: " .. data.targetId) TriggerServerEvent('license-system:server:requestLicense', data.targetId) cb('ok') end) RegisterNUICallback('requestMyLicense', function(data, cb) debugPrint("Eigene Lizenz angefordert: " .. (data.licenseType or "alle")) TriggerServerEvent('license-system:server:requestMyLicense', data.licenseType) cb('ok') end) RegisterNUICallback('requestPlayerLicenses', function(data, cb) debugPrint("Alle Lizenzen angefordert für Spieler: " .. data.targetId) TriggerServerEvent('license-system:server:requestPlayerLicenses', data.targetId) cb('ok') end) RegisterNUICallback('openLicenseCreation', function(data, cb) debugPrint("Lizenz-Erstellung angefordert: " .. data.licenseType) local config = Config.LicenseTypes[data.licenseType] if config and config.custom_fields and #config.custom_fields > 0 then openCustomLicenseCreation(data.targetId, data.licenseType) else openStandardLicenseCreation(data.targetId, data.licenseType) end cb('ok') end) RegisterNUICallback('validateImageUrl', function(data, cb) debugPrint("Validiere Bild-URL: " .. (data.url or "leer")) validateImageUrl(data.url, function(isValid, result) cb({ valid = isValid, url = isValid and result or Config.UI.default_avatar, error = not isValid and result or nil }) end) end) RegisterNUICallback('submitCustomLicense', function(data, cb) debugPrint("Benutzerdefinierte Lizenz eingereicht") debugPrint("Daten: " .. json.encode(data)) local licenseType = data.licenseType local targetId = data.targetId local customData = data.customData local classes = data.classes or {} -- Validierung local config = Config.LicenseTypes[licenseType] if not config then cb({success = false, error = "Unbekannter Lizenztyp"}) return end -- Pflichtfelder prüfen for _, field in ipairs(config.custom_fields or {}) do if field.required then local value = customData[field.name] if not value or value == "" then cb({success = false, error = "Feld '" .. field.label .. "' ist erforderlich"}) return end -- Spezielle Validierung if field.type == "date" and not isValidDate(value) then cb({success = false, error = "Ungültiges Datum in Feld '" .. field.label .. "'"}) return end if field.type == "url" and not isValidUrl(value) then cb({success = false, error = "Ungültige URL in Feld '" .. field.label .. "'"}) return end end end -- An Server senden TriggerServerEvent('license-system:server:issueCustomLicense', targetId, licenseType, customData, classes) cb({success = true}) end) RegisterNUICallback('revokeLicense', function(data, cb) debugPrint("Lizenz-Entzug angefordert: " .. data.licenseType .. " für Spieler: " .. data.targetId) TriggerServerEvent('license-system:server:revokeLicense', data.targetId, data.licenseType) cb('ok') end) -- Server-Events RegisterNetEvent('license-system:client:receiveLicense', function(licenseData) debugPrint("Lizenz erhalten") if licenseData then debugPrint("Zeige Lizenz: " .. licenseData.license.license_type) SetNuiFocus(true, true) SendNUIMessage({ action = "showLicense", data = licenseData }) else debugPrint("Keine Lizenz gefunden") QBCore.Functions.Notify("Keine Lizenz gefunden!", "error") end end) RegisterNetEvent('license-system:client:receiveMyLicense', function(licenseData, licenseType) debugPrint("Eigene Lizenz erhalten: " .. (licenseType or "unbekannt")) if licenseData then debugPrint("Zeige eigene Lizenz: " .. licenseData.license.license_type) SetNuiFocus(true, true) SendNUIMessage({ action = "showMyLicense", data = licenseData }) else debugPrint("Keine eigene Lizenz gefunden") QBCore.Functions.Notify("Du hast keine " .. (Config.LicenseTypes[licenseType] and Config.LicenseTypes[licenseType].label or "Lizenz") .. "!", "error") end end) RegisterNetEvent('license-system:client:receivePlayerLicenses', function(licenses, targetId, targetName) debugPrint("Spieler-Lizenzen erhalten: " .. #licenses .. " für " .. targetName) SetNuiFocus(true, true) SendNUIMessage({ action = "showPlayerLicenses", data = { licenses = licenses, targetId = targetId, targetName = targetName, licenseTypes = Config.LicenseTypes } }) end) RegisterNetEvent('license-system:client:licenseIssued', function(targetId, licenseType) debugPrint("Lizenz ausgestellt: " .. licenseType) QBCore.Functions.Notify("Lizenz erfolgreich ausgestellt!", "success") end) RegisterNetEvent('license-system:client:licenseRevoked', function(targetId, licenseType) debugPrint("Lizenz entzogen: " .. licenseType) QBCore.Functions.Notify("Lizenz erfolgreich entzogen!", "success") end) RegisterNetEvent('license-system:client:refreshMenu', function() debugPrint("Menü-Refresh angefordert") if isMenuOpen then -- Menü neu laden falls geöffnet Wait(500) openMainMenu() end end) -- Commands RegisterCommand('lizenz', function() if not isMenuOpen then openMainMenu() end end, false) RegisterCommand('ausweis', function() TriggerServerEvent('license-system:server:requestMyLicense', 'id_card') end, false) -- Keybinds RegisterKeyMapping('lizenz', 'Lizenz-System öffnen', 'keyboard', 'F6') RegisterKeyMapping('ausweis', 'Eigenen Ausweis zeigen', 'keyboard', 'F7') -- Cleanup AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then if isMenuOpen then SetNuiFocus(false, false) isMenuOpen = false end end end) debugPrint("License-System Client geladen (Erweiterte Erstellung)")