From 4f3cd097a2b9d857d2c6c7a75cda60ab8e4ae3f5 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 06:14:47 +0200 Subject: [PATCH] ed --- .../[tools]/nordi_license/client/main.lua | 391 ++++++++++++++++++ resources/[tools]/nordi_license/config.lua | 107 +++++ .../[tools]/nordi_license/fxmanifest.lua | 35 ++ .../[tools]/nordi_license/html/index.html | 60 +++ .../[tools]/nordi_license/html/script.js | 103 +++++ .../[tools]/nordi_license/html/style.css | 220 ++++++++++ .../[tools]/nordi_license/server/main.lua | 292 +++++++++++++ 7 files changed, 1208 insertions(+) create mode 100644 resources/[tools]/nordi_license/client/main.lua create mode 100644 resources/[tools]/nordi_license/config.lua create mode 100644 resources/[tools]/nordi_license/fxmanifest.lua create mode 100644 resources/[tools]/nordi_license/html/index.html create mode 100644 resources/[tools]/nordi_license/html/script.js create mode 100644 resources/[tools]/nordi_license/html/style.css create mode 100644 resources/[tools]/nordi_license/server/main.lua diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua new file mode 100644 index 000000000..f00da1395 --- /dev/null +++ b/resources/[tools]/nordi_license/client/main.lua @@ -0,0 +1,391 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local playerLicenses = {} +local nearbyPlayers = {} + +-- Keybind registrieren +RegisterKeyMapping(Config.Command, 'Lizenz-Menü öffnen', 'keyboard', Config.OpenKey) + +-- Events +RegisterNetEvent('license-system:client:openMenu', function() + TriggerServerEvent('license-system:server:getLicenses') +end) + +RegisterNetEvent('license-system:client:receiveLicenses', function(licenses) + playerLicenses = licenses + openMainMenu() +end) + +RegisterNetEvent('license-system:client:receiveNearbyPlayers', function(players) + nearbyPlayers = {} + 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) + + 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 + }) + 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') +end + +-- Lizenz ausstellen +function issueLicenseMenu() + local PlayerData = QBCore.Functions.GetPlayerData() + local authorizedLicenses = Config.AuthorizedJobs[PlayerData.job.name].canIssue + + 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') +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' + }) + 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) + SendNUIMessage({ + action = 'showLicense', + data = { + license = licenseData, + config = licenseConfig, + issuer = issuerName + } + }) +end + +-- NUI Callbacks +RegisterNUICallback('closeLicense', function(data, cb) + SetNuiFocus(false, false) + 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 diff --git a/resources/[tools]/nordi_license/config.lua b/resources/[tools]/nordi_license/config.lua new file mode 100644 index 000000000..11834ebb5 --- /dev/null +++ b/resources/[tools]/nordi_license/config.lua @@ -0,0 +1,107 @@ +Config = {} + +-- Keybind zum Öffnen des Menüs +Config.OpenKey = 'F6' + +-- Command zum Öffnen +Config.Command = 'licenses' + +-- Jobs die Lizenzen ausstellen können +Config.AuthorizedJobs = { + ['police'] = { + canIssue = {'id_card', 'drivers_license', 'weapon_license'}, + canRevoke = {'drivers_license', 'weapon_license'}, + canRestore = {'drivers_license', 'weapon_license'} + }, + ['government'] = { + 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 = {} + } +} + +-- Verfügbare Lizenzen/Ausweise +Config.Licenses = { + ['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' + }, + ['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' + }, + ['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' + }, + ['passport'] = { + label = 'Reisepass', + icon = 'fas fa-passport', + fields = { + name = true, + birthday = true, + gender = true, + issue_date = true, + expire_date = true, + classes = false + }, + template = 'passport' + }, + ['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' + } +} + +-- Führerscheinklassen +Config.LicenseClasses = { + 'A', 'B', 'C', 'Boot', 'Flugzeug' +} + +-- Geschlechter +Config.Genders = { + {value = 'male', label = 'Männlich'}, + {value = 'female', label = 'Weiblich'}, + {value = 'other', label = 'Divers'} +} diff --git a/resources/[tools]/nordi_license/fxmanifest.lua b/resources/[tools]/nordi_license/fxmanifest.lua new file mode 100644 index 000000000..2ddcd9f06 --- /dev/null +++ b/resources/[tools]/nordi_license/fxmanifest.lua @@ -0,0 +1,35 @@ +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/index.html b/resources/[tools]/nordi_license/html/index.html new file mode 100644 index 000000000..07e45cf8b --- /dev/null +++ b/resources/[tools]/nordi_license/html/index.html @@ -0,0 +1,60 @@ + + + + + + License System + + + + + + + + diff --git a/resources/[tools]/nordi_license/html/script.js b/resources/[tools]/nordi_license/html/script.js new file mode 100644 index 000000000..fd6da9c7a --- /dev/null +++ b/resources/[tools]/nordi_license/html/script.js @@ -0,0 +1,103 @@ +let currentLicense = 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; + } +}); + +// Lizenz anzeigen +function showLicense(data) { + currentLicense = data; + const container = document.getElementById('license-container'); + const card = document.getElementById('license-card'); + + // Lizenztyp-spezifische Klasse hinzufügen + card.className = 'license-card ' + data.license.license_type; + + // Header befüllen + document.querySelector('.license-title').textContent = data.config.label; + document.querySelector('.license-icon').className = 'license-icon ' + data.config.icon; + + // Lizenzinformationen befüllen + document.getElementById('license-name').textContent = data.license.name || 'N/A'; + document.getElementById('license-birthday').textContent = data.license.birthday || 'N/A'; + document.getElementById('license-gender').textContent = formatGender(data.license.gender) || 'N/A'; + document.getElementById('license-issue').textContent = data.license.issue_date || 'N/A'; + document.getElementById('license-expire').textContent = data.license.expire_date || 'N/A'; + + // Klassen anzeigen (nur bei Führerschein) + const classesRow = document.getElementById('license-classes-row'); + if (data.license.classes && data.license.classes !== '[]') { + try { + const classes = JSON.parse(data.license.classes); + if (classes && classes.length > 0) { + document.getElementById('license-classes').textContent = classes.join(', '); + classesRow.style.display = 'flex'; + } else { + classesRow.style.display = 'none'; + } + } catch (e) { + classesRow.style.display = 'none'; + } + } else { + classesRow.style.display = 'none'; + } + + // Status anzeigen + const statusElement = document.getElementById('license-status'); + if (data.license.is_active) { + statusElement.textContent = '✅ Gültig'; + statusElement.className = 'license-status active'; + } else { + statusElement.textContent = '❌ Ungültig'; + statusElement.className = 'license-status inactive'; + } + + // Container anzeigen + container.classList.remove('hidden'); +} + +// Geschlecht formatieren +function formatGender(gender) { + const genderMap = { + 'male': 'Männlich', + 'female': 'Weiblich', + 'other': 'Divers' + }; + return genderMap[gender] || gender; +} + +// Lizenz schließen +function closeLicense() { + const container = document.getElementById('license-container'); + container.classList.add('hidden'); + + // Callback an FiveM senden + fetch(`https://${GetParentResourceName()}/closeLicense`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: JSON.stringify({}) + }); +} + +// ESC-Taste zum Schließen +document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeLicense(); + } +}); + +// Klick außerhalb der Karte zum Schließen +document.getElementById('license-container').addEventListener('click', function(event) { + if (event.target === this) { + closeLicense(); + } +}); diff --git a/resources/[tools]/nordi_license/html/style.css b/resources/[tools]/nordi_license/html/style.css new file mode 100644 index 000000000..cd5e99b71 --- /dev/null +++ b/resources/[tools]/nordi_license/html/style.css @@ -0,0 +1,220 @@ +* { + 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; + font-size: 48px; + color: rgba(255, 255, 255, 0.7); + flex-shrink: 0; +} + +.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; +} diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua new file mode 100644 index 000000000..216a87e07 --- /dev/null +++ b/resources/[tools]/nordi_license/server/main.lua @@ -0,0 +1,292 @@ +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 + end + return false +end + +-- Erweiterte Funktionen für Lizenzverwaltung + +RegisterNetEvent('license-system:server:searchPlayer', function(searchTerm) + 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 + + -- 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 .. '%' + }, 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 + }) + 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) + + 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.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY created_at DESC', { + citizenid + }, function(result) + TriggerClientEvent('license-system:client:receivePlayerLicenses', src, result, citizenid) + 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 + +-- Erweiterte Revoke-Funktion mit Grund +RegisterNetEvent('license-system:server:revokeLicenseWithReason', function(licenseId, reason) + 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 + }, function(affectedRows) + if affectedRows > 0 then + addLicenseHistory(licenseId, 'revoked', Player.PlayerData.citizenid, playerName, reason) + TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen!', 'success') + else + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') + end + end) +end) + +-- Automatische Lizenz-Überprüfung (läuft alle 24 Stunden) +CreateThread(function() + while true do + Wait(24 * 60 * 60 * 1000) -- 24 Stunden + + -- 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.') + 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)