local QBCore = exports['qb-core']:GetCoreObject() -- Lokale Variablen local isMenuOpen = false local currentTarget = nil local nearbyPlayers = {} local isLicenseShowing = false local pendingRequests = {} local currentLicenseData = nil local currentTargetId = nil -- Hilfsfunktionen local function debugPrint(message) if Config.Debug then print("^3[License-System Client] " .. message .. "^7") end end local function showNotification(message, type) QBCore.Functions.Notify(message, type or 'primary') end -- Nearby Players abrufen local function getNearbyPlayers(radius) radius = radius or 5.0 local players = {} local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) for _, playerId in ipairs(GetActivePlayers()) do local targetPed = GetPlayerPed(playerId) if targetPed ~= playerPed then local targetCoords = GetEntityCoords(targetPed) local distance = #(playerCoords - targetCoords) if distance <= radius then local serverId = GetPlayerServerId(playerId) local playerName = GetPlayerName(playerId) table.insert(players, { id = serverId, name = playerName, distance = math.floor(distance * 100) / 100, ped = targetPed }) end end end -- Nach Entfernung sortieren table.sort(players, function(a, b) return a.distance < b.distance end) debugPrint("Gefundene Spieler in der Nähe: " .. #players) return players end -- Berechtigung prüfen local function hasPermission() local PlayerData = QBCore.Functions.GetPlayerData() if not PlayerData or not PlayerData.job then return false end local hasAuth = Config.AuthorizedJobs[PlayerData.job.name] or false debugPrint("Berechtigung für Job " .. PlayerData.job.name .. ": " .. tostring(hasAuth)) return hasAuth end -- Lizenz anzeigen local function showLicense(licenseData) if not licenseData then showNotification(Config.Notifications.license_not_found.message, Config.Notifications.license_not_found.type) return end debugPrint("Zeige Lizenz: " .. licenseData.license.license_type) SendNUIMessage({ action = 'showLicense', data = licenseData }) SetNuiFocus(true, true) isLicenseShowing = true end -- Lizenz schließen local function closeLicense() SendNUIMessage({ action = 'hideLicense' }) SetNuiFocus(false, false) isLicenseShowing = false debugPrint("Lizenz geschlossen") end -- Spieler-Lizenz anzeigen local function showPlayerLicense(targetId) debugPrint("=== showPlayerLicense START ===") debugPrint("Sende Event: requestLicense für Spieler: " .. tostring(targetId)) TriggerServerEvent('license-system:server:requestLicense', targetId) end -- Eigene Lizenz anzeigen local function showMyLicense(licenseType) debugPrint("=== showMyLicense START ===") debugPrint("Sende Event: requestMyLicense für Typ: " .. tostring(licenseType)) TriggerServerEvent('license-system:server:requestMyLicense', licenseType) end -- FORWARD DECLARATIONS (Funktionen die später definiert werden) local confirmIssueLicense local openIssueLicenseMenu local openDriversLicenseClassMenu local openRevokeLicenseMenu local openPlayerLicenseMenu local openLicenseMenu local openManualLicenseEntry local openReactivateLicenseMenu -- Helper function to get license type options local function getLicenseTypeOptions() local options = {} for licenseType, config in pairs(Config.LicenseTypes) do table.insert(options, { value = licenseType, label = config.label }) end return options end -- Lizenz-Ausstellung bestätigen (FRÜH DEFINIERT) confirmIssueLicense = function(targetId, targetName, licenseType, classes) local config = Config.LicenseTypes[licenseType] if not config then showNotification('Unbekannter Lizenztyp!', 'error') return end local classText = classes and table.concat(classes, ', ') or 'Keine' local priceText = config.price and (config.price .. ' $') or 'Kostenlos' debugPrint("Bestätige Lizenz-Ausstellung: " .. licenseType .. " für " .. targetName) lib.registerContext({ id = 'confirm_issue_license', title = 'Lizenz ausstellen bestätigen', options = { { title = 'Spieler: ' .. targetName, disabled = true, icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. config.label, disabled = true, icon = config.icon }, { title = 'Klassen: ' .. classText, disabled = true, icon = 'fas fa-list' }, { title = 'Kosten: ' .. priceText, disabled = true, icon = 'fas fa-dollar-sign' }, { title = '─────────────────', disabled = true }, { title = '✅ Bestätigen', description = 'Lizenz jetzt ausstellen', icon = 'fas fa-check', onSelect = function() debugPrint("Sende Lizenz-Ausstellung an Server...") TriggerServerEvent('license-system:server:issueLicense', targetId, licenseType, classes) lib.hideContext() end }, { title = '❌ Abbrechen', description = 'Vorgang abbrechen', icon = 'fas fa-times', onSelect = function() openIssueLicenseMenu(targetId, targetName) end } } }) lib.showContext('confirm_issue_license') end -- Führerschein-Klassen Menü openDriversLicenseClassMenu = function(targetId, targetName, licenseType) local config = Config.LicenseTypes[licenseType] if not config then showNotification('Lizenz-Konfiguration nicht gefunden!', 'error') return end local selectedClasses = {} local function updateMenu() local menuOptions = {} if config.classes then for _, class in ipairs(config.classes) do local isSelected = false for _, selected in ipairs(selectedClasses) do if selected == class then isSelected = true break end end local classDescriptions = { ['A'] = 'Motorräder', ['A1'] = 'Leichte Motorräder (bis 125ccm)', ['A2'] = 'Mittlere Motorräder (bis 35kW)', ['B'] = 'PKW (bis 3,5t)', ['BE'] = 'PKW mit Anhänger', ['C'] = 'LKW (über 3,5t)', ['CE'] = 'LKW mit Anhänger', ['D'] = 'Bus (über 8 Personen)', ['DE'] = 'Bus mit Anhänger' } table.insert(menuOptions, { title = 'Klasse ' .. class .. (isSelected and ' ✅' or ''), description = classDescriptions[class] or 'Keine Beschreibung', icon = isSelected and 'fas fa-check-square' or 'far fa-square', onSelect = function() if isSelected then -- Klasse entfernen for i, selected in ipairs(selectedClasses) do if selected == class then table.remove(selectedClasses, i) break end end else -- Klasse hinzufügen table.insert(selectedClasses, class) end updateMenu() end }) end end table.insert(menuOptions, { title = '─────────────────', disabled = true }) table.insert(menuOptions, { title = 'Bestätigen (' .. #selectedClasses .. ' Klassen)', description = 'Führerschein mit ausgewählten Klassen ausstellen', icon = 'fas fa-check', disabled = #selectedClasses == 0, onSelect = function() confirmIssueLicense(targetId, targetName, licenseType, selectedClasses) end }) table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openIssueLicenseMenu(targetId, targetName) end }) lib.registerContext({ id = 'drivers_license_classes', title = 'Führerschein-Klassen: ' .. targetName, options = menuOptions }) lib.showContext('drivers_license_classes') end updateMenu() end -- Lizenz ausstellen Menü openIssueLicenseMenu = function(targetId, targetName) debugPrint("Öffne Lizenz-Ausstellungs-Menü für: " .. targetName) local menuOptions = {} for licenseType, config in pairs(Config.LicenseTypes) do local priceText = config.price and (config.price .. ' $') or 'Kostenlos' local validityText = config.validity_days and (config.validity_days .. ' Tage') or 'Unbegrenzt' table.insert(menuOptions, { title = config.label, description = config.description or 'Keine Beschreibung verfügbar', icon = config.icon, onSelect = function() if licenseType == 'drivers_license' and config.classes then openDriversLicenseClassMenu(targetId, targetName, licenseType) else confirmIssueLicense(targetId, targetName, licenseType, nil) end end, metadata = { {label = 'Preis', value = priceText}, {label = 'Gültigkeitsdauer', value = validityText} } }) end table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openPlayerLicenseMenu(targetId, targetName) end }) lib.registerContext({ id = 'issue_license', title = 'Lizenz ausstellen: ' .. targetName, options = menuOptions }) lib.showContext('issue_license') end -- Lizenz entziehen Menü openRevokeLicenseMenu = function(targetId, targetName, licenses) debugPrint("Öffne Lizenz-Entziehungs-Menü für: " .. targetName) local menuOptions = {} for _, license in ipairs(licenses) do if license.is_active == 1 then local config = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card' } table.insert(menuOptions, { title = config.label, description = 'Diese Lizenz entziehen', icon = config.icon, onSelect = function() lib.registerContext({ id = 'confirm_revoke_license', title = 'Lizenz entziehen bestätigen', options = { { title = 'Spieler: ' .. targetName, disabled = true, icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. config.label, disabled = true, icon = config.icon }, { title = '─────────────────', disabled = true }, { title = '✅ Bestätigen', description = 'Lizenz jetzt entziehen', icon = 'fas fa-check', onSelect = function() debugPrint("Sende Lizenz-Entziehung an Server...") TriggerServerEvent('license-system:server:revokeLicense', targetId, license.license_type) lib.hideContext() end }, { title = '❌ Abbrechen', description = 'Vorgang abbrechen', icon = 'fas fa-times', onSelect = function() openRevokeLicenseMenu(targetId, targetName, licenses) end } } }) lib.showContext('confirm_revoke_license') end }) end end if #menuOptions == 0 then table.insert(menuOptions, { title = 'Keine aktiven Lizenzen', description = 'Dieser Spieler hat keine aktiven Lizenzen', icon = 'fas fa-exclamation-triangle', disabled = true }) end table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openPlayerLicenseMenu(targetId, targetName) end }) lib.registerContext({ id = 'revoke_license', title = 'Lizenz entziehen: ' .. targetName, options = menuOptions }) lib.showContext('revoke_license') end -- Manual License Entry Menu openManualLicenseEntry = function(targetId, targetName) debugPrint("Öffne manuelle Lizenz-Eingabe für: " .. targetName) local input = lib.inputDialog('Lizenz manuell ausstellen', { {type = 'select', label = 'Lizenztyp', options = getLicenseTypeOptions()}, {type = 'input', label = 'Name', default = targetName}, {type = 'input', label = 'Geburtsdatum', description = 'Format: DD.MM.YYYY'}, {type = 'select', label = 'Geschlecht', options = { {value = 'male', label = 'Männlich'}, {value = 'female', label = 'Weiblich'}, {value = 'other', label = 'Divers'} }}, {type = 'date', label = 'Ausstellungsdatum', default = os.date("%Y-%m-%d")}, {type = 'date', label = 'Ablaufdatum', default = os.date("%Y-%m-%d", os.time() + 365*24*60*60)}, {type = 'checkbox', label = 'Foto aufnehmen?'} }) if not input then return end local licenseType = input[1] local licenseData = { name = input[2], birthday = input[3], gender = input[4], issue_date = os.date("%d.%m.%Y", os.time(os.date("!*t", input[5] / 1000))), expire_date = os.date("%d.%m.%Y", os.time(os.date("!*t", input[6] / 1000))), license_type = licenseType } if input[7] then -- Take photo TriggerEvent('license-system:client:openCamera', targetId, licenseData) else TriggerServerEvent('license-system:server:issueManualLicense', targetId, licenseData) end end -- License Reactivation Menu openReactivateLicenseMenu = function(targetId, targetName) debugPrint("Öffne Lizenz-Reaktivierungs-Menü für: " .. targetName) -- Request all licenses including inactive ones TriggerServerEvent('license-system:server:requestAllPlayerLicenses', targetId, true) end -- Spieler-Lizenz-Menü openPlayerLicenseMenu = function(targetId, targetName) debugPrint("=== openPlayerLicenseMenu START ===") debugPrint("Sende Event: requestPlayerLicenses für: " .. targetName .. " (ID: " .. targetId .. ")") TriggerServerEvent('license-system:server:requestPlayerLicenses', targetId) end -- Hauptmenü für Lizenz-System openLicenseMenu = function() debugPrint("Öffne Hauptmenü für Lizenz-System") if not hasPermission() then showNotification(Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) return end nearbyPlayers = getNearbyPlayers(5.0) if #nearbyPlayers == 0 then showNotification(Config.Notifications.no_players_nearby.message, Config.Notifications.no_players_nearby.type) return end local menuOptions = {} for _, player in ipairs(nearbyPlayers) do table.insert(menuOptions, { title = player.name, description = 'Entfernung: ' .. player.distance .. 'm', icon = 'fas fa-user', onSelect = function() openPlayerLicenseMenu(player.id, player.name) end }) end lib.registerContext({ id = 'license_nearby_players', title = 'Spieler in der Nähe (' .. #nearbyPlayers .. ')', options = menuOptions }) lib.showContext('license_nearby_players') end -- Eigene Lizenzen anzeigen local function showMyLicenses() debugPrint("Öffne Menü für eigene Lizenzen") local menuOptions = {} for licenseType, config in pairs(Config.LicenseTypes) do table.insert(menuOptions, { title = config.label, description = 'Deine ' .. config.label .. ' anzeigen', icon = config.icon, onSelect = function() showMyLicense(licenseType) end }) end lib.registerContext({ id = 'my_licenses', title = 'Meine Lizenzen', options = menuOptions }) lib.showContext('my_licenses') end -- EVENT HANDLER: Einzelne Lizenz erhalten RegisterNetEvent('license-system:client:receiveLicense', function(licenseData) debugPrint("=== Event: receiveLicense ===") debugPrint("LicenseData-Typ: " .. type(licenseData)) if licenseData then debugPrint("Lizenz-Daten erhalten: " .. licenseData.license.license_type) showLicense(licenseData) else debugPrint("Keine Lizenz-Daten erhalten") showNotification(Config.Notifications.license_not_found.message, Config.Notifications.license_not_found.type) end end) -- EVENT HANDLER: Eigene Lizenz erhalten RegisterNetEvent('license-system:client:receiveMyLicense', function(licenseData, licenseType) debugPrint("=== Event: receiveMyLicense ===") debugPrint("LicenseType: " .. tostring(licenseType)) debugPrint("LicenseData-Typ: " .. type(licenseData)) if licenseData then debugPrint("Eigene Lizenz-Daten erhalten: " .. licenseData.license.license_type) showLicense(licenseData) else debugPrint("Keine eigene Lizenz gefunden") local config = Config.LicenseTypes[licenseType] local licenseName = config and config.label or licenseType showNotification('Du hast keine ' .. licenseName .. '!', 'error') end end) -- EVENT HANDLER: Alle Spieler-Lizenzen erhalten RegisterNetEvent('license-system:client:receivePlayerLicenses', function(licenses, targetId, targetName) debugPrint("=== Event: receivePlayerLicenses ===") debugPrint("Erhaltene Lizenzen: " .. #licenses) debugPrint("TargetName: " .. tostring(targetName)) local menuOptions = {} if licenses and #licenses > 0 then for _, license in ipairs(licenses) do local licenseConfig = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card', color = '#667eea' } local statusIcon = (license.is_active == 1) and '✅' or '❌' local statusText = (license.is_active == 1) and 'Gültig' or 'Ungültig' local expireText = license.expire_date or 'Unbegrenzt' table.insert(menuOptions, { title = licenseConfig.label .. ' ' .. statusIcon, description = 'Status: ' .. statusText .. ' | Gültig bis: ' .. expireText, icon = licenseConfig.icon, onSelect = function() local licenseData = { license = license, config = licenseConfig } showLicense(licenseData) end, metadata = { {label = 'Status', value = statusText}, {label = 'Ausgestellt', value = license.issue_date or 'Unbekannt'}, {label = 'Gültig bis', value = expireText}, {label = 'Aussteller', value = license.issued_by_name or 'System'} } }) end else table.insert(menuOptions, { title = 'Keine Lizenzen gefunden', description = 'Dieser Spieler hat keine Lizenzen', icon = 'fas fa-exclamation-triangle', disabled = true }) end -- Aktionen hinzufügen table.insert(menuOptions, { title = '─────────────────', disabled = true }) table.insert(menuOptions, { title = 'Neue Lizenz ausstellen', description = 'Eine neue Lizenz für diesen Spieler ausstellen', icon = 'fas fa-plus', onSelect = function() openIssueLicenseMenu(targetId, targetName) end }) table.insert(menuOptions, { title = 'Lizenz manuell ausstellen', description = 'Lizenz mit benutzerdefinierten Daten ausstellen', icon = 'fas fa-edit', onSelect = function() openManualLicenseEntry(targetId, targetName) end }) if licenses and #licenses > 0 then table.insert(menuOptions, { title = 'Lizenz entziehen', description = 'Eine bestehende Lizenz entziehen', icon = 'fas fa-minus', onSelect = function() openRevokeLicenseMenu(targetId, targetName, licenses) end }) end table.insert(menuOptions, { title = 'Lizenz reaktivieren', description = 'Eine inaktive Lizenz wieder aktivieren', icon = 'fas fa-redo', onSelect = function() openReactivateLicenseMenu(targetId, targetName) end }) table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openLicenseMenu() end }) lib.registerContext({ id = 'player_licenses', title = 'Lizenzen: ' .. targetName, options = menuOptions }) lib.showContext('player_licenses') end) -- EVENT HANDLER: Alle Spieler-Lizenzen (inkl. inaktive) erhalten RegisterNetEvent('license-system:client:receiveAllPlayerLicenses', function(licenses, targetId, targetName) debugPrint("=== Event: receiveAllPlayerLicenses ===") debugPrint("Erhaltene Lizenzen: " .. #licenses) local menuOptions = {} if licenses and #licenses > 0 then for _, license in ipairs(licenses) do if license.is_active == 0 then -- Only show inactive licenses local licenseConfig = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card', color = '#667eea' } table.insert(menuOptions, { title = licenseConfig.label .. ' ❌', description = 'Status: Ungültig | Ausgestellt: ' .. (license.issue_date or 'Unbekannt'), icon = licenseConfig.icon, onSelect = function() -- Confirmation dialog lib.registerContext({ id = 'confirm_reactivate_license', title = 'Lizenz reaktivieren bestätigen', options = { { title = 'Spieler: ' .. targetName, disabled = true, icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. licenseConfig.label, disabled = true, icon = licenseConfig.icon }, { title = '─────────────────', disabled = true }, { title = '✅ Bestätigen', description = 'Lizenz jetzt reaktivieren', icon = 'fas fa-check', onSelect = function() debugPrint("Sende Lizenz-Reaktivierung an Server...") TriggerServerEvent('license-system:server:reactivateLicense', targetId, license.license_type) lib.hideContext() end }, { title = '❌ Abbrechen', description = 'Vorgang abbrechen', icon = 'fas fa-times', onSelect = function() openReactivateLicenseMenu(targetId, targetName) end } } }) lib.showContext('confirm_reactivate_license') end }) end end end if #menuOptions == 0 then table.insert(menuOptions, { title = 'Keine inaktiven Lizenzen', description = 'Dieser Spieler hat keine inaktiven Lizenzen', icon = 'fas fa-exclamation-triangle', disabled = true }) end table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openPlayerLicenseMenu(targetId, targetName) end }) lib.registerContext({ id = 'reactivate_license', title = 'Lizenz reaktivieren: ' .. targetName, options = menuOptions }) lib.showContext('reactivate_license') end) -- EVENT HANDLER: Berechtigung erhalten RegisterNetEvent('license-system:client:receivePermission', function(hasAuth, licenseType) debugPrint("=== Event: receivePermission ===") debugPrint("Berechtigung für " .. licenseType .. ": " .. tostring(hasAuth)) if not hasAuth then showNotification(Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) end end) -- EVENT HANDLER: Lizenz erfolgreich ausgestellt RegisterNetEvent('license-system:client:licenseIssued', function(targetId, licenseType) debugPrint("=== Event: licenseIssued ===") debugPrint("Lizenz " .. licenseType .. " für Spieler " .. targetId .. " ausgestellt") -- Menü aktualisieren if lib.getOpenContextMenu() then lib.hideContext() Wait(100) openLicenseMenu() end end) -- EVENT HANDLER: Lizenz erfolgreich entzogen RegisterNetEvent('license-system:client:licenseRevoked', function(targetId, licenseType) debugPrint("=== Event: licenseRevoked ===") debugPrint("Lizenz " .. licenseType .. " für Spieler " .. targetId .. " entzogen") -- Menü aktualisieren if lib.getOpenContextMenu() then lib.hideContext() Wait(100) openLicenseMenu() end end) -- EVENT HANDLER: Lizenz erfolgreich reaktiviert RegisterNetEvent('license-system:client:licenseReactivated', function(targetId, licenseType) debugPrint("=== Event: licenseReactivated ===") debugPrint("Lizenz " .. licenseType .. " für Spieler " .. targetId .. " reaktiviert") -- Menü aktualisieren if lib.getOpenContextMenu() then lib.hideContext() Wait(100) openLicenseMenu() end end) -- EVENT HANDLER: Lizenz anzeigen (von anderen Spielern) RegisterNetEvent('license-system:client:showLicense', function(targetId) debugPrint("Event erhalten: showLicense für ID " .. tostring(targetId)) showPlayerLicense(targetId) end) -- EVENT HANDLER: Eigene Lizenz anzeigen RegisterNetEvent('license-system:client:showMyLicense', function(licenseType) debugPrint("Event erhalten: showMyLicense für Typ " .. tostring(licenseType)) showMyLicense(licenseType) end) -- EVENT HANDLER: Kamera öffnen RegisterNetEvent('license-system:client:openCamera', function(targetId, licenseData) debugPrint("Event erhalten: openCamera für Spieler " .. tostring(targetId)) -- Store the data temporarily currentLicenseData = licenseData currentTargetId = targetId SendNUIMessage({ action = 'openCamera', citizenid = QBCore.Functions.GetPlayerData().citizenid }) end) -- EVENT HANDLER: Menü aktualisieren RegisterNetEvent('license-system:client:refreshMenu', function() debugPrint("Event erhalten: refreshMenu") if lib.getOpenContextMenu() then lib.hideContext() Wait(100) openLicenseMenu() end end) -- NUI CALLBACKS RegisterNUICallback('closeLicense', function(data, cb) debugPrint("NUI Callback: closeLicense") closeLicense() if cb and type(cb) == "function" then cb('ok') end end) RegisterNUICallback('savePhoto', function(data, cb) debugPrint("NUI Callback: savePhoto") if data.photo and currentLicenseData and currentTargetId then -- Add photo to license data currentLicenseData.photo_url = data.photo -- Issue the license with the photo TriggerServerEvent('license-system:server:issueManualLicense', currentTargetId, currentLicenseData) -- Reset temporary data currentLicenseData = nil currentTargetId = nil if cb and type(cb) == "function" then cb('ok') end else debugPrint("^1Fehler: Foto-Daten unvollständig oder keine aktuelle Lizenz-Ausstellung^7") if cb and type(cb) == "function" then cb('error') end end end) RegisterNUICallback('takePicture', function(data, cb) debugPrint("NUI Callback: takePicture") -- Hier könnte eine Kamera-Funktion implementiert werden if cb and type(cb) == "function" then cb('ok') end end) -- COMMANDS RegisterCommand(Config.Commands.license.name, function() debugPrint("Command ausgeführt: " .. Config.Commands.license.name) openLicenseMenu() end, Config.Commands.license.restricted) RegisterCommand(Config.Commands.mylicense.name, function() debugPrint("Command ausgeführt: " .. Config.Commands.mylicense.name) showMyLicenses() end, Config.Commands.mylicense.restricted) -- Zusätzliche Commands für schnellen Zugriff RegisterCommand('ausweis', function() debugPrint("Command ausgeführt: ausweis") showMyLicense('id_card') end, false) RegisterCommand('führerschein', function() debugPrint("Command ausgeführt: führerschein") showMyLicense('drivers_license') end, false) RegisterCommand('waffenschein', function() debugPrint("Command ausgeführt: waffenschein") showMyLicense('weapon_license') end, false) RegisterCommand('pass', function() debugPrint("Command ausgeführt: pass") showMyLicense('passport') end, false) -- KEYBINDS if Config.Keybinds and Config.Keybinds.open_license_menu then RegisterKeyMapping(Config.Commands.license.name, Config.Keybinds.open_license_menu.description, 'keyboard', Config.Keybinds.open_license_menu.key) end if Config.Keybinds and Config.Keybinds.show_my_licenses then RegisterKeyMapping(Config.Commands.mylicense.name, Config.Keybinds.show_my_licenses.description, 'keyboard', Config.Keybinds.show_my_licenses.key) end -- ESC-Taste zum Schließen der Lizenz CreateThread(function() while true do Wait(0) if isLicenseShowing then if IsControlJustPressed(0, 322) then -- ESC-Taste closeLicense() end else Wait(500) end end end) -- CLEANUP UND INITIALISIERUNG AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then if isLicenseShowing then closeLicense() end if lib.getOpenContextMenu() then lib.hideContext() end -- Reset temporary data currentLicenseData = nil currentTargetId = nil debugPrint("License-System Client gestoppt") end end) AddEventHandler('onResourceStart', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Client gestartet (Event-basiert)") -- Warten bis QBCore geladen ist while not QBCore do Wait(100) end debugPrint("QBCore erfolgreich geladen") end end) -- Player laden Event RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() debugPrint("Spieler geladen - License-System bereit") end) -- Job Update Event RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) debugPrint("Job aktualisiert: " .. JobInfo.name) end) -- Initialisierung CreateThread(function() debugPrint("License-System Client Thread gestartet") -- Warten bis Spieler gespawnt ist while not NetworkIsPlayerActive(PlayerId()) do Wait(100) end debugPrint("Spieler ist aktiv - System bereit") end) -- Zusätzliche Utility-Funktionen local function requestLicenseWithTimeout(eventName, targetId, timeout) timeout = timeout or 5000 local requestId = math.random(1000, 9999) pendingRequests[requestId] = { timestamp = GetGameTimer(), timeout = timeout } debugPrint("Sende Request mit Timeout: " .. eventName .. " (ID: " .. requestId .. ")") TriggerServerEvent(eventName, targetId, requestId) -- Timeout-Handler CreateThread(function() Wait(timeout) if pendingRequests[requestId] then pendingRequests[requestId] = nil debugPrint("^1Request Timeout: " .. eventName .. " (ID: " .. requestId .. ")^7") showNotification('Anfrage-Timeout! Versuche es erneut.', 'error') end end) return requestId end -- Erweiterte Error-Handling local function safeExecute(func, errorMessage) local success, error = pcall(func) if not success then debugPrint("^1Fehler: " .. (errorMessage or "Unbekannter Fehler") .. "^7") debugPrint("^1Details: " .. tostring(error) .. "^7") showNotification('Ein Fehler ist aufgetreten!', 'error') end return success end -- Performance-Monitoring local performanceStats = { menuOpens = 0, licenseShows = 0, errors = 0 } CreateThread(function() while true do Wait(60000) -- Jede Minute if Config.Debug then debugPrint("=== Performance Stats ===") debugPrint("Menü-Öffnungen: " .. performanceStats.menuOpens) debugPrint("Lizenz-Anzeigen: " .. performanceStats.licenseShows) debugPrint("Fehler: " .. performanceStats.errors) end end end) -- Stats aktualisieren local originalOpenLicenseMenu = openLicenseMenu openLicenseMenu = function() performanceStats.menuOpens = performanceStats.menuOpens + 1 return originalOpenLicenseMenu() end local originalShowLicense = showLicense showLicense = function(licenseData) performanceStats.licenseShows = performanceStats.licenseShows + 1 return originalShowLicense(licenseData) end -- Erweiterte Fehlerbehandlung für Events local function safeEventHandler(eventName, handler) RegisterNetEvent(eventName, function(...) local success, error = pcall(handler, ...) if not success then debugPrint("^1Fehler in Event " .. eventName .. ": " .. tostring(error) .. "^7") performanceStats.errors = performanceStats.errors + 1 showNotification('Ein Fehler ist aufgetreten!', 'error') end end) end -- Zusätzliche Validierungen local function validateLicenseData(licenseData) if not licenseData then debugPrint("^1Validierung fehlgeschlagen: licenseData ist nil^7") return false end if not licenseData.license then debugPrint("^1Validierung fehlgeschlagen: license-Objekt fehlt^7") return false end if not licenseData.license.license_type then debugPrint("^1Validierung fehlgeschlagen: license_type fehlt^7") return false end return true end -- Erweiterte showLicense Funktion mit Validierung local function showLicenseWithValidation(licenseData) if not validateLicenseData(licenseData) then showNotification('Ungültige Lizenz-Daten!', 'error') return end debugPrint("Zeige Lizenz: " .. licenseData.license.license_type) -- Zusätzliche Daten für NUI vorbereiten local nuitData = { license = licenseData.license, config = licenseData.config or Config.LicenseTypes[licenseData.license.license_type], timestamp = GetGameTimer(), playerData = QBCore.Functions.GetPlayerData() } SendNUIMessage({ action = 'showLicense', data = nuitData }) SetNuiFocus(true, true) isLicenseShowing = true performanceStats.licenseShows = performanceStats.licenseShows + 1 end -- Überschreibe die ursprüngliche showLicense Funktion showLicense = showLicenseWithValidation -- Erweiterte Menü-Funktionen mit Error-Handling local function safeMenuAction(action, errorMessage) return function(...) local success, error = pcall(action, ...) if not success then debugPrint("^1Menü-Fehler: " .. (errorMessage or "Unbekannt") .. "^7") debugPrint("^1Details: " .. tostring(error) .. "^7") performanceStats.errors = performanceStats.errors + 1 showNotification('Menü-Fehler aufgetreten!', 'error') -- Menü schließen bei Fehler if lib.getOpenContextMenu() then lib.hideContext() end end end end -- Cache für Spieler-Daten local playerCache = {} local cacheTimeout = 30000 -- 30 Sekunden local function getCachedPlayerData(playerId) local now = GetGameTimer() local cached = playerCache[playerId] if cached and (now - cached.timestamp) < cacheTimeout then debugPrint("Verwende gecachte Spieler-Daten für ID: " .. playerId) return cached.data end return nil end local function setCachedPlayerData(playerId, data) playerCache[playerId] = { data = data, timestamp = GetGameTimer() } debugPrint("Spieler-Daten gecacht für ID: " .. playerId) end -- Cache-Cleanup CreateThread(function() while true do Wait(60000) -- Jede Minute local now = GetGameTimer() local cleaned = 0 for playerId, cached in pairs(playerCache) do if (now - cached.timestamp) > cacheTimeout then playerCache[playerId] = nil cleaned = cleaned + 1 end end if cleaned > 0 then debugPrint("Cache bereinigt: " .. cleaned .. " Einträge entfernt") end end end) -- Erweiterte Nearby-Players Funktion mit Cache local function getNearbyPlayersWithCache(radius) radius = radius or 5.0 local players = {} local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) for _, playerId in ipairs(GetActivePlayers()) do local targetPed = GetPlayerPed(playerId) if targetPed ~= playerPed then local targetCoords = GetEntityCoords(targetPed) local distance = #(playerCoords - targetCoords) if distance <= radius then local serverId = GetPlayerServerId(playerId) local playerName = GetPlayerName(playerId) -- Cache-Daten verwenden wenn verfügbar local cachedData = getCachedPlayerData(serverId) local playerData = cachedData or { id = serverId, name = playerName, ped = targetPed } playerData.distance = math.floor(distance * 100) / 100 -- Daten cachen wenn nicht bereits gecacht if not cachedData then setCachedPlayerData(serverId, playerData) end table.insert(players, playerData) end end end -- Nach Entfernung sortieren table.sort(players, function(a, b) return a.distance < b.distance end) debugPrint("Gefundene Spieler in der Nähe: " .. #players) return players end -- Überschreibe die ursprüngliche getNearbyPlayers Funktion getNearbyPlayers = getNearbyPlayersWithCache -- Erweiterte Notification-Funktion local function showNotificationWithSound(message, type, sound) QBCore.Functions.Notify(message, type or 'primary') if sound and Config.Sounds and Config.Sounds[sound] then PlaySoundFrontend(-1, Config.Sounds[sound].name, Config.Sounds[sound].set, true) end end -- Erweiterte Event-Handler mit besserer Fehlerbehandlung RegisterNetEvent('license-system:client:receiveLicenseWithValidation', function(licenseData) debugPrint("=== Event: receiveLicenseWithValidation ===") if not validateLicenseData(licenseData) then showNotificationWithSound('Ungültige Lizenz-Daten erhalten!', 'error', 'error') return end debugPrint("Gültige Lizenz-Daten erhalten: " .. licenseData.license.license_type) showLicense(licenseData) end) -- Erweiterte Lizenz-Anzeige mit Animationen local function showLicenseWithAnimation(licenseData) if not validateLicenseData(licenseData) then showNotification('Ungültige Lizenz-Daten!', 'error') return end -- Animation starten local playerPed = PlayerPedId() -- Lizenz-Animation (falls gewünscht) if Config.Animations and Config.Animations.show_license then local anim = Config.Animations.show_license RequestAnimDict(anim.dict) while not HasAnimDictLoaded(anim.dict) do Wait(10) end TaskPlayAnim(playerPed, anim.dict, anim.name, 8.0, -8.0, -1, anim.flag or 49, 0, false, false, false) end -- Lizenz anzeigen showLicense(licenseData) -- Animation nach kurzer Zeit stoppen if Config.Animations and Config.Animations.show_license then CreateThread(function() Wait(2000) ClearPedTasks(playerPed) end) end end -- Erweiterte Menü-Navigation local menuHistory = {} local function pushMenuHistory(menuId) table.insert(menuHistory, menuId) debugPrint("Menü-Historie: " .. menuId .. " hinzugefügt") end local function popMenuHistory() if #menuHistory > 0 then local lastMenu = table.remove(menuHistory) debugPrint("Menü-Historie: " .. lastMenu .. " entfernt") return lastMenu end return nil end local function clearMenuHistory() menuHistory = {} debugPrint("Menü-Historie geleert") end -- Erweiterte Cleanup-Funktion local function cleanup() debugPrint("Führe erweiterte Cleanup-Routine aus...") -- NUI schließen if isLicenseShowing then closeLicense() end -- Menüs schließen if lib.getOpenContextMenu() then lib.hideContext() end -- Cache leeren playerCache = {} -- Historie leeren clearMenuHistory() -- Pending Requests leeren pendingRequests = {} -- Temporary data leeren currentLicenseData = nil currentTargetId = nil -- Animationen stoppen local playerPed = PlayerPedId() if DoesEntityExist(playerPed) then ClearPedTasks(playerPed) end debugPrint("Cleanup abgeschlossen") end -- Erweiterte Resource-Stop Handler AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then cleanup() debugPrint("License-System Client gestoppt (erweitert)") end end) -- Erweiterte Resource-Start Handler AddEventHandler('onResourceStart', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Client gestartet (erweitert)") -- Initialisierung CreateThread(function() -- Warten bis QBCore geladen ist while not QBCore do Wait(100) end debugPrint("QBCore erfolgreich geladen (erweitert)") -- Zusätzliche Initialisierung Wait(1000) -- Performance-Stats zurücksetzen performanceStats = { menuOpens = 0, licenseShows = 0, errors = 0 } debugPrint("Erweiterte Initialisierung abgeschlossen") end) end end) -- Erweiterte Debug-Funktionen local function debugPlayerInfo() if not Config.Debug then return end local PlayerData = QBCore.Functions.GetPlayerData() debugPrint("=== PLAYER DEBUG INFO ===") debugPrint("Name: " .. (PlayerData.charinfo and PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname or "Unbekannt")) debugPrint("Job: " .. (PlayerData.job and PlayerData.job.name or "Unbekannt")) debugPrint("CitizenID: " .. (PlayerData.citizenid or "Unbekannt")) debugPrint("Server ID: " .. GetPlayerServerId(PlayerId())) debugPrint("========================") end -- Debug-Command RegisterCommand('licensedebug', function() if not Config.Debug then showNotification('Debug-Modus ist deaktiviert!', 'error') return end debugPlayerInfo() debugPrint("=== SYSTEM DEBUG INFO ===") debugPrint("Nearby Players: " .. #nearbyPlayers) debugPrint("License Showing: " .. tostring(isLicenseShowing)) debugPrint("Menu Open: " .. tostring(lib.getOpenContextMenu() ~= nil)) debugPrint("Cached Players: " .. #playerCache) debugPrint("Menu History: " .. #menuHistory) debugPrint("Pending Requests: " .. #pendingRequests) debugPrint("========================") showNotification('Debug-Informationen in der Konsole ausgegeben!', 'success') end, false) -- Finaler Debug-Output debugPrint("License-System Client vollständig geladen - Alle Funktionen verfügbar")