2025-08-04 06:14:47 +02:00
local QBCore = exports [ ' qb-core ' ] : GetCoreObject ( )
2025-08-04 07:40:06 +02:00
-- Hilfsfunktionen
local function debugPrint ( message )
if Config.Debug then
print ( " ^2[License-System] " .. message .. " ^7 " )
end
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local function safeCallback ( cb , ... )
if cb and type ( cb ) == " function " then
cb ( ... )
else
debugPrint ( " ^1Callback ist keine Funktion!^7 " )
end
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local function formatDate ( date )
if not date then return nil end
return os.date ( " %d.%m.%Y " , date )
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local function addDaysToDate ( days )
return os.time ( ) + ( days * 24 * 60 * 60 )
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local function isLicenseExpired ( expireDate )
if not expireDate then return false end
return os.time ( ) > expireDate
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local function getDaysUntilExpiry ( expireDate )
if not expireDate then return nil end
local diff = expireDate - os.time ( )
return math.ceil ( diff / ( 24 * 60 * 60 ) )
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
return false
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end
2025-08-04 07:40:06 +02:00
return true
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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 ,
2025-08-04 06:14:47 +02:00
licenseData.issue_date ,
licenseData.expire_date ,
2025-08-04 07:40:06 +02:00
licenseData.issued_by ,
licenseData.is_active ,
licenseData.classes ,
licenseData.created_at
2025-08-04 06:14:47 +02:00
} , function ( insertId )
if insertId then
2025-08-04 07:40:06 +02:00
debugPrint ( " Lizenz erstellt: " .. licenseType .. " für " .. citizenid )
return true
2025-08-04 06:14:47 +02:00
else
2025-08-04 07:40:06 +02:00
debugPrint ( " ^1Fehler beim Erstellen der Lizenz!^7 " )
return false
2025-08-04 06:14:47 +02:00
end
end )
2025-08-04 07:40:06 +02:00
return true
end
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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 )
2025-08-04 06:14:47 +02:00
return
end
2025-08-04 07:40:06 +02:00
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 )
2025-08-04 06:14:47 +02:00
end
else
2025-08-04 07:40:06 +02:00
debugPrint ( " Keine Lizenz gefunden für: " .. citizenid )
safeCallback ( cb , nil )
2025-08-04 06:14:47 +02:00
end
end )
end )
2025-08-04 07:40:06 +02:00
QBCore.Functions . CreateCallback ( ' license-system:server:getMyLicense ' , function ( source , cb , licenseType )
debugPrint ( " getMyLicense aufgerufen für Typ: " .. tostring ( licenseType ) )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
local Player = QBCore.Functions . GetPlayer ( source )
if not Player then
safeCallback ( cb , nil )
2025-08-04 06:14:47 +02:00
return
end
2025-08-04 07:40:06 +02:00
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 )
2025-08-04 06:14:47 +02:00
end
else
2025-08-04 07:40:06 +02:00
debugPrint ( " Keine Lizenz vom Typ " .. licenseType .. " gefunden " )
safeCallback ( cb , nil )
2025-08-04 06:14:47 +02:00
end
end )
end )
2025-08-04 07:40:06 +02:00
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
2025-08-04 06:14:47 +02:00
end
2025-08-04 07:40:06 +02:00
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 )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
QBCore.Functions . CreateCallback ( ' license-system:server:canIssueLicense ' , function ( source , cb , licenseType )
local hasAuth = hasPermission ( source , licenseType )
safeCallback ( cb , hasAuth )
end )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- Events
RegisterNetEvent ( ' license-system:server:issueLicense ' , function ( targetId , licenseType , classes )
2025-08-04 06:14:47 +02:00
local src = source
local Player = QBCore.Functions . GetPlayer ( src )
2025-08-04 07:40:06 +02:00
local TargetPlayer = QBCore.Functions . GetPlayer ( targetId )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
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
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- Berechtigung prüfen
if not hasPermission ( src , licenseType ) then
TriggerClientEvent ( ' QBCore:Notify ' , src , Config.Notifications . no_permission.message , Config.Notifications . no_permission.type )
2025-08-04 06:14:47 +02:00
return
end
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end )
2025-08-04 07:40:06 +02:00
RegisterNetEvent ( ' license-system:server:revokeLicense ' , function ( targetId , licenseType )
2025-08-04 06:14:47 +02:00
local src = source
local Player = QBCore.Functions . GetPlayer ( src )
2025-08-04 07:40:06 +02:00
local TargetPlayer = QBCore.Functions . GetPlayer ( targetId )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
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
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- Berechtigung prüfen
if not hasPermission ( src , licenseType ) then
TriggerClientEvent ( ' QBCore:Notify ' , src , Config.Notifications . no_permission.message , Config.Notifications . no_permission.type )
2025-08-04 06:14:47 +02:00
return
end
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end )
end )
2025-08-04 07:40:06 +02:00
RegisterNetEvent ( ' license-system:server:savePhoto ' , function ( citizenid , photoData )
2025-08-04 06:14:47 +02:00
local src = source
local Player = QBCore.Functions . GetPlayer ( src )
if not Player then return end
2025-08-04 07:40:06 +02:00
-- Foto in der Datenbank speichern
MySQL.Async . execute ( ' UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = TRUE ' , {
photoData ,
citizenid
2025-08-04 06:14:47 +02:00
} , function ( affectedRows )
if affectedRows > 0 then
2025-08-04 07:40:06 +02:00
TriggerClientEvent ( ' QBCore:Notify ' , src , Config.Notifications . photo_saved.message , Config.Notifications . photo_saved.type )
debugPrint ( " Foto gespeichert für: " .. citizenid )
2025-08-04 06:14:47 +02:00
else
2025-08-04 07:40:06 +02:00
TriggerClientEvent ( ' QBCore:Notify ' , src , ' Fehler beim Speichern des Fotos! ' , ' error ' )
2025-08-04 06:14:47 +02:00
end
end )
end )
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end
2025-08-04 07:40:06 +02:00
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 ' )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
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 ' )
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
2025-08-04 07:40:06 +02:00
-- 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
2025-08-04 06:14:47 +02:00
end
end
2025-08-04 07:40:06 +02:00
end )
2025-08-04 06:14:47 +02:00
end
end )
2025-08-04 06:51:25 +02:00
2025-08-04 07:40:06 +02:00
-- 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 )
)
] ] )
2025-08-04 06:51:25 +02:00
end
end )
2025-08-04 07:40:06 +02:00
AddEventHandler ( ' onResourceStop ' , function ( resourceName )
if GetCurrentResourceName ( ) == resourceName then
debugPrint ( " License-System Server gestoppt " )
end
2025-08-04 06:51:25 +02:00
end )