From b7d3d90e2829693046a96f96f0c9cd763f9d89ae Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 08:39:38 +0200 Subject: [PATCH] Update main.lua --- .../[tools]/nordi_license/client/main.lua | 660 +++++++++++++++--- 1 file changed, 558 insertions(+), 102 deletions(-) diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua index ae09941b7..5bfad1e16 100644 --- a/resources/[tools]/nordi_license/client/main.lua +++ b/resources/[tools]/nordi_license/client/main.lua @@ -109,62 +109,87 @@ local function showMyLicense(licenseType) TriggerServerEvent('license-system:server:requestMyLicense', licenseType) end --- Spieler-Lizenz-Menü -local function openPlayerLicenseMenu(targetId, targetName) - debugPrint("=== openPlayerLicenseMenu START ===") - debugPrint("Sende Event: requestPlayerLicenses für: " .. targetName .. " (ID: " .. targetId .. ")") - - TriggerServerEvent('license-system:server:requestPlayerLicenses', targetId) -end +-- FORWARD DECLARATIONS (Funktionen die später definiert werden) +local confirmIssueLicense +local openIssueLicenseMenu +local openDriversLicenseClassMenu +local openRevokeLicenseMenu +local openPlayerLicenseMenu +local openLicenseMenu --- 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} - } - }) +-- 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 - table.insert(menuOptions, { - title = '← Zurück', - icon = 'fas fa-arrow-left', - onSelect = function() - openPlayerLicenseMenu(targetId, targetName) - 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 = 'issue_license', - title = 'Lizenz ausstellen: ' .. targetName, - options = menuOptions + 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('issue_license') + lib.showContext('confirm_issue_license') end -- Führerschein-Klassen Menü -local function openDriversLicenseClassMenu(targetId, targetName, licenseType) +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() @@ -250,68 +275,53 @@ local function openDriversLicenseClassMenu(targetId, targetName, licenseType) 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' +-- Lizenz ausstellen Menü +openIssueLicenseMenu = function(targetId, targetName) + debugPrint("Öffne Lizenz-Ausstellungs-Menü für: " .. targetName) - debugPrint("Bestätige Lizenz-Ausstellung: " .. licenseType .. " für " .. targetName) + local menuOptions = {} - 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) + 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.showContext('confirm_issue_license') + lib.registerContext({ + id = 'issue_license', + title = 'Lizenz ausstellen: ' .. targetName, + options = menuOptions + }) + + lib.showContext('issue_license') end -- Lizenz entziehen Menü -local function openRevokeLicenseMenu(targetId, targetName, licenses) +openRevokeLicenseMenu = function(targetId, targetName, licenses) debugPrint("Öffne Lizenz-Entziehungs-Menü für: " .. targetName) local menuOptions = {} @@ -399,8 +409,16 @@ local function openRevokeLicenseMenu(targetId, targetName, licenses) lib.showContext('revoke_license') 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 -local function openLicenseMenu() +openLicenseMenu = function() debugPrint("Öffne Hauptmenü für Lizenz-System") if not hasPermission() then @@ -721,11 +739,11 @@ RegisterCommand('pass', function() end, false) -- KEYBINDS -if Config.Keybinds.open_license_menu then +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.show_my_licenses then +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 @@ -839,7 +857,7 @@ local performanceStats = { } CreateThread(function() - while true do + while true do Wait(60000) -- Jede Minute if Config.Debug then @@ -864,3 +882,441 @@ showLicense = function(licenseData) 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 = {} + + -- 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) + +-- Erweiterte Keybind-Behandlung +CreateThread(function() + while true do + Wait(0) + + -- ESC-Taste für Lizenz schließen + if isLicenseShowing then + if IsControlJustPressed(0, 322) then -- ESC + closeLicense() + end + end + + -- Zusätzliche Hotkeys (falls konfiguriert) + if Config.Keybinds and Config.Keybinds.emergency_close then + if IsControlJustPressed(0, Config.Keybinds.emergency_close.control) then + cleanup() + showNotification('Notfall-Schließung aktiviert!', 'info') + end + end + + -- Performance-Optimierung + if not isLicenseShowing and not lib.getOpenContextMenu() then + Wait(500) + end + end +end) + +-- Erweiterte Netzwerk-Events mit Retry-Mechanismus +local function sendEventWithRetry(eventName, data, maxRetries) + maxRetries = maxRetries or 3 + local retries = 0 + + local function attemptSend() + retries = retries + 1 + debugPrint("Sende Event: " .. eventName .. " (Versuch " .. retries .. "/" .. maxRetries .. ")") + + TriggerServerEvent(eventName, table.unpack(data or {})) + + -- Timeout für Response + CreateThread(function() + Wait(5000) -- 5 Sekunden Timeout + + if retries < maxRetries then + debugPrint("^3Timeout für Event " .. eventName .. " - Wiederhole...^7") + attemptSend() + else + debugPrint("^1Maximale Wiederholungen für Event " .. eventName .. " erreicht^7") + showNotification('Netzwerk-Fehler! Bitte versuche es später erneut.', 'error') + end + end) + end + + attemptSend() +end + +-- Erweiterte Export-Funktionen für andere Resources +exports('hasLicense', function(licenseType) + -- Diese Funktion kann von anderen Resources verwendet werden + local PlayerData = QBCore.Functions.GetPlayerData() + if not PlayerData or not PlayerData.citizenid then return false end + + -- Hier würde normalerweise eine Server-Anfrage gemacht werden + -- Für jetzt geben wir false zurück + return false +end) + +exports('showPlayerLicense', function(targetId, licenseType) + -- Export für andere Resources um Lizenzen anzuzeigen + if licenseType then + TriggerServerEvent('license-system:server:requestSpecificLicense', targetId, licenseType) + else + showPlayerLicense(targetId) + end +end) + +exports('openLicenseMenu', function() + -- Export für andere Resources um das Lizenz-Menü zu öffnen + openLicenseMenu() +end) + +-- Finaler Debug-Output +debugPrint("License-System Client vollständig geladen - Alle Funktionen verfügbar") +