local QBCore = exports['qb-core']:GetCoreObject() -- Lokale Variablen local isMenuOpen = false local currentTarget = nil local nearbyPlayers = {} local isLicenseShowing = false -- Hilfsfunktionen local function debugPrint(message) if Config.Debug then print("^3[License-System Client] " .. message .. "^7") end end local function safeCallback(cb, ...) if cb and type(cb) == "function" then cb(...) return true else debugPrint("^1FEHLER: Callback ist keine Funktion! Typ: " .. type(cb) .. "^7") return false 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("Rufe Server-Callback auf für Spieler: " .. tostring(targetId)) QBCore.Functions.TriggerCallback('license-system:server:getLicense', function(licenseData) debugPrint("Callback-Antwort erhalten für Spieler-Lizenz") 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, targetId) end -- Eigene Lizenz anzeigen local function showMyLicense(licenseType) debugPrint("Rufe Server-Callback auf für eigene Lizenz: " .. tostring(licenseType)) QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) debugPrint("Eigene Lizenz Callback-Antwort erhalten") 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, licenseType) end -- Spieler-Lizenz-Menü local function openPlayerLicenseMenu(targetId, targetName) debugPrint("Öffne Lizenz-Menü für Spieler: " .. targetName .. " (ID: " .. targetId .. ")") QBCore.Functions.TriggerCallback('license-system:server:getPlayerLicenses', function(licenses) debugPrint("Spieler-Lizenzen Callback-Antwort erhalten, Anzahl: " .. (licenses and #licenses or 0)) 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 }) 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 = '← 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, targetId) end -- Lizenz ausstellen Menü local function openIssueLicenseMenu(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 -- Führerschein-Klassen Menü local function openDriversLicenseClassMenu(targetId, targetName, licenseType) local config = Config.LicenseTypes[licenseType] 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-Ausstellung bestätigen local function confirmIssueLicense(targetId, targetName, licenseType, classes) local config = Config.LicenseTypes[licenseType] 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 -- Lizenz entziehen Menü local function openRevokeLicenseMenu(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 -- Hauptmenü für Lizenz-System local function openLicenseMenu() 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 -- Events RegisterNetEvent('license-system:client:showLicense', function(targetId) debugPrint("Event erhalten: showLicense für ID " .. tostring(targetId)) showPlayerLicense(targetId) end) RegisterNetEvent('license-system:client:showMyLicense', function(licenseType) debugPrint("Event erhalten: showMyLicense für Typ " .. tostring(licenseType)) showMyLicense(licenseType) end) RegisterNetEvent('license-system:client:openCamera', function() debugPrint("Event erhalten: openCamera") SendNUIMessage({ action = 'openCamera' }) end) 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 data.citizenid then TriggerServerEvent('license-system:server:savePhoto', data.citizenid, data.photo) if cb and type(cb) == "function" then cb('ok') end else debugPrint("^1Fehler: Foto-Daten unvollständig^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.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.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 debugPrint("License-System Client gestoppt") end end) AddEventHandler('onResourceStart', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Client gestartet") -- 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)