local QBCore = exports['qb-core']:GetCoreObject() -- Hilfsfunktionen 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 -- 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 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 = 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 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) 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 end) end) QBCore.Functions.CreateCallback('license-system:server:getMyLicense', function(source, cb, licenseType) debugPrint("getMyLicense aufgerufen für Typ: " .. tostring(licenseType)) 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) 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) QBCore.Functions.CreateCallback('license-system:server:canIssueLicense', function(source, cb, licenseType) local hasAuth = hasPermission(source, licenseType) safeCallback(cb, hasAuth) end) -- 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 -- 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 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 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(60 * 60 * 1000) -- Jede Stunde 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) -- 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) AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Server gestoppt") end end)