forked from Simnation/Main
ed
This commit is contained in:
parent
a44c08fe7e
commit
4f3cd097a2
7 changed files with 1208 additions and 0 deletions
391
resources/[tools]/nordi_license/client/main.lua
Normal file
391
resources/[tools]/nordi_license/client/main.lua
Normal file
|
@ -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
|
107
resources/[tools]/nordi_license/config.lua
Normal file
107
resources/[tools]/nordi_license/config.lua
Normal file
|
@ -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'}
|
||||
}
|
35
resources/[tools]/nordi_license/fxmanifest.lua
Normal file
35
resources/[tools]/nordi_license/fxmanifest.lua
Normal file
|
@ -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'
|
||||
}
|
60
resources/[tools]/nordi_license/html/index.html
Normal file
60
resources/[tools]/nordi_license/html/index.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>License System</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="license-container" class="hidden">
|
||||
<div class="license-card" id="license-card">
|
||||
<div class="license-header">
|
||||
<div class="license-title"></div>
|
||||
<div class="license-logo">
|
||||
<i class="license-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="license-content">
|
||||
<div class="license-photo">
|
||||
<i class="fas fa-user"></i>
|
||||
</div>
|
||||
<div class="license-info">
|
||||
<div class="info-row">
|
||||
<span class="label">Name:</span>
|
||||
<span class="value" id="license-name"></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Geburtsdatum:</span>
|
||||
<span class="value" id="license-birthday"></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Geschlecht:</span>
|
||||
<span class="value" id="license-gender"></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Ausgestellt:</span>
|
||||
<span class="value" id="license-issue"></span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">Gültig bis:</span>
|
||||
<span class="value" id="license-expire"></span>
|
||||
</div>
|
||||
<div class="info-row" id="license-classes-row">
|
||||
<span class="label">Klassen:</span>
|
||||
<span class="value" id="license-classes"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="license-footer">
|
||||
<div class="license-status" id="license-status"></div>
|
||||
<button class="close-btn" onclick="closeLicense()">
|
||||
<i class="fas fa-times"></i> Schließen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
103
resources/[tools]/nordi_license/html/script.js
Normal file
103
resources/[tools]/nordi_license/html/script.js
Normal file
|
@ -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();
|
||||
}
|
||||
});
|
220
resources/[tools]/nordi_license/html/style.css
Normal file
220
resources/[tools]/nordi_license/html/style.css
Normal file
|
@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grain" width="100" height="100" patternUnits="userSpaceOnUse"><circle cx="25" cy="25" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="75" cy="75" r="1" fill="rgba(255,255,255,0.1)"/></pattern></defs><rect width="100" height="100" fill="url(%23grain)"/></svg>');
|
||||
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;
|
||||
}
|
292
resources/[tools]/nordi_license/server/main.lua
Normal file
292
resources/[tools]/nordi_license/server/main.lua
Normal file
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue