diff --git a/resources/[inventory]/inventory_images/images/greencard.png b/resources/[inventory]/inventory_images/images/greencard.png new file mode 100644 index 000000000..820d25bf0 Binary files /dev/null and b/resources/[inventory]/inventory_images/images/greencard.png differ diff --git a/resources/[inventory]/tgiann-inventory/configs/configMetadata.lua b/resources/[inventory]/tgiann-inventory/configs/configMetadata.lua index 2471c9687..644ea4082 100644 --- a/resources/[inventory]/tgiann-inventory/configs/configMetadata.lua +++ b/resources/[inventory]/tgiann-inventory/configs/configMetadata.lua @@ -8,6 +8,15 @@ config.metadata = { { metadata = "nationality", value = "Nationality:" }, { metadata = "quality", value = "Quality:" }, }, + greencard = { + { metadata = "citizenid", value = "CSN:" }, + { metadata = "firstname", value = "First Name:" }, + { metadata = "lastname", value = "Last Name:" }, + { metadata = "birthdate", value = "Birth Date:" }, + { metadata = "gender", value = "Gender:" }, + { metadata = "nationality", value = "Nationality:" }, + { metadata = "quality", value = "Quality:" }, + }, driver_license = { { metadata = "firstname", value = "First Name:" }, { metadata = "lastname", value = "Last Name:" }, diff --git a/resources/[inventory]/tgiann-inventory/items/items.lua b/resources/[inventory]/tgiann-inventory/items/items.lua index 22f07c456..c30ee9d33 100644 --- a/resources/[inventory]/tgiann-inventory/items/items.lua +++ b/resources/[inventory]/tgiann-inventory/items/items.lua @@ -10915,5 +10915,18 @@ itemsData = { image = 'weaponlicense.png', name = 'weaponlicense', }, + greencard= { + shouldClose = true, + type = 'item', + description = 'Deine Zeitlich begrenzte Genehmigung für deine LS aufenthalt.', + weight = 10, + label = 'Greencard', + unique = true, + useable = true, + image = 'greencard.png', + name = 'greencard', + }, + + } diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua deleted file mode 100644 index 1d649e49f..000000000 --- a/resources/[tools]/nordi_license/client/main.lua +++ /dev/null @@ -1,1471 +0,0 @@ -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 (Vollständig manuell) -openManualLicenseEntry = function(targetId, targetName) - debugPrint("Öffne manuelle Lizenz-Eingabe für: " .. targetName) - - -- Aktuelles Datum für Standardwerte - local currentDate = os.date("%d.%m.%Y") - - -- Berechne Standardablaufdatum (1 Jahr später) - local day, month, year = currentDate:match("(%d+)%.(%d+)%.(%d+)") - local defaultExpireDate = day .. "." .. month .. "." .. (tonumber(year) + 1) - - 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 = 'input', label = 'Ausstellungsdatum', description = 'Format: DD.MM.YYYY', default = currentDate}, - {type = 'input', label = 'Ablaufdatum', description = 'Format: DD.MM.YYYY', default = defaultExpireDate}, - {type = 'input', label = 'Klassen (kommagetrennt)', description = 'z.B. A,B,C', default = ''}, - {type = 'input', label = 'Bemerkungen', default = ''}, - {type = 'checkbox', label = 'Foto aufnehmen?'} - }) - - if not input then return end - - local licenseType = input[1] - - -- Klassen verarbeiten - local classes = {} - if input[7] and input[7] ~= "" then - for class in string.gmatch(input[7], '([^,]+)') do - table.insert(classes, class:match("^%s*(.-)%s*$")) -- Leerzeichen entfernen - end - end - - local licenseData = { - name = input[2], - birthday = input[3], - gender = input[4], - issue_date = input[5], - expire_date = input[6], - classes = classes, - notes = input[8], - license_type = licenseType - } - - if input[9] then -- Foto aufnehmen - 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") - diff --git a/resources/[tools]/nordi_license/config.lua b/resources/[tools]/nordi_license/config.lua deleted file mode 100644 index d9a05f41f..000000000 --- a/resources/[tools]/nordi_license/config.lua +++ /dev/null @@ -1,317 +0,0 @@ -Config = {} - --- Allgemeine Einstellungen -Config.Debug = true -Config.UseBackgroundImages = true -Config.MaxLicenseAge = 365 -- Tage bis Ablauf -Config.RenewalDays = 30 -- Tage vor Ablauf für Verlängerung - --- Berechtigte Jobs -Config.AuthorizedJobs = { - ['police'] = true, - ['sheriff'] = true, - ['government'] = true, - ['judge'] = true, - ['lawyer'] = true, - ['ambulance'] = true, -- Für medizinische Lizenzen - ['mechanic'] = true -- Für Fahrzeug-Lizenzen -} - --- Jobs that can reactivate specific license types -Config.ReactivationPermissions = { - ['police'] = {'weapon_license', 'drivers_license'}, - ['admin'] = {'id_card', 'passport', 'business_license'}, - ['ambulance'] = {'medical_license'}, - ['driving_school'] = {'drivers_license'}, - ['harbor'] = {'boat_license'}, - ['airport'] = {'pilot_license'} -} - - --- Lizenz-Typen -Config.LicenseTypes = { - ['id_card'] = { - label = 'Personalausweis', - icon = 'fas fa-id-card', - color = '#667eea', - price = 50, - required_items = {}, - can_expire = true, - validity_days = 3650, -- 10 Jahre - required_job = nil, - description = 'Offizieller Personalausweis' - }, - ['drivers_license'] = { - label = 'Führerschein', - icon = 'fas fa-car', - color = '#f093fb', - price = 500, - required_items = {'driving_test_certificate'}, - can_expire = true, - validity_days = 5475, -- 15 Jahre - required_job = 'driving_school', - description = 'Berechtigung zum Führen von Kraftfahrzeugen', - classes = { - 'A', 'A1', 'A2', 'B', 'BE', 'C', 'CE', 'D', 'DE' - } - }, - ['weapon_license'] = { - label = 'Waffenschein', - icon = 'fas fa-crosshairs', - color = '#4facfe', - price = 2500, - required_items = {'weapon_course_certificate', 'psychological_evaluation'}, - can_expire = true, - validity_days = 1095, -- 3 Jahre - required_job = 'police', - description = 'Berechtigung zum Führen von Schusswaffen', - restrictions = { - 'Nur für registrierte Waffen', - 'Regelmäßige Überprüfung erforderlich', - 'Nicht übertragbar' - } - }, - ['passport'] = { - label = 'Reisepass', - icon = 'fas fa-passport', - color = '#43e97b', - price = 150, - required_items = {'birth_certificate', 'id_card'}, - can_expire = true, - validity_days = 3650, -- 10 Jahre - required_job = 'government', - description = 'Internationales Reisedokument' - }, - ['business_license'] = { - label = 'Gewerbeschein', - icon = 'fas fa-briefcase', - color = '#fa709a', - price = 1000, - required_items = {'business_plan', 'tax_certificate'}, - can_expire = true, - validity_days = 1825, -- 5 Jahre - required_job = 'government', - description = 'Berechtigung zur Ausübung eines Gewerbes' - }, - ['pilot_license'] = { - label = 'Pilotenlizenz', - icon = 'fas fa-plane', - color = '#667eea', - price = 5000, - required_items = {'flight_hours_log', 'medical_certificate'}, - can_expire = true, - validity_days = 730, -- 2 Jahre - required_job = 'airport', - description = 'Berechtigung zum Führen von Luftfahrzeugen' - }, - ['boat_license'] = { - label = 'Bootsführerschein', - icon = 'fas fa-ship', - color = '#00f2fe', - price = 800, - required_items = {'boat_course_certificate'}, - can_expire = true, - validity_days = 1825, -- 5 Jahre - required_job = 'harbor', - description = 'Berechtigung zum Führen von Wasserfahrzeugen' - }, - ['medical_license'] = { - label = 'Approbation', - icon = 'fas fa-user-md', - color = '#ff6b6b', - price = 0, -- Kostenlos für Ärzte - required_items = {'medical_degree', 'medical_exam'}, - can_expire = false, - validity_days = nil, - required_job = 'ambulance', - description = 'Berechtigung zur Ausübung der Heilkunde' - }, - ['hunting_license'] = { - label = 'Jagdschein', - icon = 'fas fa-crosshairs', - color = '#8b5a3c', - price = 300, - required_items = {'hunting_course_certificate'}, - can_expire = true, - validity_days = 1095, -- 3 Jahre - required_job = 'police', - description = 'Berechtigung zur Ausübung der Jagd' - }, - ['fishing_license'] = { - label = 'Angelschein', - icon = 'fas fa-fish', - color = '#4ecdc4', - price = 50, - required_items = {}, - can_expire = true, - validity_days = 365, -- 1 Jahr - required_job = 'police', - description = 'Berechtigung zum Angeln in öffentlichen Gewässern' - } -} - --- Standorte für Lizenz-Ausgabe -Config.LicenseLocations = { - ['city_hall'] = { - label = 'Rathaus', - coords = vector3(-544.85, -204.13, 38.22), - blip = { - sprite = 419, - color = 2, - scale = 0.8 - }, - available_licenses = { - 'id_card', 'passport', 'business_license' - }, - ped = { - model = 'a_m_m_business_01', - coords = vector4(-544.9543, -204.8450, 37.2151, 219.1676) - } - }, - ['driving_school'] = { - label = 'Fahrschule', - coords = vector3(-829.22, -1209.58, 7.33), - blip = { - sprite = 225, - color = 46, - scale = 0.8 - }, - available_licenses = { - 'drivers_license' - }, - ped = { - model = 'a_m_y_business_02', - coords = vector4(-829.22, -1209.58, 6.33, 90.0) - } - }, - ['police_station'] = { - label = 'Polizeiwache', - coords = vector3(441.07, -979.76, 30.69), - blip = { - sprite = 60, - color = 29, - scale = 0.8 - }, - available_licenses = { - 'weapon_license' - }, - ped = { - model = 's_m_y_cop_01', - coords = vector4(441.07, -979.76, 29.69, 270.0) - } - }, - ['hospital'] = { - label = 'Krankenhaus', - coords = vector3(307.7, -1433.4, 29.9), - blip = { - sprite = 61, - color = 1, - scale = 0.8 - }, - available_licenses = { - 'medical_license' - }, - ped = { - model = 's_m_m_doctor_01', - coords = vector4(307.7, -1433.4, 28.9, 180.0) - } - } -} - --- Kommandos -Config.Commands = { - ['license'] = { - name = 'lizenz', - help = 'Lizenz-System öffnen', - restricted = true -- Nur für berechtigte Jobs - }, - ['mylicense'] = { - name = 'meinelizenz', - help = 'Eigene Lizenzen anzeigen', - restricted = false -- Für alle Spieler - }, - ['givelicense'] = { - name = 'givelicense', - help = 'Lizenz an Spieler vergeben', - restricted = true, - admin_only = true - }, - ['revokelicense'] = { - name = 'revokelicense', - help = 'Lizenz entziehen', - restricted = true, - admin_only = false - } -} - --- Keybinds -Config.Keybinds = { - ['open_license_menu'] = { - key = 'F6', - command = 'lizenz', - description = 'Lizenz-System öffnen' - }, - ['show_my_licenses'] = { - key = 'F7', - command = 'meinelizenz', - description = 'Meine Lizenzen anzeigen' - } -} - --- Benachrichtigungen -Config.Notifications = { - ['no_permission'] = { - message = 'Du hast keine Berechtigung!', - type = 'error' - }, - ['no_players_nearby'] = { - message = 'Keine Spieler in der Nähe!', - type = 'error' - }, - ['license_not_found'] = { - message = 'Keine Lizenz gefunden!', - type = 'error' - }, - ['license_expired'] = { - message = 'Diese Lizenz ist abgelaufen!', - type = 'warning' - }, - ['license_expires_soon'] = { - message = 'Diese Lizenz läuft bald ab!', - type = 'warning' - }, - ['license_granted'] = { - message = 'Lizenz erfolgreich ausgestellt!', - type = 'success' - }, - ['license_revoked'] = { - message = 'Lizenz wurde entzogen!', - type = 'info' - }, - ['photo_saved'] = { - message = 'Foto gespeichert!', - type = 'success' - }, - ['insufficient_funds'] = { - message = 'Nicht genügend Geld!', - type = 'error' - }, - ['missing_items'] = { - message = 'Benötigte Gegenstände fehlen!', - type = 'error' - } -} - --- Sounds -Config.Sounds = { - ['card_flip'] = 'sounds/card_flip.mp3', - ['camera_shutter'] = 'sounds/camera_shutter.mp3', - ['notification'] = 'sounds/notification.mp3' -} - --- Datenbank-Einstellungen -Config.Database = { - ['table_name'] = 'player_licenses', - ['auto_cleanup'] = true, -- Alte Lizenzen automatisch löschen - ['cleanup_days'] = 365 -- Nach wie vielen Tagen löschen -} diff --git a/resources/[tools]/nordi_license/fxmanifest.lua b/resources/[tools]/nordi_license/fxmanifest.lua deleted file mode 100644 index 2ddcd9f06..000000000 --- a/resources/[tools]/nordi_license/fxmanifest.lua +++ /dev/null @@ -1,35 +0,0 @@ -fx_version 'cerulean' -game 'gta5' - -author 'YourName' -description 'License System for QBCore' -version '1.0.0' - -shared_scripts { - '@ox_lib/init.lua', - 'config.lua' -} - -client_scripts { - 'client/main.lua' -} - -server_scripts { - '@oxmysql/lib/MySQL.lua', - 'server/main.lua' -} - -ui_page 'html/index.html' - -files { - 'html/index.html', - 'html/style.css', - 'html/script.js', - 'html/images/*.png' -} - -dependencies { - 'qb-core', - 'ox_lib', - 'oxmysql' -} diff --git a/resources/[tools]/nordi_license/html/images/drivers_license.png b/resources/[tools]/nordi_license/html/images/drivers_license.png deleted file mode 100644 index 77dff3b18..000000000 Binary files a/resources/[tools]/nordi_license/html/images/drivers_license.png and /dev/null differ diff --git a/resources/[tools]/nordi_license/html/images/government_seal.png b/resources/[tools]/nordi_license/html/images/government_seal.png deleted file mode 100644 index 993072d3e..000000000 Binary files a/resources/[tools]/nordi_license/html/images/government_seal.png and /dev/null differ diff --git a/resources/[tools]/nordi_license/html/images/id_card.png b/resources/[tools]/nordi_license/html/images/id_card.png deleted file mode 100644 index 6118c3441..000000000 Binary files a/resources/[tools]/nordi_license/html/images/id_card.png and /dev/null differ diff --git a/resources/[tools]/nordi_license/html/images/watermark.png b/resources/[tools]/nordi_license/html/images/watermark.png deleted file mode 100644 index d304a430e..000000000 Binary files a/resources/[tools]/nordi_license/html/images/watermark.png and /dev/null differ diff --git a/resources/[tools]/nordi_license/html/images/weapon_license.png b/resources/[tools]/nordi_license/html/images/weapon_license.png deleted file mode 100644 index 6afad08b6..000000000 Binary files a/resources/[tools]/nordi_license/html/images/weapon_license.png and /dev/null differ diff --git a/resources/[tools]/nordi_license/html/index.html b/resources/[tools]/nordi_license/html/index.html deleted file mode 100644 index bc0d9109e..000000000 --- a/resources/[tools]/nordi_license/html/index.html +++ /dev/null @@ -1,274 +0,0 @@ - - - - - - License System - - - - - - - - - - - - - - - - - -
- -
- - - - - - - - - - diff --git a/resources/[tools]/nordi_license/html/script.js b/resources/[tools]/nordi_license/html/script.js deleted file mode 100644 index 67a71241b..000000000 --- a/resources/[tools]/nordi_license/html/script.js +++ /dev/null @@ -1,341 +0,0 @@ -let currentLicense = null; -let isCardFlipped = false; -let cameraStream = null; - -// Event Listener für Nachrichten von FiveM -window.addEventListener('message', function(event) { - const data = event.data; - - switch(data.action) { - case 'showLicense': - showLicense(data.data); - break; - case 'hideLicense': - closeLicense(); - break; - case 'openCamera': - openCamera(); - break; - } -}); - -// In der showLicense Funktion in script.js -function showLicense(data) { - currentLicense = data; - const container = document.getElementById('license-container'); - const card = document.getElementById('license-card'); - - // Loading anzeigen - showLoading(); - - setTimeout(() => { - // Lizenztyp-spezifische Klasse hinzufügen - card.className = 'license-card ' + data.license.license_type; - - // Header befüllen - document.getElementById('license-title').textContent = data.config.label; - document.getElementById('license-icon').className = 'license-icon ' + data.config.icon; - - // Persönliche Daten - document.getElementById('license-name').textContent = data.license.name || 'N/A'; - document.getElementById('license-birthday').textContent = formatDate(data.license.birthday) || 'N/A'; - document.getElementById('license-gender').textContent = formatGender(data.license.gender) || 'N/A'; - - // Dokument-Informationen - document.getElementById('license-issue').textContent = formatDate(data.license.issue_date) || 'N/A'; - document.getElementById('license-expire').textContent = formatDate(data.license.expire_date) || 'N/A'; - document.getElementById('license-id').textContent = '#' + (data.license.id || '000000').toString().padStart(6, '0'); - document.getElementById('license-issuer').textContent = data.license.issued_by_name || 'Behörde'; - - // Foto anzeigen - displayPlayerPhoto(data.license); - - // Klassen anzeigen (nur bei Führerschein) - displayLicenseClasses(data.license); - - // Status und Gültigkeit - displayLicenseStatus(data.license); - displayValidityIndicator(data.license); - - // Rückseite vorbereiten - prepareBackSide(data.license); - - // Container anzeigen - hideLoading(); - container.classList.remove('hidden'); - - // Sound-Effekt - playSound('card-flip-sound'); - - // Notification - showNotification('Lizenz geladen', 'success'); - }, 500); -} - -// Erweitere die prepareBackSide Funktion um Bemerkungen anzuzeigen -function prepareBackSide(license) { - const classesGrid = document.getElementById('classes-grid'); - const restrictionsList = document.getElementById('restrictions-list'); - const notesText = document.getElementById('notes-text'); - - // Klassen-Grid für Führerschein - if (license.license_type === 'drivers_license' && license.classes) { - try { - const classes = Array.isArray(license.classes) ? license.classes : JSON.parse(license.classes); - classesGrid.innerHTML = ''; - - const classDescriptions = { - 'A': 'Motorräder', - 'A1': 'Leichte Motorräder', - 'A2': 'Mittlere Motorräder', - 'B': 'PKW', - 'BE': 'PKW mit Anhänger', - 'C': 'LKW', - 'CE': 'LKW mit Anhänger', - 'D': 'Bus', - 'DE': 'Bus mit Anhänger' - }; - - if (classes && classes.length > 0) { - classes.forEach(cls => { - const classItem = document.createElement('div'); - classItem.className = 'class-item'; - classItem.innerHTML = ` -
${cls}
-
${classDescriptions[cls] || 'Unbekannt'}
- `; - classesGrid.appendChild(classItem); - }); - } else { - classesGrid.innerHTML = '

Keine Klassen verfügbar

'; - } - } catch (e) { - classesGrid.innerHTML = '

Keine Klassen verfügbar

'; - } - } else { - classesGrid.innerHTML = '

Nicht zutreffend

'; - } - - // Beschränkungen (Beispiel) - restrictionsList.innerHTML = '
  • Keine besonderen Beschränkungen
  • '; - - // Bemerkungen - notesText.textContent = license.notes || 'Keine besonderen Bemerkungen'; -} - - -// Karte drehen -function flipCard() { - const frontSide = document.querySelector('.license-content, .license-footer'); - const backSide = document.getElementById('license-back'); - - isCardFlipped = !isCardFlipped; - - if (isCardFlipped) { - // Zur Rückseite - document.querySelector('.license-content').classList.add('hidden'); - document.querySelector('.license-footer').classList.add('hidden'); - backSide.classList.remove('hidden'); - } else { - // Zur Vorderseite - document.querySelector('.license-content').classList.remove('hidden'); - document.querySelector('.license-footer').classList.remove('hidden'); - backSide.classList.add('hidden'); - } - - playSound('card-flip-sound'); -} - -// Kamera öffnen -async function openCamera() { - const container = document.getElementById('camera-container'); - const video = document.getElementById('camera-video'); - - try { - cameraStream = await navigator.mediaDevices.getUserMedia({ - video: { - width: 640, - height: 480, - facingMode: 'user' - } - }); - - video.srcObject = cameraStream; - container.classList.remove('hidden'); - - showNotification('Kamera geöffnet', 'info'); - } catch (err) { - console.error('Kamera-Zugriff fehlgeschlagen:', err); - showNotification('Kamera-Zugriff fehlgeschlagen', 'error'); - } -} - -// Foto aufnehmen -function takePhoto() { - const video = document.getElementById('camera-video'); - const canvas = document.getElementById('camera-canvas'); - const ctx = canvas.getContext('2d'); - - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - ctx.drawImage(video, 0, 0); - - const photoData = canvas.toDataURL('image/jpeg', 0.8); - - // Sound-Effekt - playSound('camera-shutter-sound'); - - // An FiveM senden - fetch(`https://${GetParentResourceName()}/savePhoto`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - photo: photoData, - citizenid: currentLicense?.license?.citizenid - }) - }).then(() => { - showNotification('Foto gespeichert', 'success'); - closeCamera(); - }).catch(err => { - console.error('Fehler beim Speichern:', err); - showNotification('Fehler beim Speichern', 'error'); - }); -} - -// Kamera schließen -function closeCamera() { - const container = document.getElementById('camera-container'); - - if (cameraStream) { - cameraStream.getTracks().forEach(track => track.stop()); - cameraStream = null; - } - - container.classList.add('hidden'); -} - -// Lizenz schließen -function closeLicense() { - const container = document.getElementById('license-container'); - container.classList.add('hidden'); - - // Karte zurücksetzen - isCardFlipped = false; - document.querySelector('.license-content').classList.remove('hidden'); - document.querySelector('.license-footer').classList.remove('hidden'); - document.getElementById('license-back').classList.add('hidden'); - - // Callback an FiveM - fetch(`https://${GetParentResourceName()}/closeLicense`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({}) - }).catch(() => {}); // Fehler ignorieren -} - -// Hilfsfunktionen -function formatGender(gender) { - const genderMap = { - 'male': 'Männlich', - 'female': 'Weiblich', - 'other': 'Divers', - 'm': 'Männlich', - 'f': 'Weiblich' - }; - return genderMap[gender?.toLowerCase()] || gender || 'Unbekannt'; -} - -function formatDate(dateString) { - if (!dateString) return null; - - try { - const date = new Date(dateString); - return date.toLocaleDateString('de-DE'); - } catch (e) { - return dateString; - } -} - -function showLoading() { - document.getElementById('loading-overlay').classList.remove('hidden'); -} - -function hideLoading() { - document.getElementById('loading-overlay').classList.add('hidden'); -} - -function playSound(soundId) { - const audio = document.getElementById(soundId); - if (audio) { - audio.volume = 0.3; - audio.play().catch(() => {}); // Fehler ignorieren - } -} - -function showNotification(message, type = 'info') { - const container = document.getElementById('notification-container'); - const notification = document.createElement('div'); - - notification.className = `notification ${type}`; - notification.innerHTML = ` -
    - - ${message} -
    - - `; - - container.appendChild(notification); - - // Auto-remove nach 5 Sekunden - setTimeout(() => { - if (notification.parentElement) { - notification.remove(); - } - }, 5000); -} - -function getNotificationIcon(type) { - const icons = { - 'success': 'fa-check-circle', - 'error': 'fa-exclamation-circle', - 'warning': 'fa-exclamation-triangle', - 'info': 'fa-info-circle' - }; - return icons[type] || icons.info; -} - -// Event Listeners -document.addEventListener('keydown', function(event) { - if (event.key === 'Escape') { - if (!document.getElementById('camera-container').classList.contains('hidden')) { - closeCamera(); - } else if (!document.getElementById('license-container').classList.contains('hidden')) { - closeLicense(); - } - } - - if (event.key === 'f' || event.key === 'F') { - if (!document.getElementById('license-container').classList.contains('hidden')) { - flipCard(); - } - } -}); - -// Klick außerhalb zum Schließen -document.getElementById('license-container').addEventListener('click', function(event) { - if (event.target.classList.contains('license-overlay')) { - closeLicense(); - } -}); - -// Initialisierung -document.addEventListener('DOMContentLoaded', function() { - console.log('License System UI geladen'); -}); diff --git a/resources/[tools]/nordi_license/html/style.css b/resources/[tools]/nordi_license/html/style.css deleted file mode 100644 index f631638fb..000000000 --- a/resources/[tools]/nordi_license/html/style.css +++ /dev/null @@ -1,368 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Arial', sans-serif; - background: transparent; - overflow: hidden; -} - -.hidden { - display: none !important; -} - -#license-container { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background: rgba(0, 0, 0, 0.8); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; -} - -.license-card { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-radius: 15px; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); - width: 450px; - min-height: 300px; - color: white; - position: relative; - overflow: hidden; -} - -.license-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: url('data:image/svg+xml,'); - opacity: 0.3; - pointer-events: none; -} - -.license-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 20px; - border-bottom: 2px solid rgba(255, 255, 255, 0.2); -} - -.license-title { - font-size: 24px; - font-weight: bold; - text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); -} - -.license-logo { - font-size: 32px; - opacity: 0.8; -} - -.license-content { - display: flex; - padding: 20px; - gap: 20px; -} - -.license-photo { - width: 100px; - height: 120px; - background: rgba(255, 255, 255, 0.2); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - border: 2px solid rgba(255, 255, 255, 0.3); - overflow: hidden; - position: relative; -} - -#player-photo { - width: 100%; - height: 100%; - object-fit: cover; - border-radius: 6px; -} - -#photo-placeholder { - font-size: 48px; - color: rgba(255, 255, 255, 0.7); -} - -.hidden { - display: none !important; -} - - -.license-info { - flex: 1; -} - -.info-row { - display: flex; - justify-content: space-between; - margin-bottom: 12px; - padding: 8px 0; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.info-row:last-child { - border-bottom: none; - margin-bottom: 0; -} - -.label { - font-weight: bold; - opacity: 0.9; - min-width: 100px; -} - -.value { - text-align: right; - opacity: 0.8; -} - -.license-footer { - padding: 20px; - border-top: 2px solid rgba(255, 255, 255, 0.2); - display: flex; - justify-content: space-between; - align-items: center; -} - -.license-status { - font-weight: bold; - padding: 5px 15px; - border-radius: 20px; - font-size: 14px; -} - -.license-status.active { - background: rgba(76, 175, 80, 0.3); - color: #4CAF50; - border: 1px solid #4CAF50; -} - -.license-status.inactive { - background: rgba(244, 67, 54, 0.3); - color: #F44336; - border: 1px solid #F44336; -} - -.close-btn { - background: rgba(255, 255, 255, 0.2); - border: 1px solid rgba(255, 255, 255, 0.3); - color: white; - padding: 10px 20px; - border-radius: 25px; - cursor: pointer; - transition: all 0.3s ease; - font-size: 14px; - display: flex; - align-items: center; - gap: 8px; -} - -.close-btn:hover { - background: rgba(255, 255, 255, 0.3); - transform: translateY(-2px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); -} - -/* Spezielle Styles für verschiedene Lizenztypen */ -.license-card.id_card { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.license-card.drivers_license { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -} - -.license-card.weapon_license { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); -} - -.license-card.passport { - background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); -} - -.license-card.business_license { - background: linear-gradient(135deg, #fa709a 0%, #fee140 100%); -} - -/* Responsive Design */ -@media (max-width: 500px) { - .license-card { - width: 90vw; - margin: 20px; - } - - .license-content { - flex-direction: column; - align-items: center; - } - - .license-photo { - width: 80px; - height: 100px; - font-size: 36px; - } -} - -/* Animation für das Erscheinen */ -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(-50px) scale(0.9); - } - to { - opacity: 1; - transform: translateY(0) scale(1); - } -} - -.license-card { - animation: slideIn 0.3s ease-out; -} - -/* Notification System */ -#notification-container { - position: fixed; - top: 20px; - right: 20px; - z-index: 10000; - display: flex; - flex-direction: column; - gap: 10px; -} - -.notification { - background: rgba(0, 0, 0, 0.9); - color: white; - padding: 15px; - border-radius: 8px; - display: flex; - align-items: center; - justify-content: space-between; - min-width: 300px; - animation: slideInRight 0.3s ease; -} - -.notification.success { border-left: 4px solid #4CAF50; } -.notification.error { border-left: 4px solid #f44336; } -.notification.warning { border-left: 4px solid #ff9800; } -.notification.info { border-left: 4px solid #2196F3; } - -.notification-content { - display: flex; - align-items: center; - gap: 10px; -} - -.notification-close { - background: none; - border: none; - color: white; - cursor: pointer; - padding: 5px; -} - -/* Loading Overlay */ -#loading-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.8); - display: flex; - align-items: center; - justify-content: center; - z-index: 9999; -} - -.loading-spinner { - text-align: center; - color: white; -} - -.spinner { - width: 50px; - height: 50px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-top: 3px solid white; - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 20px; -} - -/* Kamera Interface */ -#camera-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.9); - display: flex; - align-items: center; - justify-content: center; - z-index: 9998; -} - -.camera-interface { - background: white; - border-radius: 15px; - padding: 20px; - max-width: 600px; - width: 90%; -} - -.camera-preview { - position: relative; - margin: 20px 0; -} - -#camera-video { - width: 100%; - border-radius: 10px; -} - -.face-guide { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - text-align: center; - color: white; -} - -.guide-circle { - width: 200px; - height: 200px; - border: 3px solid rgba(255, 255, 255, 0.8); - border-radius: 50%; - margin: 0 auto 10px; -} - -/* Animationen */ -@keyframes slideInRight { - from { transform: translateX(100%); opacity: 0; } - to { transform: translateX(0); opacity: 1; } -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua deleted file mode 100644 index 2a7925a33..000000000 --- a/resources/[tools]/nordi_license/server/main.lua +++ /dev/null @@ -1,1249 +0,0 @@ -local QBCore = exports['qb-core']:GetCoreObject() - --- Lokale Variablen -local licenseCache = {} -local cacheTimeout = 300000 -- 5 Minuten - --- Debug-Funktion -local function debugPrint(message) - if Config.Debug then - print("^3[License-System Server] " .. message .. "^7") - end -end - --- Spieler-Name abrufen -local function getPlayerName(src) - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return "Unbekannt" end - - local charinfo = Player.PlayerData.charinfo - if charinfo and charinfo.firstname and charinfo.lastname then - return charinfo.firstname .. " " .. charinfo.lastname - end - - return "Unbekannt" -end - --- Berechtigung prüfen -local function hasPermission(src) - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return false end - - local job = Player.PlayerData.job - if not job then return false end - - return Config.AuthorizedJobs[job.name] or false -end - --- Cache-Funktionen (KORRIGIERT - Aggressive Cache-Invalidierung) -local function getCachedLicense(citizenid, licenseType) - local cacheKey = citizenid .. "_" .. licenseType - local cached = licenseCache[cacheKey] - - if cached and (os.time() * 1000 - cached.timestamp) < cacheTimeout then - debugPrint("Cache-Hit für: " .. cacheKey) - return cached.data - end - - return nil -end - -local function setCachedLicense(citizenid, licenseType, data) - local cacheKey = citizenid .. "_" .. licenseType - licenseCache[cacheKey] = { - data = data, - timestamp = os.time() * 1000 - } - debugPrint("Lizenz gecacht: " .. cacheKey) -end - --- Cache invalidieren (ERWEITERT) -local function invalidateCache(citizenid, licenseType) - if licenseType then - -- Spezifische Lizenz invalidieren - local cacheKey = citizenid .. "_" .. licenseType - licenseCache[cacheKey] = nil - debugPrint("Cache invalidiert für: " .. cacheKey) - else - -- Alle Lizenzen des Spielers invalidieren - for key, _ in pairs(licenseCache) do - if string.find(key, citizenid .. "_") then - licenseCache[key] = nil - debugPrint("Cache invalidiert für: " .. key) - end - end - end -end - --- Spieler-Name aus JSON extrahieren -local function extractPlayerName(charinfo_json) - if not charinfo_json then return "Unbekannt" end - - local success, charinfo = pcall(json.decode, charinfo_json) - if success and charinfo and charinfo.firstname and charinfo.lastname then - return charinfo.firstname .. " " .. charinfo.lastname - end - - return "Unbekannt" -end - --- Sichere DB-Operation -local function safeDBOperation(operation, errorMessage, maxRetries) - maxRetries = maxRetries or 3 - local retries = 0 - - while retries < maxRetries do - retries = retries + 1 - local success, result = pcall(operation) - - if success then - return result - else - debugPrint("^3DB-Retry " .. retries .. "/" .. maxRetries .. ": " .. tostring(result) .. "^7") - if retries >= maxRetries then - debugPrint("^1DB-Fehler: " .. (errorMessage or "Unbekannt") .. "^7") - debugPrint("^1Details: " .. tostring(result) .. "^7") - return nil - end - Wait(1000) - end - end - - return nil -end - --- Lizenz-Status prüfen (NEUE FUNKTION) -local function isLicenseActive(license) - if not license then return false end - - -- is_active prüfen (1 = aktiv, 0 = inaktiv, nil = aktiv per default) - local isActive = license.is_active - if isActive == nil then - isActive = 1 -- Default: aktiv - end - - if isActive ~= 1 then - debugPrint("Lizenz inaktiv (is_active = " .. tostring(isActive) .. ")") - return false - end - - -- Ablaufdatum prüfen (falls vorhanden) - if license.expire_date and license.expire_date ~= "" then - local expireDate = license.expire_date - local currentDate = os.date("%d.%m.%Y") - - -- Einfache Datumsvergleich (DD.MM.YYYY) - local function parseDate(dateStr) - local day, month, year = dateStr:match("(%d+)%.(%d+)%.(%d+)") - if day and month and year then - return os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)}) - end - return nil - end - - local expireTimestamp = parseDate(expireDate) - local currentTimestamp = parseDate(currentDate) - - if expireTimestamp and currentTimestamp and expireTimestamp < currentTimestamp then - debugPrint("Lizenz abgelaufen: " .. expireDate .. " < " .. currentDate) - return false - end - end - - debugPrint("Lizenz ist aktiv und gültig") - return true -end - --- KORRIGIERTE Lizenz-Abfrage (Ohne Cache für frische Daten) -local function getLicenseFromDB(citizenid, licenseType, skipCache) - debugPrint("=== getLicenseFromDB START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - debugPrint("LicenseType: " .. tostring(licenseType)) - debugPrint("SkipCache: " .. tostring(skipCache)) - - -- Cache prüfen (nur wenn nicht übersprungen) - if not skipCache then - local cached = getCachedLicense(citizenid, licenseType) - if cached then - debugPrint("Lizenz aus Cache geladen") - return cached - end - end - - -- Direkte DB-Abfrage (VEREINFACHT - nur aktive Lizenzen) - local query = [[ - SELECT * FROM player_licenses - WHERE citizenid = ? AND license_type = ? - ORDER BY created_at DESC - LIMIT 1 - ]] - - local result = safeDBOperation(function() - return MySQL.query.await(query, {citizenid, licenseType}) - end, "Fehler beim Abrufen der Lizenz") - - if not result or #result == 0 then - debugPrint("^1Keine Lizenz in DB gefunden für " .. citizenid .. " / " .. licenseType .. "^7") - return nil - end - - local license = result[1] - debugPrint("Rohe Lizenz-Daten aus DB:") - debugPrint("ID: " .. tostring(license.id)) - debugPrint("is_active: " .. tostring(license.is_active)) - debugPrint("created_at: " .. tostring(license.created_at)) - - -- Spieler-Namen abrufen - local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local holderResult = safeDBOperation(function() - return MySQL.query.await(holderQuery, {citizenid}) - end, "Fehler beim Abrufen des Spieler-Namens") - - if holderResult and #holderResult > 0 then - license.holder_name = extractPlayerName(holderResult[1].charinfo) - else - license.holder_name = "Unbekannt" - end - - -- Aussteller-Namen abrufen - if license.issued_by then - local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local issuerResult = safeDBOperation(function() - return MySQL.query.await(issuerQuery, {license.issued_by}) - end, "Fehler beim Abrufen des Aussteller-Namens") - - if issuerResult and #issuerResult > 0 then - license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) - else - license.issued_by_name = "System" - end - else - license.issued_by_name = "System" - end - - -- Classes parsen - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - license.classes = {} - end - - -- is_active normalisieren (WICHTIG!) - if license.is_active == nil then - license.is_active = 1 - debugPrint("is_active war nil, auf 1 gesetzt") - end - - -- Status prüfen - local isActive = isLicenseActive(license) - debugPrint("Lizenz-Status-Prüfung: " .. tostring(isActive)) - - if not isActive then - debugPrint("Lizenz ist nicht aktiv/gültig") - return nil - end - - debugPrint("Lizenz erfolgreich geladen: " .. license.license_type .. " (Active: " .. tostring(license.is_active) .. ")") - - -- In Cache speichern (nur wenn aktiv) - if not skipCache then - setCachedLicense(citizenid, licenseType, license) - end - - return license -end - --- Alle Lizenzen eines Spielers abrufen (KORRIGIERT) -local function getAllPlayerLicenses(citizenid) - debugPrint("=== getAllPlayerLicenses START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - - -- Alle Lizenzen abrufen (ohne is_active Filter) - local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" - - local result = safeDBOperation(function() - return MySQL.query.await(query, {citizenid}) - end, "Fehler beim Abrufen aller Lizenzen") - - if not result or #result == 0 then - debugPrint("Keine Lizenzen gefunden für: " .. citizenid) - return {} - end - - local licenses = {} - local seenTypes = {} - - -- Spieler-Namen einmal abrufen - local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local holderResult = safeDBOperation(function() - return MySQL.query.await(holderQuery, {citizenid}) - end, "Fehler beim Abrufen des Spieler-Namens") - - local holderName = "Unbekannt" - if holderResult and #holderResult > 0 then - holderName = extractPlayerName(holderResult[1].charinfo) - end - - for _, license in ipairs(result) do - -- Nur die neueste Lizenz pro Typ nehmen - if not seenTypes[license.license_type] then - seenTypes[license.license_type] = true - - license.holder_name = holderName - - -- Aussteller-Namen abrufen - if license.issued_by then - local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local issuerResult = safeDBOperation(function() - return MySQL.query.await(issuerQuery, {license.issued_by}) - end, "Fehler beim Abrufen des Aussteller-Namens") - - if issuerResult and #issuerResult > 0 then - license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) - else - license.issued_by_name = "System" - end - else - license.issued_by_name = "System" - end - - -- Classes parsen - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - license.classes = {} - end - - -- is_active normalisieren - if license.is_active == nil then - license.is_active = 1 - end - - -- Status prüfen und nur aktive Lizenzen hinzufügen - if isLicenseActive(license) then - table.insert(licenses, license) - debugPrint("Aktive Lizenz hinzugefügt: " .. license.license_type) - else - debugPrint("Inaktive Lizenz übersprungen: " .. license.license_type) - end - end - end - - debugPrint("Verarbeitete aktive Lizenzen: " .. #licenses) - return licenses -end - --- Lizenz in Datenbank speichern (KORRIGIERT - Explizite is_active Setzung) -local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) - debugPrint("=== saveLicenseToDB START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - debugPrint("LicenseType: " .. tostring(licenseType)) - debugPrint("IssuedBy: " .. tostring(issuedBy)) - - local config = Config.LicenseTypes[licenseType] - if not config then - debugPrint("^1Fehler: Unbekannter Lizenztyp: " .. licenseType .. "^7") - return false - end - - -- Cache für diesen Spieler komplett invalidieren - invalidateCache(citizenid) - - -- Spieler-Name abrufen - local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local holderResult = safeDBOperation(function() - return MySQL.query.await(holderQuery, {citizenid}) - end, "Fehler beim Abrufen des Spieler-Namens") - - local holderName = "Unbekannt" - if holderResult and #holderResult > 0 and holderResult[1].charinfo then - holderName = extractPlayerName(holderResult[1].charinfo) - end - - -- Datum berechnen - local issueDate = os.date("%d.%m.%Y") - local expireDate = nil - - if config.validity_days then - local expireTimestamp = os.time() + (config.validity_days * 24 * 60 * 60) - expireDate = os.date("%d.%m.%Y", expireTimestamp) - end - - -- Classes zu JSON konvertieren - local classesJson = json.encode(classes or {}) - - -- WICHTIG: Alte Lizenz explizit deaktivieren - local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" - local deactivateResult = safeDBOperation(function() - return MySQL.query.await(deactivateQuery, {citizenid, licenseType}) - end, "Fehler beim Deaktivieren alter Lizenz") - - debugPrint("Alte Lizenzen deaktiviert: " .. tostring(deactivateResult ~= nil)) - - -- Neue Lizenz einfügen (EXPLIZIT is_active = 1) - local insertQuery = [[ - INSERT INTO player_licenses - (citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at) - VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?) - ]] - - local createdAt = os.time() -- Unix Timestamp - - local insertData = { - citizenid, - licenseType, - holderName, - issueDate, - expireDate, - issuedBy, - -- is_active = 1 ist direkt in der Query - classesJson, - createdAt - } - - debugPrint("Führe INSERT-Query aus...") - debugPrint("Daten: " .. json.encode(insertData)) - - local result = safeDBOperation(function() - return MySQL.insert.await(insertQuery, insertData) - end, "Fehler beim Speichern der Lizenz") - - if result then - debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result) - - -- Cache komplett invalidieren (sicherstellen dass neue Daten geladen werden) - invalidateCache(citizenid) - - -- Sofort neue Lizenz aus DB laden um zu verifizieren - Wait(100) -- Kurz warten - local newLicense = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache - - if newLicense then - debugPrint("Neue Lizenz erfolgreich verifiziert: is_active = " .. tostring(newLicense.is_active)) - else - debugPrint("^3Warnung: Neue Lizenz konnte nicht verifiziert werden^7") - end - - return true - else - debugPrint("^1Fehler beim Speichern der Lizenz^7") - return false - end -end - --- Lizenz entziehen (KORRIGIERT) -local function revokeLicenseInDB(citizenid, licenseType) - debugPrint("=== revokeLicenseInDB START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - debugPrint("LicenseType: " .. tostring(licenseType)) - - local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1" - - local result = safeDBOperation(function() - return MySQL.query.await(query, {citizenid, licenseType}) - end, "Fehler beim Entziehen der Lizenz") - - if result then - debugPrint("Lizenz erfolgreich entzogen") - - -- Cache invalidieren - invalidateCache(citizenid, licenseType) - - return true - else - debugPrint("^1Fehler beim Entziehen der Lizenz^7") - return false - end -end - --- Cache bereinigen -local function cleanupCache() - local now = os.time() * 1000 - local cleaned = 0 - - for key, cached in pairs(licenseCache) do - if (now - cached.timestamp) > cacheTimeout then - licenseCache[key] = nil - cleaned = cleaned + 1 - end - end - - if cleaned > 0 then - debugPrint("Cache bereinigt: " .. cleaned .. " Einträge") - end -end - --- Cache-Cleanup Thread -CreateThread(function() - while true do - Wait(300000) -- 5 Minuten - cleanupCache() - end -end) - --- EVENT HANDLER: Lizenz anfordern -RegisterNetEvent('license-system:server:requestLicense', function(targetId) - local src = source - debugPrint("=== Event: requestLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('license-system:client:receiveLicense', src, nil) - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('license-system:client:receiveLicense', src, nil) - return - end - - local citizenid = targetPlayer.PlayerData.citizenid - - -- PRIORITÄT: Erst nach Ausweis suchen, dann andere Lizenzen - local licenseTypes = {"id_card", "driver_license", "weapon_license", "pilot_license"} - local foundLicense = nil - - for _, licenseType in ipairs(licenseTypes) do - if Config.LicenseTypes[licenseType] then - local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten - - if license then - foundLicense = { - license = license, - config = Config.LicenseTypes[licenseType] - } - debugPrint("Lizenz gefunden: " .. licenseType) - break - end - end - end - - if foundLicense then - debugPrint("Sende Lizenz an Client: " .. foundLicense.license.license_type) - TriggerClientEvent('license-system:client:receiveLicense', src, foundLicense) - else - debugPrint("Keine Lizenz gefunden") - TriggerClientEvent('license-system:client:receiveLicense', src, nil) - end -end) - --- EVENT HANDLER: Eigene Lizenz anfordern (KORRIGIERT) -RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) - local src = source - debugPrint("=== Event: requestMyLicense ===") - debugPrint("Source: " .. src .. ", LicenseType: " .. tostring(licenseType)) - - local Player = QBCore.Functions.GetPlayer(src) - if not Player then - debugPrint("Spieler nicht gefunden: " .. src) - TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) - return - end - - local citizenid = Player.PlayerData.citizenid - - -- Falls kein spezifischer Typ angegeben, suche nach Ausweis - if not licenseType or licenseType == "" then - licenseType = "id_card" - debugPrint("Kein Lizenztyp angegeben, verwende: " .. licenseType) - end - - local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten - - if license then - local licenseData = { - license = license, - config = Config.LicenseTypes[licenseType] - } - debugPrint("Sende eigene Lizenz an Client: " .. licenseType) - TriggerClientEvent('license-system:client:receiveMyLicense', src, licenseData, licenseType) - else - debugPrint("Keine aktive eigene Lizenz gefunden: " .. licenseType) - TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) - end -end) - --- EVENT HANDLER: Alle Spieler-Lizenzen anfordern -RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetId) - local src = source - debugPrint("=== Event: requestPlayerLicenses ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local citizenid = targetPlayer.PlayerData.citizenid - local targetName = getPlayerName(targetId) - local licenses = getAllPlayerLicenses(citizenid) - - debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) - TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) -end) - --- EVENT HANDLER: Lizenz ausstellen (KORRIGIERT) -RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) - local src = source - debugPrint("=== Event: issueLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local issuerPlayer = QBCore.Functions.GetPlayer(src) - if not issuerPlayer then - debugPrint("Aussteller nicht gefunden: " .. src) - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - local issuerCitizenId = issuerPlayer.PlayerData.citizenid - - -- Prüfen ob aktive Lizenz bereits existiert (Skip Cache) - local existingLicense = getLicenseFromDB(targetCitizenId, licenseType, true) - - if existingLicense then - debugPrint("Lizenz bereits vorhanden und aktiv") - TriggerClientEvent('QBCore:Notify', src, 'Spieler hat bereits eine aktive ' .. (Config.LicenseTypes[licenseType].label or licenseType) .. '!', 'error') - return - end - - -- Kosten prüfen - local config = Config.LicenseTypes[licenseType] - if config.price and config.price > 0 then - if issuerPlayer.PlayerData.money.cash < config.price then - debugPrint("Nicht genug Geld für Lizenz-Ausstellung") - TriggerClientEvent('QBCore:Notify', src, 'Nicht genug Bargeld! Benötigt: $' .. config.price, 'error') - return - end - - -- Geld abziehen - issuerPlayer.Functions.RemoveMoney('cash', config.price, 'license-issued') - TriggerClientEvent('QBCore:Notify', src, 'Lizenz-Gebühr bezahlt: $' .. config.price, 'success') - end - - -- Lizenz in Datenbank speichern - local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) - - if success then - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - - debugPrint("Lizenz erfolgreich ausgestellt") - - -- Benachrichtigungen - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') - - -- Events senden - TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseType .. " ausgestellt von " .. issuerName .. " für " .. targetName) - else - debugPrint("^1Fehler beim Ausstellen der Lizenz^7") - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') - end -end) - --- EVENT HANDLER: Lizenz entziehen -RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) - local src = source - debugPrint("=== Event: revokeLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - - -- Lizenz entziehen - local success = revokeLicenseInDB(targetCitizenId, licenseType) - - if success then - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - local config = Config.LicenseTypes[licenseType] - - debugPrint("Lizenz erfolgreich entzogen") - - -- Benachrichtigungen - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen von ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Deine Lizenz wurde entzogen: ' .. (config.label or licenseType), 'error') - - -- Events senden - TriggerClientEvent('license-system:client:licenseRevoked', src, targetId, licenseType) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseType .. " entzogen von " .. issuerName .. " für " .. targetName) - else - debugPrint("^1Fehler beim Entziehen der Lizenz^7") - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') - end -end) - --- EVENT HANDLER: Lizenz reaktivieren -RegisterNetEvent('license-system:server:reactivateLicense', function(targetId, licenseType) - local src = source - debugPrint("=== Event: reactivateLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) - - -- Check if player has permission to reactivate this license type - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - local job = Player.PlayerData.job.name - local canReactivate = false - - -- Check if job can reactivate this license type - if Config.ReactivationPermissions and Config.ReactivationPermissions[job] then - for _, allowedType in ipairs(Config.ReactivationPermissions[job]) do - if allowedType == licenseType then - canReactivate = true - break - end - end - end - - if not canReactivate then - TriggerClientEvent('QBCore:Notify', src, 'Du darfst diesen Lizenztyp nicht reaktivieren!', 'error') - return - end - - -- Get target player - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - - -- Find the most recent license of this type (even if inactive) - local query = [[ - SELECT * FROM player_licenses - WHERE citizenid = ? AND license_type = ? - ORDER BY created_at DESC - LIMIT 1 - ]] - - local result = MySQL.query.await(query, {targetCitizenId, licenseType}) - - if not result or #result == 0 then - TriggerClientEvent('QBCore:Notify', src, 'Keine Lizenz dieses Typs gefunden!', 'error') - return - end - - -- Reactivate the license - local updateQuery = "UPDATE player_licenses SET is_active = 1 WHERE id = ?" - local success = MySQL.update.await(updateQuery, {result[1].id}) - - if success then - -- Invalidate cache - invalidateCache(targetCitizenId, licenseType) - - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - local config = Config.LicenseTypes[licenseType] - - -- Notifications - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich reaktiviert für ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (config.label or licenseType) .. ' wurde reaktiviert!', 'success') - - -- Events - TriggerClientEvent('license-system:client:licenseReactivated', src, targetId, licenseType) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseType .. " reaktiviert von " .. issuerName .. " für " .. targetName) - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Reaktivieren der Lizenz!', 'error') - end -end) - --- EVENT HANDLER: Manuelle Lizenz ausstellen (Erweitert) -RegisterNetEvent('license-system:server:issueManualLicense', function(targetId, licenseData) - local src = source - debugPrint("=== Event: issueManualLicense ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') - return - end - - local issuerPlayer = QBCore.Functions.GetPlayer(src) - if not issuerPlayer then - debugPrint("Aussteller nicht gefunden: " .. src) - return - end - - local targetCitizenId = targetPlayer.PlayerData.citizenid - local issuerCitizenId = issuerPlayer.PlayerData.citizenid - - -- Prüfen ob Lizenztyp gültig ist - if not Config.LicenseTypes[licenseData.license_type] then - TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error') - return - end - - -- Foto speichern falls vorhanden - if licenseData.photo_url then - debugPrint("Foto für Lizenz vorhanden") - end - - -- Alte Lizenzen deaktivieren - local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" - MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type}) - - -- Klassen zu JSON konvertieren - local classesJson = json.encode(licenseData.classes or {}) - - -- In Datenbank einfügen mit manuellen Daten - local query = [[ - INSERT INTO player_licenses - (citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, classes, photo_url, notes, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?) - ]] - - local createdAt = os.time() - - local insertData = { - targetCitizenId, - licenseData.license_type, - licenseData.name, - licenseData.birthday, - licenseData.gender, - licenseData.issue_date, - licenseData.expire_date, - issuerCitizenId, - classesJson, - licenseData.photo_url or '', - licenseData.notes or '', - createdAt - } - - local result = MySQL.insert.await(query, insertData) - - if result then - -- Cache invalidieren - invalidateCache(targetCitizenId) - - local targetName = getPlayerName(targetId) - local issuerName = getPlayerName(src) - local config = Config.LicenseTypes[licenseData.license_type] - - -- Benachrichtigungen - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') - - -- Events senden - TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseData.license_type) - TriggerClientEvent('license-system:client:refreshMenu', src) - - -- Log - debugPrint("Lizenz " .. licenseData.license_type .. " manuell ausgestellt von " .. issuerName .. " für " .. targetName) - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') - end -end) - - --- EVENT HANDLER: Alle Lizenzen eines Spielers anfordern (inkl. inaktive) -RegisterNetEvent('license-system:server:requestAllPlayerLicenses', function(targetId, includeInactive) - local src = source - debugPrint("=== Event: requestAllPlayerLicenses ===") - debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", IncludeInactive: " .. tostring(includeInactive)) - - if not hasPermission(src) then - debugPrint("Keine Berechtigung für Spieler: " .. src) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local targetPlayer = QBCore.Functions.GetPlayer(targetId) - if not targetPlayer then - debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") - return - end - - local citizenid = targetPlayer.PlayerData.citizenid - local targetName = getPlayerName(targetId) - - -- Query to get all licenses, including inactive ones if requested - local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" - - local result = MySQL.query.await(query, {citizenid}) - - if not result or #result == 0 then - debugPrint("Keine Lizenzen gefunden für: " .. citizenid) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, targetName) - return - end - - -- Process licenses - local licenses = {} - local seenTypes = {} - - for _, license in ipairs(result) do - -- If includeInactive is true, add all licenses - -- Otherwise, only add the newest license per type - local shouldAdd = includeInactive or not seenTypes[license.license_type] - if shouldAdd then - seenTypes[license.license_type] = true - - -- Add holder name - license.holder_name = targetName - - -- Add issuer name - if license.issued_by then - local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" - local issuerResult = MySQL.query.await(issuerQuery, {license.issued_by}) - - if issuerResult and #issuerResult > 0 then - license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) - else - license.issued_by_name = "System" - end - else - license.issued_by_name = "System" - end - - -- Parse classes - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - license.classes = {} - end - - -- Normalize is_active - if license.is_active == nil then - license.is_active = 1 - end - - table.insert(licenses, license) - end - end - - debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) - TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, licenses, targetId, targetName) -end) - --- EXPORT FUNKTIONEN (KORRIGIERT) -exports('hasLicense', function(citizenid, licenseType) - if not citizenid or not licenseType then return false end - - local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für aktuelle Daten - return license ~= nil -end) - -exports('issueLicense', function(citizenid, licenseType, issuedBy, classes) - if not citizenid or not licenseType then return false end - - issuedBy = issuedBy or 'system' - return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) -end) - -exports('revokeLicense', function(citizenid, licenseType) - if not citizenid or not licenseType then return false end - - return revokeLicenseInDB(citizenid, licenseType) -end) - -exports('getPlayerLicenses', function(citizenid) - if not citizenid then return {} end - - return getAllPlayerLicenses(citizenid) -end) - -exports('getPlayerLicense', function(citizenid, licenseType) - if not citizenid or not licenseType then return nil end - - return getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache -end) - --- DEBUG COMMAND: Lizenz manuell erstellen -RegisterCommand('createlicense', function(source, args, rawCommand) - if source == 0 then -- Console only - if #args < 2 then - print("Usage: createlicense ") - return - end - - local citizenid = args[1] - local licenseType = args[2] - - if not Config.LicenseTypes[licenseType] then - print("Unbekannter Lizenztyp: " .. licenseType) - return - end - - local success = saveLicenseToDB(citizenid, licenseType, 'console', {}) - - if success then - print("Lizenz erfolgreich erstellt: " .. licenseType .. " für " .. citizenid) - else - print("Fehler beim Erstellen der Lizenz") - end - end -end, true) - --- DEBUG COMMAND: Lizenz prüfen (ERWEITERT) -RegisterCommand('checklicense', function(source, args, rawCommand) - if source == 0 then -- Console only - if #args < 2 then - print("Usage: checklicense ") - return - end - - local citizenid = args[1] - local licenseType = args[2] - - -- Direkte DB-Abfrage ohne Cache - local query = "SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? ORDER BY created_at DESC" - local result = MySQL.query.await(query, {citizenid, licenseType}) - - if result and #result > 0 then - print("=== ALLE LIZENZEN GEFUNDEN ===") - for i, license in ipairs(result) do - print("--- Lizenz " .. i .. " ---") - print("ID: " .. (license.id or "N/A")) - print("CitizenID: " .. (license.citizenid or "N/A")) - print("Typ: " .. (license.license_type or "N/A")) - print("Name: " .. (license.name or "N/A")) - print("Ausstellungsdatum: " .. (license.issue_date or "N/A")) - print("Ablaufdatum: " .. (license.expire_date or "N/A")) - print("Ausgestellt von: " .. (license.issued_by or "N/A")) - print("Aktiv (DB): " .. tostring(license.is_active)) - print("Klassen: " .. (license.classes or "[]")) - print("Erstellt am: " .. (license.created_at or "N/A")) - print("---") - end - print("===============================") - - -- Zusätzlich: Lizenz über Funktion prüfen - local license = getLicenseFromDB(citizenid, licenseType, true) - if license then - print("=== AKTIVE LIZENZ (über Funktion) ===") - print("Typ: " .. license.license_type) - print("Aktiv: " .. tostring(license.is_active)) - print("Status-Check: " .. tostring(isLicenseActive(license))) - print("====================================") - else - print("=== KEINE AKTIVE LIZENZ (über Funktion) ===") - end - else - print("Keine Lizenz gefunden für: " .. citizenid .. " / " .. licenseType) - end - end -end, true) - --- DEBUG COMMAND: Alle Lizenzen eines Spielers anzeigen -RegisterCommand('checkalllicenses', function(source, args, rawCommand) - if source == 0 then -- Console only - if #args < 1 then - print("Usage: checkalllicenses ") - return - end - - local citizenid = args[1] - - -- Direkte DB-Abfrage - local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" - local result = MySQL.query.await(query, {citizenid}) - - if result and #result > 0 then - print("=== ALLE LIZENZEN FÜR " .. citizenid .. " ===") - for i, license in ipairs(result) do - print(i .. ". " .. license.license_type .. " | Aktiv: " .. tostring(license.is_active) .. " | ID: " .. license.id) - end - print("=========================================") - - -- Über Funktion - local activeLicenses = getAllPlayerLicenses(citizenid) - print("=== AKTIVE LIZENZEN (über Funktion) ===") - for i, license in ipairs(activeLicenses) do - print(i .. ". " .. license.license_type .. " | Aktiv: " .. tostring(license.is_active)) - end - print("======================================") - else - print("Keine Lizenzen gefunden für: " .. citizenid) - end - end -end, true) - --- DEBUG COMMAND: Lizenz-Status forciert aktualisieren -RegisterCommand('fixlicense', function(source, args, rawCommand) - if source == 0 then -- Console only - if #args < 2 then - print("Usage: fixlicense ") - return - end - - local citizenid = args[1] - local licenseType = args[2] - - -- Neueste Lizenz auf aktiv setzen - local query = [[ - UPDATE player_licenses - SET is_active = 1 - WHERE citizenid = ? AND license_type = ? AND id = ( - SELECT id FROM ( - SELECT id FROM player_licenses - WHERE citizenid = ? AND license_type = ? - ORDER BY created_at DESC LIMIT 1 - ) as temp - ) - ]] - - local result = MySQL.query.await(query, {citizenid, licenseType, citizenid, licenseType}) - - if result then - print("Lizenz-Status aktualisiert für: " .. citizenid .. " / " .. licenseType) - - -- Cache invalidieren - invalidateCache(citizenid, licenseType) - - -- Prüfen - local license = getLicenseFromDB(citizenid, licenseType, true) - if license then - print("Lizenz ist jetzt aktiv: " .. tostring(license.is_active)) - else - print("Lizenz konnte nicht geladen werden") - end - else - print("Fehler beim Aktualisieren der Lizenz") - end - end -end, true) - --- INITIALISIERUNG -CreateThread(function() - debugPrint("License-System Server gestartet (Status-Fix)") - - -- Warten bis QBCore geladen ist - while not QBCore do - Wait(100) - end - - debugPrint("QBCore erfolgreich geladen") - - -- Datenbank-Verbindung testen - local testResult = safeDBOperation(function() - return MySQL.query.await("SELECT 1 as test") - end, "Datenbank-Verbindungstest") - - if testResult then - debugPrint("Datenbank-Verbindung erfolgreich") - else - debugPrint("^1Datenbank-Verbindung fehlgeschlagen^7") - end - - debugPrint("License-System Server vollständig initialisiert") -end) - --- CLEANUP -AddEventHandler('onResourceStop', function(resourceName) - if GetCurrentResourceName() == resourceName then - debugPrint("License-System Server gestoppt") - licenseCache = {} - end -end) - --- DEBUG COMMANDS -RegisterCommand('licensestats', function(source, args, rawCommand) - if source == 0 then -- Console only - local cacheCount = 0 - for _ in pairs(licenseCache) do - cacheCount = cacheCount + 1 - end - - print("=== LICENSE SYSTEM STATS ===") - print("Cache Entries: " .. cacheCount) - print("Config License Types: " .. (Config.LicenseTypes and table.count(Config.LicenseTypes) or 0)) - print("============================") - end -end, true) - -RegisterCommand('licenseclearcache', function(source, args, rawCommand) - if source == 0 then -- Console only - local oldCount = 0 - for _ in pairs(licenseCache) do - oldCount = oldCount + 1 - end - - licenseCache = {} - print("License-Cache geleert. Entfernte Einträge: " .. oldCount) - end -end, true) - --- Hilfsfunktion für table.count -function table.count(t) - local count = 0 - for _ in pairs(t) do - count = count + 1 - end - return count -end - -debugPrint("License-System Server vollständig geladen (Status-Fix)") - -