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
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
SECURE DOCUMENT • OFFICIAL USE ONLY • GOVERNMENT ISSUED
-
-
-
-
-
-
-
-
-
-
![Spieler Foto]()
-
-
-
-
-
-
-
-
-
-
-
Persönliche Daten
-
-
-
-
- Name:
-
- Max Mustermann
-
-
-
-
-
- Geburtsdatum:
-
- 01.01.1990
-
-
-
-
-
- Geschlecht:
-
- Männlich
-
-
-
-
-
Dokument-Informationen
-
-
-
-
- Ausgestellt:
-
- 01.01.2024
-
-
-
-
-
- Gültig bis:
-
- 01.01.2034
-
-
-
-
-
- Dokument-ID:
-
- #000001
-
-
-
-
-
- Klassen:
-
- A, B, C
-
-
-
-
-
- Ausgestellt von:
-
- Behörde
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Bemerkungen:
-
Keine besonderen Bemerkungen
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Gesicht hier positionieren
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Lizenz wird geladen...
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
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)")
-
-