local QBCore = exports['qb-core']:GetCoreObject() -- Lokale Variablen local licenseCache = {} -- Hilfsfunktionen local function debugPrint(message) if Config.Debug then print("^2[License-System Server] " .. message .. "^7") end end local function safeCallback(cb, ...) if cb and type(cb) == "function" then cb(...) return true else debugPrint("^1FEHLER: Callback ist keine Funktion! Typ: " .. type(cb) .. "^7") return false end end local function formatDate(timestamp) if not timestamp then return nil end return os.date("%d.%m.%Y", timestamp) end local function addDaysToDate(days) return os.time() + (days * 24 * 60 * 60) end local function isLicenseExpired(expireDate) if not expireDate then return false end local expireTime = os.time({ year = tonumber(string.sub(expireDate, 7, 10)), month = tonumber(string.sub(expireDate, 4, 5)), day = tonumber(string.sub(expireDate, 1, 2)) }) return os.time() > expireTime 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, firstname = Player.PlayerData.charinfo.firstname, lastname = 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 -- 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 -- Lizenz erstellen local function createLicense(citizenid, licenseType, issuedBy, classes) local licenseConfig = Config.LicenseTypes[licenseType] if not licenseConfig then debugPrint("^1Lizenz-Konfiguration nicht gefunden: " .. licenseType .. "^7") return false end local issueDate = os.time() local expireDate = nil 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 = 1, classes = classes and json.encode(classes) or '[]', created_at = issueDate } debugPrint("Erstelle Lizenz: " .. licenseType .. " für " .. citizenid) 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 erfolgreich erstellt mit ID: " .. insertId) -- Cache aktualisieren if not licenseCache[citizenid] then licenseCache[citizenid] = {} end licenseCache[citizenid][licenseType] = licenseData else debugPrint("^1Fehler beim Erstellen der Lizenz in der Datenbank!^7") end end) return true end -- Aussteller-Name abrufen local function getIssuerName(citizenid, callback) if not citizenid then callback('System') return end MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { citizenid }, function(issuerName) callback(issuerName or 'Unbekannt') end) end -- Callbacks QBCore.Functions.CreateCallback('license-system:server:getLicense', function(source, cb, targetId) debugPrint("getLicense Callback - Source: " .. source .. ", Target: " .. tostring(targetId)) if not cb or type(cb) ~= "function" then debugPrint("^1FEHLER: Ungültiger Callback in getLicense!^7") return end local TargetPlayer = QBCore.Functions.GetPlayer(targetId) if not TargetPlayer then debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") safeCallback(cb, nil) return end local citizenid = TargetPlayer.PlayerData.citizenid debugPrint("Suche Lizenz für CitizenID: " .. citizenid) MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1', { citizenid }, function(result) if result and result[1] then local license = result[1] debugPrint("Lizenz gefunden: " .. license.license_type) -- 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 getIssuerName(license.issued_by, function(issuerName) license.issued_by_name = issuerName local licenseData = { license = license, config = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card', color = '#667eea' } } safeCallback(cb, licenseData) end) else debugPrint("Keine aktive Lizenz gefunden für: " .. citizenid) safeCallback(cb, nil) end end) end) QBCore.Functions.CreateCallback('license-system:server:getMyLicense', function(source, cb, licenseType) debugPrint("getMyLicense Callback - Source: " .. source .. ", Typ: " .. tostring(licenseType)) if not cb or type(cb) ~= "function" then debugPrint("^1FEHLER: Ungültiger Callback in getMyLicense!^7") return end local Player = QBCore.Functions.GetPlayer(source) if not Player then debugPrint("^1Spieler nicht gefunden: " .. source .. "^7") safeCallback(cb, nil) return end local citizenid = Player.PlayerData.citizenid debugPrint("Suche eigene Lizenz - CitizenID: " .. citizenid .. ", Typ: " .. licenseType) MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1', { citizenid, licenseType }, function(result) if result and result[1] then local license = result[1] debugPrint("Eigene Lizenz gefunden: " .. license.license_type) -- 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 getIssuerName(license.issued_by, function(issuerName) license.issued_by_name = issuerName local licenseData = { license = license, config = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card', color = '#667eea' } } safeCallback(cb, licenseData) end) else debugPrint("Keine eigene Lizenz vom Typ " .. licenseType .. " gefunden") safeCallback(cb, nil) end end) end) QBCore.Functions.CreateCallback('license-system:server:getPlayerLicenses', function(source, cb, targetId) debugPrint("getPlayerLicenses Callback - Source: " .. source .. ", Target: " .. tostring(targetId)) if not cb or type(cb) ~= "function" then debugPrint("^1FEHLER: Ungültiger Callback in getPlayerLicenses!^7") return end local TargetPlayer = QBCore.Functions.GetPlayer(targetId) if not TargetPlayer then debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") safeCallback(cb, {}) return end local citizenid = TargetPlayer.PlayerData.citizenid debugPrint("Suche alle Lizenzen für CitizenID: " .. citizenid) MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY created_at DESC', { citizenid }, function(result) if result then debugPrint("Gefundene Lizenzen: " .. #result) -- 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 license.issued_by_name = 'System' -- Wird später durch echten Namen ersetzt end safeCallback(cb, result) else debugPrint("Keine Lizenzen gefunden") safeCallback(cb, {}) end end) end) QBCore.Functions.CreateCallback('license-system:server:canIssueLicense', function(source, cb, licenseType) debugPrint("canIssueLicense Callback - Source: " .. source .. ", Typ: " .. tostring(licenseType)) if not cb or type(cb) ~= "function" then debugPrint("^1FEHLER: Ungültiger Callback in canIssueLicense!^7") return end local hasAuth = hasPermission(source, licenseType) debugPrint("Berechtigung für Lizenz " .. licenseType .. ": " .. tostring(hasAuth)) safeCallback(cb, hasAuth) end) -- Events RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) local src = source debugPrint("Event: issueLicense - Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) 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') debugPrint("Geld abgezogen: " .. licenseConfig.price .. "$ von " .. TargetPlayer.PlayerData.charinfo.firstname) end -- Alte Lizenz deaktivieren MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?', { TargetPlayer.PlayerData.citizenid, licenseType }, function(affectedRows) debugPrint("Alte Lizenzen deaktiviert: " .. affectedRows) end) -- 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 debugPrint("Event: revokeLicense - Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) 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 = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1', { 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] and Config.LicenseTypes[licenseType].label or licenseType) .. ' wurde entzogen!', 'error') -- Cache aktualisieren if licenseCache[TargetPlayer.PlayerData.citizenid] then licenseCache[TargetPlayer.PlayerData.citizenid][licenseType] = nil end debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' die ' .. (Config.LicenseTypes[licenseType] and 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 debugPrint("Event: savePhoto für CitizenID: " .. citizenid) local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Foto in der Datenbank speichern MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = 1', { photoData, citizenid }, function(affectedRows) if affectedRows > 0 then 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 Speichern des Fotos!', 'error') end end) end) -- 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 debugPrint("Admin-Command: givelicense - ID: " .. tostring(targetId) .. ", Typ: " .. tostring(licenseType)) 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] debugPrint("Admin-Command: revokelicense - ID: " .. tostring(targetId) .. ", Typ: " .. tostring(licenseType)) 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 = 0 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(60 * 60 * 1000) -- Jede Stunde MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE is_active = 1 AND expire_date IS NOT NULL', {}, function(result) if result then for _, license in ipairs(result) do if isLicenseExpired(license.expire_date) then -- Lizenz als abgelaufen markieren MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE id = ?', { license.id }) debugPrint("Lizenz abgelaufen: " .. license.license_type .. " für " .. license.citizenid) end end end end) end end) -- 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 TINYINT(1) DEFAULT 1, 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) ) ]], {}, function(success) if success then debugPrint("Datenbank-Tabelle erfolgreich erstellt/überprüft") else debugPrint("^1Fehler beim Erstellen der Datenbank-Tabelle!^7") end end) end end) AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Server gestoppt") -- Cache leeren licenseCache = {} end end)