From 7932af55cb7e548707e92357b5e97ec0bb4057a4 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 07:40:06 +0200 Subject: [PATCH] ed --- .../[tools]/nordi_license/client/main.lua | 937 +++++++++++------- resources/[tools]/nordi_license/config.lua | 353 +++++-- .../[tools]/nordi_license/server/main.lua | 736 +++++++++----- 3 files changed, 1311 insertions(+), 715 deletions(-) diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua index 135d4c2ae..804995a00 100644 --- a/resources/[tools]/nordi_license/client/main.lua +++ b/resources/[tools]/nordi_license/client/main.lua @@ -1,416 +1,595 @@ local QBCore = exports['qb-core']:GetCoreObject() -local playerLicenses = {} + +-- Lokale Variablen +local isMenuOpen = false +local currentTarget = nil local nearbyPlayers = {} --- Keybind registrieren -RegisterKeyMapping(Config.Command, 'Lizenz-Menü öffnen', 'keyboard', Config.OpenKey) +-- Hilfsfunktionen +local function debugPrint(message) + if Config.Debug then + print("^3[License-System Client] " .. message .. "^7") + end +end --- Events -RegisterNetEvent('license-system:client:openMenu', function() - TriggerServerEvent('license-system:server:getLicenses') -end) +local function safeCallback(cb, ...) + if cb and type(cb) == "function" then + cb(...) + else + debugPrint("^1Callback ist keine Funktion!^7") + end +end -RegisterNetEvent('license-system:client:receiveLicenses', function(licenses) - playerLicenses = licenses - openMainMenu() -end) +local function showNotification(message, type) + QBCore.Functions.Notify(message, type or 'primary') +end -RegisterNetEvent('license-system:client:receiveNearbyPlayers', function(players) - nearbyPlayers = {} +-- 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(players) do - if playerId ~= GetPlayerServerId(PlayerId()) then - local targetPed = GetPlayerPed(GetPlayerFromServerId(playerId)) - if targetPed and targetPed ~= 0 then - local targetCoords = GetEntityCoords(targetPed) - local distance = #(playerCoords - targetCoords) + 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) - if distance <= 5.0 then - local targetPlayer = QBCore.Functions.GetPlayerData(playerId) - table.insert(nearbyPlayers, { - id = playerId, - name = targetPlayer and (targetPlayer.charinfo.firstname .. ' ' .. targetPlayer.charinfo.lastname) or 'Unbekannt' - }) - end - end - end - end -end) - -RegisterNetEvent('license-system:client:viewLicense', function(licenseData, issuerName) - showLicenseOverlay(licenseData, issuerName) -end) - --- Hauptmenü -function openMainMenu() - local options = { - { - title = 'Meine Lizenzen anzeigen', - description = 'Zeige alle deine Lizenzen an', - icon = 'fas fa-eye', - onSelect = function() - showMyLicenses() - end - }, - { - title = 'Lizenz zeigen', - description = 'Zeige jemandem in der Nähe eine Lizenz', - icon = 'fas fa-share', - onSelect = function() - showLicenseToPlayer() - end - } - } - - -- Admin-Optionen hinzufügen - local PlayerData = QBCore.Functions.GetPlayerData() - if PlayerData.job and Config.AuthorizedJobs[PlayerData.job.name] then - table.insert(options, { - title = 'Lizenz ausstellen', - description = 'Stelle eine neue Lizenz aus', - icon = 'fas fa-plus', - onSelect = function() - issueLicenseMenu() - end - }) - - table.insert(options, { - title = 'Lizenz verwalten', - description = 'Entziehe oder stelle Lizenzen wieder her', - icon = 'fas fa-cog', - onSelect = function() - manageLicensesMenu() - end - }) - end - - lib.registerContext({ - id = 'license_main_menu', - title = 'Lizenz-System', - options = options - }) - - lib.showContext('license_main_menu') -end - --- Meine Lizenzen anzeigen -function showMyLicenses() - local options = {} - - for _, license in ipairs(playerLicenses) do - local licenseConfig = Config.Licenses[license.license_type] - if licenseConfig then - local statusText = license.is_active and "✅ Aktiv" or "❌ Entzogen" - table.insert(options, { - title = licenseConfig.label, - description = 'Status: ' .. statusText .. ' | Ausgestellt: ' .. license.issue_date, - icon = licenseConfig.icon, - onSelect = function() - showLicenseOverlay(license) - end - }) - end - end - - if #options == 0 then - options = {{ - title = 'Keine Lizenzen vorhanden', - description = 'Du besitzt derzeit keine Lizenzen', - icon = 'fas fa-exclamation-triangle' - }} - end - - lib.registerContext({ - id = 'my_licenses_menu', - title = 'Meine Lizenzen', - menu = 'license_main_menu', - options = options - }) - - lib.showContext('my_licenses_menu') -end - --- Lizenz jemandem zeigen -function showLicenseToPlayer() - TriggerServerEvent('license-system:server:getNearbyPlayers') - - Wait(500) -- Kurz warten für die Antwort - - if #nearbyPlayers == 0 then - lib.notify({ - title = 'Lizenz-System', - description = 'Keine Spieler in der Nähe gefunden', - type = 'error' - }) - return - end - - local licenseOptions = {} - for _, license in ipairs(playerLicenses) do - if license.is_active then - local licenseConfig = Config.Licenses[license.license_type] - if licenseConfig then - table.insert(licenseOptions, { - title = licenseConfig.label, - description = 'Ausgestellt: ' .. license.issue_date, - icon = licenseConfig.icon, - onSelect = function() - selectPlayerToShow(license) - end + table.insert(players, { + id = serverId, + name = playerName, + distance = math.floor(distance * 100) / 100, + ped = targetPed }) end end end - - if #licenseOptions == 0 then - lib.notify({ - title = 'Lizenz-System', - description = 'Du hast keine aktiven Lizenzen zum Zeigen', - type = 'error' - }) - return - end - - lib.registerContext({ - id = 'select_license_to_show', - title = 'Lizenz auswählen', - menu = 'license_main_menu', - options = licenseOptions - }) - - lib.showContext('select_license_to_show') -end - -function selectPlayerToShow(license) - local playerOptions = {} - for _, player in ipairs(nearbyPlayers) do - table.insert(playerOptions, { - title = player.name, - description = 'Zeige diesem Spieler deine Lizenz', - icon = 'fas fa-user', - onSelect = function() - TriggerServerEvent('license-system:server:showLicense', player.id, license) - lib.notify({ - title = 'Lizenz-System', - description = 'Lizenz wurde ' .. player.name .. ' gezeigt', - type = 'success' - }) - end - }) - end - - lib.registerContext({ - id = 'select_player_to_show', - title = 'Spieler auswählen', - menu = 'select_license_to_show', - options = playerOptions - }) - - lib.showContext('select_player_to_show') + -- Nach Entfernung sortieren + table.sort(players, function(a, b) + return a.distance < b.distance + end) + + return players end --- Lizenz ausstellen -function issueLicenseMenu() +-- Berechtigung prüfen +local function hasPermission() local PlayerData = QBCore.Functions.GetPlayerData() - local authorizedLicenses = Config.AuthorizedJobs[PlayerData.job.name].canIssue + if not PlayerData or not PlayerData.job then return false end - local options = {} - for _, licenseType in ipairs(authorizedLicenses) do - local licenseConfig = Config.Licenses[licenseType] - if licenseConfig then - table.insert(options, { - title = licenseConfig.label, - description = 'Stelle einen ' .. licenseConfig.label .. ' aus', - icon = licenseConfig.icon, - onSelect = function() - selectPlayerForLicense(licenseType) - end - }) - end - end - - lib.registerContext({ - id = 'issue_license_menu', - title = 'Lizenz ausstellen', - menu = 'license_main_menu', - options = options - }) - - lib.showContext('issue_license_menu') + return Config.AuthorizedJobs[PlayerData.job.name] or false end -function selectPlayerForLicense(licenseType) - TriggerServerEvent('license-system:server:getNearbyPlayers') - - Wait(500) - - if #nearbyPlayers == 0 then - lib.notify({ - title = 'Lizenz-System', - description = 'Keine Spieler in der Nähe gefunden', - type = 'error' - }) +-- 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 - - local playerOptions = {} - for _, player in ipairs(nearbyPlayers) do - table.insert(playerOptions, { - title = player.name, - description = 'Stelle diesem Spieler eine Lizenz aus', - icon = 'fas fa-user', - onSelect = function() - openLicenseForm(player.id, licenseType) - end - }) - end - - lib.registerContext({ - id = 'select_player_for_license', - title = 'Spieler auswählen', - menu = 'issue_license_menu', - options = playerOptions - }) - - lib.showContext('select_player_for_license') -end - -function openLicenseForm(targetId, licenseType) - local licenseConfig = Config.Licenses[licenseType] - local input = {} - - if licenseConfig.fields.name then - table.insert(input, { - type = 'input', - isRequired = true, - label = 'Name', - placeholder = 'Vor- und Nachname' - }) - end - - if licenseConfig.fields.birthday then - table.insert(input, { - type = 'date', - isRequired = true, - label = 'Geburtsdatum' - }) - end - - if licenseConfig.fields.gender then - table.insert(input, { - type = 'select', - label = 'Geschlecht', - options = Config.Genders - }) - end - - if licenseConfig.fields.issue_date then - table.insert(input, { - type = 'date', - isRequired = true, - label = 'Ausstellungsdatum', - default = os.date('%Y-%m-%d') - }) - end - - if licenseConfig.fields.expire_date then - table.insert(input, { - type = 'date', - isRequired = true, - label = 'Ablaufdatum' - }) - end - - if licenseConfig.fields.classes then - local classOptions = {} - for _, class in ipairs(Config.LicenseClasses) do - table.insert(classOptions, { - value = class, - label = 'Klasse ' .. class - }) - end - - table.insert(input, { - type = 'multi-select', - label = 'Führerscheinklassen', - options = classOptions - }) - end - - local dialog = lib.inputDialog('Lizenz ausstellen - ' .. licenseConfig.label, input) - if dialog then - local licenseData = { - name = dialog[1], - birthday = dialog[2], - gender = dialog[3], - issue_date = dialog[4] or os.date('%Y-%m-%d'), - expire_date = dialog[5], - classes = dialog[6] - } - - TriggerServerEvent('license-system:server:issueLicense', targetId, licenseType, licenseData) - end -end - --- Lizenz-Overlay anzeigen -function showLicenseOverlay(licenseData, issuerName) - local licenseConfig = Config.Licenses[licenseData.license_type] - if not licenseConfig then return end - - -- NUI öffnen - SetNuiFocus(true, true) + debugPrint("Zeige Lizenz: " .. licenseData.license.license_type) + SendNUIMessage({ action = 'showLicense', - data = { - license = licenseData, - config = licenseConfig, - issuer = issuerName + data = licenseData + }) + + SetNuiFocus(true, true) + isMenuOpen = true +end + +-- Lizenz schließen +local function closeLicense() + SendNUIMessage({ + action = 'hideLicense' + }) + + SetNuiFocus(false, false) + isMenuOpen = false + debugPrint("Lizenz geschlossen") +end + +-- Spieler-Lizenz-Menü +local function openPlayerLicenseMenu(targetId, targetName) + debugPrint("Öffne Lizenz-Menü für Spieler: " .. targetName) + + QBCore.Functions.TriggerCallback('license-system:server:getPlayerLicenses', function(licenses) + 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' + } + + local statusIcon = license.is_active and '✅' or '❌' + local statusText = license.is_active and 'Gültig' or 'Ungültig' + local expireText = license.expire_date or 'Unbegrenzt' + + table.insert(menuOptions, { + title = licenseConfig.label, + description = 'Status: ' .. statusText .. ' | Gültig bis: ' .. expireText, + icon = licenseConfig.icon, + onSelect = function() + -- Lizenz anzeigen + 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 + + -- Aktionen hinzufügen + table.insert(menuOptions, { + title = '─────────────────', + disabled = true + }) + + -- Neue Lizenz ausstellen + 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 + }) + + -- Lizenz entziehen + table.insert(menuOptions, { + title = 'Lizenz entziehen', + description = 'Eine bestehende Lizenz entziehen', + icon = 'fas fa-minus', + onSelect = function() + openRevokeLicenseMenu(targetId, targetName, licenses) + end + }) + + else + table.insert(menuOptions, { + title = 'Keine Lizenzen gefunden', + description = 'Dieser Spieler hat keine Lizenzen', + icon = 'fas fa-exclamation-triangle', + 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 + }) + end + + -- Zurück-Option + table.insert(menuOptions, { + title = '← Zurück', + icon = 'fas fa-arrow-left', + onSelect = function() + openLicenseMenu() + end + }) + + lib.registerContext({ + id = 'player_licenses', + title = 'Lizenzen: ' .. targetName, + options = menuOptions + }) + + lib.showContext('player_licenses') + + end, targetId) +end + +-- Lizenz ausstellen Menü +local function openIssueLicenseMenu(targetId, targetName) + local menuOptions = {} + + for licenseType, config in pairs(Config.LicenseTypes) do + 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 = config.price .. ' $'}, + {label = 'Gültigkeitsdauer', value = config.validity_days and (config.validity_days .. ' Tage') or 'Unbegrenzt'} + } + }) + end + + table.insert(menuOptions, { + title = '← Zurück', + icon = 'fas fa-arrow-left', + onSelect = function() + openPlayerLicenseMenu(targetId, targetName) + end + }) + + lib.registerContext({ + id = 'issue_license', + title = 'Lizenz ausstellen: ' .. targetName, + options = menuOptions + }) + + lib.showContext('issue_license') +end + +-- Führerschein-Klassen Menü +local function openDriversLicenseClassMenu(targetId, targetName, licenseType) + local config = Config.LicenseTypes[licenseType] + local selectedClasses = {} + + local function updateMenu() + local menuOptions = {} + + 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 + + table.insert(menuOptions, { + title = '─────────────────', + disabled = true + }) + + table.insert(menuOptions, { + title = 'Bestätigen (' .. #selectedClasses .. ' Klassen)', + description = 'Führerschein mit ausgewählten Klassen ausstellen', + icon = 'fas fa-check', + disabled = #selectedClasses == 0, + onSelect = function() + confirmIssueLicense(targetId, targetName, licenseType, selectedClasses) + end + }) + + table.insert(menuOptions, { + title = '← Zurück', + icon = 'fas fa-arrow-left', + onSelect = function() + openIssueLicenseMenu(targetId, targetName) + end + }) + + lib.registerContext({ + id = 'drivers_license_classes', + title = 'Führerschein-Klassen: ' .. targetName, + options = menuOptions + }) + + lib.showContext('drivers_license_classes') + end + + updateMenu() +end + +-- Lizenz-Ausstellung bestätigen +local function confirmIssueLicense(targetId, targetName, licenseType, classes) + local config = Config.LicenseTypes[licenseType] + local classText = classes and table.concat(classes, ', ') or 'Keine' + + lib.registerContext({ + id = 'confirm_issue_license', + title = 'Lizenz ausstellen bestätigen', + options = { + { + title = 'Spieler: ' .. targetName, + disabled = true + }, + { + title = 'Lizenztyp: ' .. config.label, + disabled = true + }, + { + title = 'Klassen: ' .. classText, + disabled = true + }, + { + title = 'Kosten: ' .. config.price .. ' $', + disabled = true + }, + { + title = '─────────────────', + disabled = true + }, + { + title = '✅ Bestätigen', + description = 'Lizenz jetzt ausstellen', + icon = 'fas fa-check', + onSelect = function() + TriggerServerEvent('license-system:server:issueLicense', targetId, licenseType, classes) + lib.hideContext() + end + }, + { + title = '❌ Abbrechen', + description = 'Vorgang abbrechen', + icon = 'fas fa-times', + onSelect = function() + openIssueLicenseMenu(targetId, targetName) + end + } } }) + + lib.showContext('confirm_issue_license') end +-- Lizenz entziehen Menü +local function openRevokeLicenseMenu(targetId, targetName, licenses) + local menuOptions = {} + + for _, license in ipairs(licenses) do + if license.is_active 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 + }, + { + title = 'Lizenztyp: ' .. config.label, + disabled = true + }, + { + title = '─────────────────', + disabled = true + }, + { + title = '✅ Bestätigen', + description = 'Lizenz jetzt entziehen', + icon = 'fas fa-check', + onSelect = function() + TriggerServerEvent('license-system:server:revokeLicense', targetId, license.license_type) + lib.hideContext() + end + }, + { + title = '❌ Abbrechen', + description = 'Vorgang abbrechen', + icon = 'fas fa-times', + onSelect = function() + openRevokeLicenseMenu(targetId, targetName, licenses) + end + } + } + }) + + lib.showContext('confirm_revoke_license') + end + }) + end + end + + if #menuOptions == 0 then + table.insert(menuOptions, { + title = 'Keine aktiven Lizenzen', + description = 'Dieser Spieler hat keine aktiven Lizenzen', + icon = 'fas fa-exclamation-triangle', + disabled = true + }) + end + + table.insert(menuOptions, { + title = '← Zurück', + icon = 'fas fa-arrow-left', + onSelect = function() + openPlayerLicenseMenu(targetId, targetName) + end + }) + + lib.registerContext({ + id = 'revoke_license', + title = 'Lizenz entziehen: ' .. targetName, + options = menuOptions + }) + + lib.showContext('revoke_license') +end + +-- Hauptmenü für Lizenz-System +local function openLicenseMenu() + 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() + 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() + QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) + if licenseData then + showLicense(licenseData) + else + showNotification('Du hast keine ' .. config.label .. '!', 'error') + end + end, licenseType) + end + }) + end + + lib.registerContext({ + id = 'my_licenses', + title = 'Meine Lizenzen', + options = menuOptions + }) + + lib.showContext('my_licenses') +end + +-- Events +RegisterNetEvent('license-system:client:showLicense', function(targetId) + QBCore.Functions.TriggerCallback('license-system:server:getLicense', function(licenseData) + showLicense(licenseData) + end, targetId) +end) + +RegisterNetEvent('license-system:client:showMyLicense', function(licenseType) + QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) + showLicense(licenseData) + end, licenseType) +end) + +RegisterNetEvent('license-system:client:openCamera', function() + SendNUIMessage({ + action = 'openCamera' + }) +end) + -- NUI Callbacks RegisterNUICallback('closeLicense', function(data, cb) - SetNuiFocus(false, false) - cb('ok') + closeLicense() + safeCallback(cb, 'ok') end) --- Lizenz verwalten (für autorisierte Jobs) -function manageLicensesMenu() - -- Hier würde die Verwaltung für das Entziehen/Wiederherstellen von Lizenzen implementiert - -- Ähnlich wie die anderen Menüs, aber mit Suchfunktion nach Spielern - lib.notify({ - title = 'Lizenz-System', - description = 'Lizenz-Verwaltung wird implementiert...', - type = 'info' - }) -end - --- Foto vom Spieler machen -RegisterNetEvent('license-system:client:takePlayerPhoto', function(targetId) - local targetPed = GetPlayerPed(GetPlayerFromServerId(targetId)) - - if targetPed and targetPed ~= 0 then - -- Mugshot erstellen - local mugshot = RegisterPedheadshot(targetPed) - - -- Warten bis Mugshot geladen ist - while not IsPedheadshotReady(mugshot) do - Wait(100) - end - - -- Mugshot-Textur holen - local mugshotTxd = GetPedheadshotTxdString(mugshot) - - -- An Server senden - TriggerServerEvent('license-system:server:savePlayerPhoto', QBCore.Functions.GetPlayerData().citizenid, mugshotTxd) - - -- Mugshot wieder freigeben - UnregisterPedheadshot(mugshot) +RegisterNUICallback('savePhoto', function(data, cb) + if data.photo and data.citizenid then + TriggerServerEvent('license-system:server:savePhoto', data.citizenid, data.photo) + safeCallback(cb, 'ok') + else + safeCallback(cb, 'error') end end) +-- Commands +RegisterCommand(Config.Commands.license.name, function() + openLicenseMenu() +end, Config.Commands.license.restricted) + +RegisterCommand(Config.Commands.mylicense.name, function() + showMyLicenses() +end, Config.Commands.mylicense.restricted) + +-- Keybinds +if Config.Keybinds.open_license_menu then + RegisterKeyMapping(Config.Commands.license.name, Config.Keybinds.open_license_menu.description, 'keyboard', Config.Keybinds.open_license_menu.key) +end + +if Config.Keybinds.show_my_licenses then + RegisterKeyMapping(Config.Commands.mylicense.name, Config.Keybinds.show_my_licenses.description, 'keyboard', Config.Keybinds.show_my_licenses.key) +end + +-- Cleanup +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() == resourceName then + if isMenuOpen then + closeLicense() + end + debugPrint("License-System Client gestoppt") + end +end) + +-- Initialisierung +CreateThread(function() + debugPrint("License-System Client gestartet") +end) diff --git a/resources/[tools]/nordi_license/config.lua b/resources/[tools]/nordi_license/config.lua index eca2c9454..3e4e7d74d 100644 --- a/resources/[tools]/nordi_license/config.lua +++ b/resources/[tools]/nordi_license/config.lua @@ -1,107 +1,306 @@ Config = {} --- Keybind zum Öffnen des Menüs -Config.OpenKey = 'F6' +-- Allgemeine Einstellungen +Config.Debug = true +Config.UseBackgroundImages = true +Config.MaxLicenseAge = 365 -- Tage bis Ablauf +Config.RenewalDays = 30 -- Tage vor Ablauf für Verlängerung --- Command zum Öffnen -Config.Command = 'licenses' - --- Jobs die Lizenzen ausstellen können +-- Berechtigte Jobs Config.AuthorizedJobs = { - ['police'] = { - canIssue = {'id_card', 'drivers_license', 'weapon_license'}, - canRevoke = {'drivers_license', 'weapon_license'}, - canRestore = {'drivers_license', 'weapon_license'} - }, - ['admin'] = { - canIssue = {'id_card', 'drivers_license', 'passport', 'business_license'}, - canRevoke = {'drivers_license', 'business_license'}, - canRestore = {'drivers_license', 'business_license'} - }, - ['driving_school'] = { - canIssue = {'drivers_license'}, - canRevoke = {}, - canRestore = {} - } + ['police'] = true, + ['sheriff'] = true, + ['government'] = true, + ['judge'] = true, + ['lawyer'] = true, + ['ambulance'] = true, -- Für medizinische Lizenzen + ['mechanic'] = true -- Für Fahrzeug-Lizenzen } --- Verfügbare Lizenzen/Ausweise -Config.Licenses = { +-- Lizenz-Typen +Config.LicenseTypes = { ['id_card'] = { label = 'Personalausweis', icon = 'fas fa-id-card', - fields = { - name = true, - birthday = true, - gender = true, - issue_date = true, - expire_date = true, - classes = false - }, - template = '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', - fields = { - name = true, - birthday = true, - gender = true, - issue_date = true, - expire_date = true, - classes = true - }, - template = 'drivers_license' + 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-gun', - fields = { - name = true, - birthday = true, - gender = true, - issue_date = true, - expire_date = true, - classes = false - }, - template = 'weapon_license' + 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', - fields = { - name = true, - birthday = true, - gender = true, - issue_date = true, - expire_date = true, - classes = false - }, - template = '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', - fields = { - name = true, - birthday = false, - gender = false, - issue_date = true, - expire_date = true, - classes = false - }, - template = 'business_license' + 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 = 'ranger', + 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 = nil, + description = 'Berechtigung zum Angeln in öffentlichen Gewässern' } } --- Führerscheinklassen -Config.LicenseClasses = { - 'A', 'B', 'C', 'Boot', 'Flugzeug' +-- 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.85, -204.13, 37.22, 180.0) + } + }, + ['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) + } + } } --- Geschlechter -Config.Genders = { - {value = 'male', label = 'Männlich'}, - {value = 'female', label = 'Weiblich'}, - {value = 'other', label = 'Divers'} +-- 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/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index b28bb6aa9..94fc23174 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -1,312 +1,530 @@ local QBCore = exports['qb-core']:GetCoreObject() --- Datenbank Setup -MySQL.ready(function() - MySQL.Async.execute([[ - CREATE TABLE IF NOT EXISTS player_licenses ( - id INT AUTO_INCREMENT PRIMARY KEY, - citizenid VARCHAR(50) NOT NULL, - license_type VARCHAR(50) NOT NULL, - name VARCHAR(100) NOT NULL, - birthday VARCHAR(20), - gender VARCHAR(20), - issue_date VARCHAR(20) NOT NULL, - expire_date VARCHAR(20), - classes TEXT, - issued_by VARCHAR(50) NOT NULL, - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - INDEX(citizenid), - INDEX(license_type) - ) - ]]) -end) - --- Command registrieren -QBCore.Commands.Add(Config.Command, 'Öffne das Lizenz-Menü', {}, false, function(source, args) - TriggerClientEvent('license-system:client:openMenu', source) -end) - --- Events -RegisterNetEvent('license-system:server:getLicenses', function() - local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ?', { - Player.PlayerData.citizenid - }, function(result) - TriggerClientEvent('license-system:client:receiveLicenses', src, result) - end) -end) - -RegisterNetEvent('license-system:server:getNearbyPlayers', function() - local src = source - TriggerClientEvent('license-system:client:receiveNearbyPlayers', src, QBCore.Functions.GetPlayers()) -end) - -RegisterNetEvent('license-system:server:showLicense', function(targetId, licenseData) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end - - TriggerClientEvent('license-system:client:viewLicense', targetId, licenseData, Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname) -end) - -RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, licenseData) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - - if not Player or not TargetPlayer then return end - - -- Job-Berechtigung prüfen - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] or not hasPermission(Config.AuthorizedJobs[playerJob].canIssue, licenseType) then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') - return - end - - -- Lizenz in Datenbank speichern - MySQL.Async.execute('INSERT INTO player_licenses (citizenid, license_type, name, birthday, gender, issue_date, expire_date, classes, issued_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', { - TargetPlayer.PlayerData.citizenid, - licenseType, - licenseData.name, - licenseData.birthday, - licenseData.gender, - licenseData.issue_date, - licenseData.expire_date, - json.encode(licenseData.classes or {}), - Player.PlayerData.citizenid - }, function(insertId) - if insertId then - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt!', 'success') - TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. Config.Licenses[licenseType].label, 'success') - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') - end - end) -end) - -RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseId) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - - if not Player then return end - - -- Job-Berechtigung prüfen - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') - return - end - - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE id = ?', { - licenseId - }, function(affectedRows) - if affectedRows > 0 then - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen!', 'success') - if targetId then - TriggerClientEvent('QBCore:Notify', targetId, 'Eine deiner Lizenzen wurde entzogen!', 'error') - end - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') - end - end) -end) - -RegisterNetEvent('license-system:server:restoreLicense', function(targetId, licenseId) - local src = source - local Player = QBCore.Functions.GetPlayer(src) - - if not Player then return end - - -- Job-Berechtigung prüfen - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') - return - end - - MySQL.Async.execute('UPDATE player_licenses SET is_active = TRUE WHERE id = ?', { - licenseId - }, function(affectedRows) - if affectedRows > 0 then - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich wiederhergestellt!', 'success') - if targetId then - TriggerClientEvent('QBCore:Notify', targetId, 'Eine deiner Lizenzen wurde wiederhergestellt!', 'success') - end - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Wiederherstellen der Lizenz!', 'error') - end - end) -end) - -- Hilfsfunktionen -function hasPermission(permissions, licenseType) - for _, permission in ipairs(permissions) do - if permission == licenseType then - return true - end +local function debugPrint(message) + if Config.Debug then + print("^2[License-System] " .. message .. "^7") end +end + +local function safeCallback(cb, ...) + if cb and type(cb) == "function" then + cb(...) + else + debugPrint("^1Callback ist keine Funktion!^7") + end +end + +local function formatDate(date) + if not date then return nil end + return os.date("%d.%m.%Y", date) +end + +local function addDaysToDate(days) + return os.time() + (days * 24 * 60 * 60) +end + +local function isLicenseExpired(expireDate) + if not expireDate then return false end + return os.time() > expireDate +end + +local function getDaysUntilExpiry(expireDate) + if not expireDate then return nil end + local diff = expireDate - os.time() + return math.ceil(diff / (24 * 60 * 60)) +end + +-- Spieler-Daten abrufen +local function getPlayerData(source) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return nil end + + return { + citizenid = Player.PlayerData.citizenid, + name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, + birthday = Player.PlayerData.charinfo.birthdate, + gender = Player.PlayerData.charinfo.gender, + job = Player.PlayerData.job.name, + money = Player.PlayerData.money.cash + } +end + +-- Berechtigung prüfen +local function hasPermission(source, licenseType) + local playerData = getPlayerData(source) + if not playerData then return false end + + -- Admin-Check + if QBCore.Functions.HasPermission(source, 'admin') then + return true + end + + -- Job-Check + if Config.AuthorizedJobs[playerData.job] then + return true + end + + -- Spezifische Lizenz-Berechtigung + local licenseConfig = Config.LicenseTypes[licenseType] + if licenseConfig and licenseConfig.required_job then + return playerData.job == licenseConfig.required_job + end + return false end --- Erweiterte Funktionen für Lizenzverwaltung +-- Benötigte Items prüfen +local function hasRequiredItems(source, licenseType) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return false end + + local licenseConfig = Config.LicenseTypes[licenseType] + if not licenseConfig or not licenseConfig.required_items then return true end + + for _, item in ipairs(licenseConfig.required_items) do + local hasItem = Player.Functions.GetItemByName(item) + if not hasItem or hasItem.amount < 1 then + return false + end + end + + return true +end -RegisterNetEvent('license-system:server:searchPlayer', function(searchTerm) - local src = source - local Player = QBCore.Functions.GetPlayer(src) +-- Lizenz erstellen +local function createLicense(citizenid, licenseType, issuedBy, classes) + local licenseConfig = Config.LicenseTypes[licenseType] + if not licenseConfig then return false end - if not Player then return end + local issueDate = os.time() + local expireDate = nil - -- Job-Berechtigung prüfen - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') + if licenseConfig.can_expire and licenseConfig.validity_days then + expireDate = addDaysToDate(licenseConfig.validity_days) + end + + local licenseData = { + citizenid = citizenid, + license_type = licenseType, + issue_date = formatDate(issueDate), + expire_date = expireDate and formatDate(expireDate) or nil, + issued_by = issuedBy, + is_active = true, + classes = classes and json.encode(classes) or '[]', + created_at = issueDate + } + + MySQL.Async.insert('INSERT INTO player_licenses (citizenid, license_type, issue_date, expire_date, issued_by, is_active, classes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', { + licenseData.citizenid, + licenseData.license_type, + licenseData.issue_date, + licenseData.expire_date, + licenseData.issued_by, + licenseData.is_active, + licenseData.classes, + licenseData.created_at + }, function(insertId) + if insertId then + debugPrint("Lizenz erstellt: " .. licenseType .. " für " .. citizenid) + return true + else + debugPrint("^1Fehler beim Erstellen der Lizenz!^7") + return false + end + end) + + return true +end + +-- Callbacks +QBCore.Functions.CreateCallback('license-system:server:getLicense', function(source, cb, targetId) + debugPrint("getLicense aufgerufen für Spieler: " .. tostring(targetId)) + + local TargetPlayer = QBCore.Functions.GetPlayer(targetId) + if not TargetPlayer then + debugPrint("^1Ziel-Spieler nicht gefunden!^7") + safeCallback(cb, nil) return end - -- Spieler suchen - MySQL.Async.fetchAll('SELECT citizenid, charinfo FROM players WHERE JSON_EXTRACT(charinfo, "$.firstname") LIKE ? OR JSON_EXTRACT(charinfo, "$.lastname") LIKE ? LIMIT 10', { - '%' .. searchTerm .. '%', - '%' .. searchTerm .. '%' + local citizenid = TargetPlayer.PlayerData.citizenid + + MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND is_active = TRUE ORDER BY created_at DESC LIMIT 1', { + citizenid }, function(result) - local players = {} - for _, player in ipairs(result) do - local charinfo = json.decode(player.charinfo) - table.insert(players, { - citizenid = player.citizenid, - name = charinfo.firstname .. ' ' .. charinfo.lastname - }) + if result and result[1] then + local license = result[1] + + -- Spieler-Daten hinzufügen + license.name = TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname + license.birthday = TargetPlayer.PlayerData.charinfo.birthdate + license.gender = TargetPlayer.PlayerData.charinfo.gender + + -- Aussteller-Name abrufen + if license.issued_by then + MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { + license.issued_by + }, function(issuerName) + license.issued_by_name = issuerName or 'Unbekannt' + + local licenseData = { + license = license, + config = Config.LicenseTypes[license.license_type] or { + label = license.license_type, + icon = 'fas fa-id-card' + } + } + + debugPrint("Lizenz gefunden: " .. license.license_type) + safeCallback(cb, licenseData) + end) + else + license.issued_by_name = 'System' + + local licenseData = { + license = license, + config = Config.LicenseTypes[license.license_type] or { + label = license.license_type, + icon = 'fas fa-id-card' + } + } + + debugPrint("Lizenz gefunden: " .. license.license_type) + safeCallback(cb, licenseData) + end + else + debugPrint("Keine Lizenz gefunden für: " .. citizenid) + safeCallback(cb, nil) end - TriggerClientEvent('license-system:client:receiveSearchResults', src, players) end) end) -RegisterNetEvent('license-system:server:getPlayerLicenses', function(citizenid) - local src = source - local Player = QBCore.Functions.GetPlayer(src) +QBCore.Functions.CreateCallback('license-system:server:getMyLicense', function(source, cb, licenseType) + debugPrint("getMyLicense aufgerufen für Typ: " .. tostring(licenseType)) - if not Player then return end - - -- Job-Berechtigung prüfen - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') + local Player = QBCore.Functions.GetPlayer(source) + if not Player then + safeCallback(cb, nil) return end + local citizenid = Player.PlayerData.citizenid + + MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? AND is_active = TRUE ORDER BY created_at DESC LIMIT 1', { + citizenid, + licenseType + }, function(result) + if result and result[1] then + local license = result[1] + + -- Spieler-Daten hinzufügen + license.name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + license.birthday = Player.PlayerData.charinfo.birthdate + license.gender = Player.PlayerData.charinfo.gender + + -- Aussteller-Name abrufen + if license.issued_by then + MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { + license.issued_by + }, function(issuerName) + license.issued_by_name = issuerName or 'Unbekannt' + + local licenseData = { + license = license, + config = Config.LicenseTypes[license.license_type] or { + label = license.license_type, + icon = 'fas fa-id-card' + } + } + + safeCallback(cb, licenseData) + end) + else + license.issued_by_name = 'System' + + local licenseData = { + license = license, + config = Config.LicenseTypes[license.license_type] or { + label = license.license_type, + icon = 'fas fa-id-card' + } + } + + safeCallback(cb, licenseData) + end + else + debugPrint("Keine Lizenz vom Typ " .. licenseType .. " gefunden") + safeCallback(cb, nil) + end + end) +end) + +QBCore.Functions.CreateCallback('license-system:server:getPlayerLicenses', function(source, cb, targetId) + debugPrint("getPlayerLicenses aufgerufen für Spieler: " .. tostring(targetId)) + + local TargetPlayer = QBCore.Functions.GetPlayer(targetId) + if not TargetPlayer then + safeCallback(cb, {}) + return + end + + local citizenid = TargetPlayer.PlayerData.citizenid + MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY created_at DESC', { citizenid }, function(result) - TriggerClientEvent('license-system:client:receivePlayerLicenses', src, result, citizenid) + if result then + -- Spieler-Daten zu jeder Lizenz hinzufügen + for i, license in ipairs(result) do + license.name = TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname + license.birthday = TargetPlayer.PlayerData.charinfo.birthdate + license.gender = TargetPlayer.PlayerData.charinfo.gender + end + + debugPrint("Gefundene Lizenzen: " .. #result) + safeCallback(cb, result) + else + debugPrint("Keine Lizenzen gefunden") + safeCallback(cb, {}) + end end) end) --- Lizenz-Historie hinzufügen -function addLicenseHistory(licenseId, action, performedBy, performedByName, reason) - MySQL.Async.execute('INSERT INTO license_history (license_id, action, performed_by, performed_by_name, reason) VALUES (?, ?, ?, ?, ?)', { - licenseId, - action, - performedBy, - performedByName, - reason or '' - }) -end +QBCore.Functions.CreateCallback('license-system:server:canIssueLicense', function(source, cb, licenseType) + local hasAuth = hasPermission(source, licenseType) + safeCallback(cb, hasAuth) +end) --- Erweiterte Revoke-Funktion mit Grund -RegisterNetEvent('license-system:server:revokeLicenseWithReason', function(licenseId, reason) +-- Events +RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local TargetPlayer = QBCore.Functions.GetPlayer(targetId) + + if not Player or not TargetPlayer then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_players_nearby.message, Config.Notifications.no_players_nearby.type) + return + end + + -- Berechtigung prüfen + if not hasPermission(src, licenseType) then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) + return + end + + -- Lizenz-Konfiguration prüfen + local licenseConfig = Config.LicenseTypes[licenseType] + if not licenseConfig then + TriggerClientEvent('QBCore:Notify', src, 'Unbekannter Lizenztyp!', 'error') + return + end + + -- Benötigte Items prüfen + if not hasRequiredItems(targetId, licenseType) then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.missing_items.message, Config.Notifications.missing_items.type) + return + end + + -- Geld prüfen (falls Kosten anfallen) + if licenseConfig.price > 0 then + if TargetPlayer.PlayerData.money.cash < licenseConfig.price then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.insufficient_funds.message, Config.Notifications.insufficient_funds.type) + return + end + + -- Geld abziehen + TargetPlayer.Functions.RemoveMoney('cash', licenseConfig.price, 'license-fee') + end + + -- Alte Lizenz deaktivieren + MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE citizenid = ? AND license_type = ?', { + TargetPlayer.PlayerData.citizenid, + licenseType + }) + + -- Neue Lizenz erstellen + local success = createLicense(TargetPlayer.PlayerData.citizenid, licenseType, Player.PlayerData.citizenid, classes) + + if success then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_granted.message, Config.Notifications.license_granted.type) + TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue ' .. licenseConfig.label .. ' erhalten!', 'success') + + -- Log erstellen + debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' eine ' .. licenseConfig.label .. ' ausgestellt') + else + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') + end +end) + +RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local TargetPlayer = QBCore.Functions.GetPlayer(targetId) + + if not Player or not TargetPlayer then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_players_nearby.message, Config.Notifications.no_players_nearby.type) + return + end + + -- Berechtigung prüfen + if not hasPermission(src, licenseType) then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) + return + end + + -- Lizenz deaktivieren + MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE citizenid = ? AND license_type = ? AND is_active = TRUE', { + TargetPlayer.PlayerData.citizenid, + licenseType + }, function(affectedRows) + if affectedRows > 0 then + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_revoked.message, Config.Notifications.license_revoked.type) + TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (Config.LicenseTypes[licenseType]?.label or licenseType) .. ' wurde entzogen!', 'error') + + -- Log erstellen + debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' die ' .. (Config.LicenseTypes[licenseType]?.label or licenseType) .. ' entzogen') + else + TriggerClientEvent('QBCore:Notify', src, 'Keine aktive Lizenz gefunden!', 'error') + end + end) +end) + +RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoData) local src = source local Player = QBCore.Functions.GetPlayer(src) if not Player then return end - local playerJob = Player.PlayerData.job.name - if not Config.AuthorizedJobs[playerJob] then - TriggerClientEvent('QBCore:Notify', src, 'Du hast keine Berechtigung für diese Aktion!', 'error') - return - end - - local playerName = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname - - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE, revoked_by = ?, revoked_by_name = ?, revoked_date = NOW(), revoked_reason = ? WHERE id = ?', { - Player.PlayerData.citizenid, - playerName, - reason, - licenseId + -- Foto in der Datenbank speichern + MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = TRUE', { + photoData, + citizenid }, function(affectedRows) if affectedRows > 0 then - addLicenseHistory(licenseId, 'revoked', Player.PlayerData.citizenid, playerName, reason) - TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen!', 'success') + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.photo_saved.message, Config.Notifications.photo_saved.type) + debugPrint("Foto gespeichert für: " .. citizenid) else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Speichern des Fotos!', 'error') end end) end) --- Automatische Lizenz-Überprüfung (läuft alle 24 Stunden) +-- Admin-Kommandos +QBCore.Commands.Add('givelicense', 'Lizenz an Spieler vergeben', { + {name = 'id', help = 'Spieler ID'}, + {name = 'type', help = 'Lizenztyp'}, + {name = 'classes', help = 'Klassen (optional)'} +}, true, function(source, args) + local targetId = tonumber(args[1]) + local licenseType = args[2] + local classes = args[3] and {args[3]} or nil + + if not targetId or not licenseType then + TriggerClientEvent('QBCore:Notify', source, 'Verwendung: /givelicense [id] [typ] [klassen]', 'error') + return + end + + if not Config.LicenseTypes[licenseType] then + TriggerClientEvent('QBCore:Notify', source, 'Unbekannter Lizenztyp!', 'error') + return + end + + TriggerEvent('license-system:server:issueLicense', targetId, licenseType, classes) +end, 'admin') + +QBCore.Commands.Add('revokelicense', 'Lizenz entziehen', { + {name = 'id', help = 'Spieler ID'}, + {name = 'type', help = 'Lizenztyp'} +}, true, function(source, args) + local targetId = tonumber(args[1]) + local licenseType = args[2] + + if not targetId or not licenseType then + TriggerClientEvent('QBCore:Notify', source, 'Verwendung: /revokelicense [id] [typ]', 'error') + return + end + + TriggerEvent('license-system:server:revokeLicense', targetId, licenseType) +end, 'admin') + +-- Cleanup-Task für abgelaufene Lizenzen +if Config.Database.auto_cleanup then + CreateThread(function() + while true do + Wait(24 * 60 * 60 * 1000) -- Einmal täglich + + local cutoffDate = os.time() - (Config.Database.cleanup_days * 24 * 60 * 60) + + MySQL.Async.execute('DELETE FROM player_licenses WHERE is_active = FALSE AND created_at < ?', { + cutoffDate + }, function(affectedRows) + if affectedRows > 0 then + debugPrint("Cleanup: " .. affectedRows .. " alte Lizenzen gelöscht") + end + end) + end + end) +end + +-- Lizenz-Ablauf-Checker CreateThread(function() while true do - Wait(24 * 60 * 60 * 1000) -- 24 Stunden + Wait(60 * 60 * 1000) -- Jede Stunde - -- Abgelaufene Lizenzen deaktivieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE expire_date < CURDATE() AND is_active = TRUE', {}, function(affectedRows) - if affectedRows > 0 then - print('^3[License-System]^7 ' .. affectedRows .. ' abgelaufene Lizenzen wurden deaktiviert.') + MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE is_active = TRUE AND expire_date IS NOT NULL', {}, function(result) + if result then + for _, license in ipairs(result) do + local expireTime = os.time({ + year = tonumber(string.sub(license.expire_date, 7, 10)), + month = tonumber(string.sub(license.expire_date, 4, 5)), + day = tonumber(string.sub(license.expire_date, 1, 2)) + }) + + if isLicenseExpired(expireTime) then + -- Lizenz als abgelaufen markieren + MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE id = ?', { + license.id + }) + + debugPrint("Lizenz abgelaufen: " .. license.license_type .. " für " .. license.citizenid) + end + end end end) end end) --- Export-Funktionen für andere Ressourcen -exports('hasLicense', function(citizenid, licenseType) - local result = MySQL.Sync.fetchScalar('SELECT COUNT(*) FROM player_licenses WHERE citizenid = ? AND license_type = ? AND is_active = TRUE', { - citizenid, licenseType - }) - return result > 0 -end) - -exports('getLicenses', function(citizenid) - return MySQL.Sync.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND is_active = TRUE', { - citizenid - }) -end) - -exports('hasLicenseClass', function(citizenid, class) - local result = MySQL.Sync.fetchAll('SELECT classes FROM player_licenses WHERE citizenid = ? AND license_type = "drivers_license" AND is_active = TRUE', { - citizenid - }) - - for _, license in ipairs(result) do - if license.classes then - local classes = json.decode(license.classes) - for _, licenseClass in ipairs(classes) do - if licenseClass == class then - return true - end - end - end - end - return false -end) - --- Spieler-Foto aus QBCore holen -RegisterNetEvent('license-system:server:getPlayerPhoto', function(targetId) - local src = source - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - - if TargetPlayer then - -- Mugshot vom Spieler machen - TriggerClientEvent('license-system:client:takePlayerPhoto', src, targetId) +-- Resource Start/Stop Events +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() == resourceName then + debugPrint("License-System Server gestartet") + + -- Datenbank-Tabelle erstellen falls nicht vorhanden + MySQL.Async.execute([[ + CREATE TABLE IF NOT EXISTS player_licenses ( + id INT AUTO_INCREMENT PRIMARY KEY, + citizenid VARCHAR(50) NOT NULL, + license_type VARCHAR(50) NOT NULL, + issue_date VARCHAR(20) NOT NULL, + expire_date VARCHAR(20) NULL, + issued_by VARCHAR(50) NULL, + is_active BOOLEAN DEFAULT TRUE, + classes TEXT NULL, + photo_url TEXT NULL, + notes TEXT NULL, + created_at BIGINT NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX idx_citizenid (citizenid), + INDEX idx_license_type (license_type), + INDEX idx_active (is_active) + ) + ]]) end end) --- Foto-URL speichern -RegisterNetEvent('license-system:server:savePlayerPhoto', function(citizenid, photoUrl) - MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND id = ?', { - photoUrl, - citizenid, - -- Hier die aktuelle Lizenz-ID - }) +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() == resourceName then + debugPrint("License-System Server gestoppt") + end end)