1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-04 09:24:53 +02:00
parent 9461b582f6
commit 4f8d916728
6 changed files with 2396 additions and 2490 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,306 +1,347 @@
Config = {}
-- Allgemeine Einstellungen
-- Debug-Modus
Config.Debug = true
Config.UseBackgroundImages = true
Config.MaxLicenseAge = 365 -- Tage bis Ablauf
Config.RenewalDays = 30 -- Tage vor Ablauf für Verlängerung
-- Berechtigte Jobs
Config.AuthorizedJobs = {
['police'] = true,
['sheriff'] = true,
['government'] = true,
['judge'] = true,
['lawyer'] = true,
['ambulance'] = true, -- Für medizinische Lizenzen
['mechanic'] = true -- Für Fahrzeug-Lizenzen
}
-- Lizenz-Typen
Config.LicenseTypes = {
['id_card'] = {
label = 'Personalausweis',
icon = 'fas fa-id-card',
color = '#667eea',
price = 50,
required_items = {},
can_expire = true,
validity_days = 3650, -- 10 Jahre
required_job = nil,
description = 'Offizieller Personalausweis'
},
['drivers_license'] = {
label = 'Führerschein',
icon = 'fas fa-car',
color = '#f093fb',
price = 500,
required_items = {'driving_test_certificate'},
can_expire = true,
validity_days = 5475, -- 15 Jahre
required_job = 'driving_school',
description = 'Berechtigung zum Führen von Kraftfahrzeugen',
classes = {
'A', 'A1', 'A2', 'B', 'BE', 'C', 'CE', 'D', 'DE'
}
},
['weapon_license'] = {
label = 'Waffenschein',
icon = 'fas fa-crosshairs',
color = '#4facfe',
price = 2500,
required_items = {'weapon_course_certificate', 'psychological_evaluation'},
can_expire = true,
validity_days = 1095, -- 3 Jahre
required_job = 'police',
description = 'Berechtigung zum Führen von Schusswaffen',
restrictions = {
'Nur für registrierte Waffen',
'Regelmäßige Überprüfung erforderlich',
'Nicht übertragbar'
}
},
['passport'] = {
label = 'Reisepass',
icon = 'fas fa-passport',
color = '#43e97b',
price = 150,
required_items = {'birth_certificate', 'id_card'},
can_expire = true,
validity_days = 3650, -- 10 Jahre
required_job = 'government',
description = 'Internationales Reisedokument'
},
['business_license'] = {
label = 'Gewerbeschein',
icon = 'fas fa-briefcase',
color = '#fa709a',
price = 1000,
required_items = {'business_plan', 'tax_certificate'},
can_expire = true,
validity_days = 1825, -- 5 Jahre
required_job = 'government',
description = 'Berechtigung zur Ausübung eines Gewerbes'
},
['pilot_license'] = {
label = 'Pilotenlizenz',
icon = 'fas fa-plane',
color = '#667eea',
price = 5000,
required_items = {'flight_hours_log', 'medical_certificate'},
can_expire = true,
validity_days = 730, -- 2 Jahre
required_job = 'airport',
description = 'Berechtigung zum Führen von Luftfahrzeugen'
},
['boat_license'] = {
label = 'Bootsführerschein',
icon = 'fas fa-ship',
color = '#00f2fe',
price = 800,
required_items = {'boat_course_certificate'},
can_expire = true,
validity_days = 1825, -- 5 Jahre
required_job = 'harbor',
description = 'Berechtigung zum Führen von Wasserfahrzeugen'
},
['medical_license'] = {
label = 'Approbation',
icon = 'fas fa-user-md',
color = '#ff6b6b',
price = 0, -- Kostenlos für Ärzte
required_items = {'medical_degree', 'medical_exam'},
can_expire = false,
validity_days = nil,
required_job = 'ambulance',
description = 'Berechtigung zur Ausübung der Heilkunde'
},
['hunting_license'] = {
label = 'Jagdschein',
icon = 'fas fa-crosshairs',
color = '#8b5a3c',
price = 300,
required_items = {'hunting_course_certificate'},
can_expire = true,
validity_days = 1095, -- 3 Jahre
required_job = 'ranger',
description = 'Berechtigung zur Ausübung der Jagd'
},
['fishing_license'] = {
label = 'Angelschein',
icon = 'fas fa-fish',
color = '#4ecdc4',
price = 50,
required_items = {},
can_expire = true,
validity_days = 365, -- 1 Jahr
required_job = nil,
description = 'Berechtigung zum Angeln in öffentlichen Gewässern'
}
}
-- Standorte für Lizenz-Ausgabe
Config.LicenseLocations = {
['city_hall'] = {
label = 'Rathaus',
coords = vector3(-544.85, -204.13, 38.22),
blip = {
sprite = 419,
color = 2,
scale = 0.8
},
available_licenses = {
'id_card', 'passport', 'business_license'
},
ped = {
model = 'a_m_m_business_01',
coords = vector4(-544.9543, -204.8450, 37.2151, 219.1676)
}
},
['driving_school'] = {
label = 'Fahrschule',
coords = vector3(-829.22, -1209.58, 7.33),
blip = {
sprite = 225,
color = 46,
scale = 0.8
},
available_licenses = {
'drivers_license'
},
ped = {
model = 'a_m_y_business_02',
coords = vector4(-829.22, -1209.58, 6.33, 90.0)
}
},
['police_station'] = {
label = 'Polizeiwache',
coords = vector3(441.07, -979.76, 30.69),
blip = {
sprite = 60,
color = 29,
scale = 0.8
},
available_licenses = {
'weapon_license'
},
ped = {
model = 's_m_y_cop_01',
coords = vector4(441.07, -979.76, 29.69, 270.0)
}
},
['hospital'] = {
label = 'Krankenhaus',
coords = vector3(307.7, -1433.4, 29.9),
blip = {
sprite = 61,
color = 1,
scale = 0.8
},
available_licenses = {
'medical_license'
},
ped = {
model = 's_m_m_doctor_01',
coords = vector4(307.7, -1433.4, 28.9, 180.0)
}
}
}
-- Kommandos
Config.Commands = {
['license'] = {
name = 'lizenz',
help = 'Lizenz-System öffnen',
restricted = true -- Nur für berechtigte Jobs
},
['mylicense'] = {
name = 'meinelizenz',
help = 'Eigene Lizenzen anzeigen',
restricted = false -- Für alle Spieler
},
['givelicense'] = {
name = 'givelicense',
help = 'Lizenz an Spieler vergeben',
restricted = true,
admin_only = true
},
['revokelicense'] = {
name = 'revokelicense',
help = 'Lizenz entziehen',
restricted = true,
admin_only = false
}
}
-- Keybinds
Config.Keybinds = {
['open_license_menu'] = {
key = 'F6',
command = 'lizenz',
description = 'Lizenz-System öffnen'
},
['show_my_licenses'] = {
key = 'F7',
command = 'meinelizenz',
description = 'Meine Lizenzen anzeigen'
}
['doj'] = true,
['ambulance'] = true,
['mechanic'] = true
}
-- Benachrichtigungen
Config.Notifications = {
['no_permission'] = {
message = 'Du hast keine Berechtigung!',
type = 'error'
no_permission = {
message = "Du hast keine Berechtigung dafür!",
type = "error"
},
['no_players_nearby'] = {
message = 'Keine Spieler in der Nähe!',
type = 'error'
license_issued = {
message = "Lizenz erfolgreich ausgestellt!",
type = "success"
},
['license_not_found'] = {
message = 'Keine Lizenz gefunden!',
type = 'error'
},
['license_expired'] = {
message = 'Diese Lizenz ist abgelaufen!',
type = 'warning'
},
['license_expires_soon'] = {
message = 'Diese Lizenz läuft bald ab!',
type = 'warning'
},
['license_granted'] = {
message = 'Lizenz erfolgreich ausgestellt!',
type = 'success'
},
['license_revoked'] = {
message = 'Lizenz wurde entzogen!',
type = 'info'
},
['photo_saved'] = {
message = 'Foto gespeichert!',
type = 'success'
},
['insufficient_funds'] = {
message = 'Nicht genügend Geld!',
type = 'error'
},
['missing_items'] = {
message = 'Benötigte Gegenstände fehlen!',
type = 'error'
license_revoked = {
message = "Lizenz erfolgreich entzogen!",
type = "success"
}
}
-- Sounds
Config.Sounds = {
['card_flip'] = 'sounds/card_flip.mp3',
['camera_shutter'] = 'sounds/camera_shutter.mp3',
['notification'] = 'sounds/notification.mp3'
-- Lizenz-Typen (ERWEITERT mit benutzerdefinierten Feldern)
Config.LicenseTypes = {
['id_card'] = {
label = 'Personalausweis',
description = 'Offizieller Personalausweis',
price = 50,
validity_days = nil, -- Unbegrenzt gültig
color = '#2E86AB',
icon = 'fas fa-id-card',
template = 'id_card',
custom_fields = {
{
name = 'birth_date',
label = 'Geburtsdatum',
type = 'date',
required = true,
placeholder = 'TT.MM.JJJJ'
},
{
name = 'birth_place',
label = 'Geburtsort',
type = 'text',
required = true,
placeholder = 'z.B. Los Santos'
},
{
name = 'nationality',
label = 'Staatsangehörigkeit',
type = 'select',
required = true,
options = {
{value = 'usa', label = 'USA'},
{value = 'germany', label = 'Deutschland'},
{value = 'uk', label = 'Vereinigtes Königreich'},
{value = 'france', label = 'Frankreich'},
{value = 'other', label = 'Andere'}
}
},
{
name = 'address',
label = 'Adresse',
type = 'textarea',
required = true,
placeholder = 'Vollständige Adresse'
},
{
name = 'height',
label = 'Größe (cm)',
type = 'number',
required = false,
placeholder = 'z.B. 180'
},
{
name = 'eye_color',
label = 'Augenfarbe',
type = 'select',
required = false,
options = {
{value = 'brown', label = 'Braun'},
{value = 'blue', label = 'Blau'},
{value = 'green', label = 'Grün'},
{value = 'gray', label = 'Grau'},
{value = 'hazel', label = 'Haselnuss'}
}
},
{
name = 'photo_url',
label = 'Foto-URL',
type = 'url',
required = false,
placeholder = 'https://example.com/photo.jpg'
}
}
},
['driver_license'] = {
label = 'Führerschein',
description = 'Führerschein für Kraftfahrzeuge',
price = 150,
validity_days = 1825, -- 5 Jahre
color = '#F18F01',
icon = 'fas fa-car',
template = 'driver_license',
classes = {
['A'] = 'Motorräder',
['B'] = 'PKW',
['C'] = 'LKW',
['D'] = 'Busse'
},
custom_fields = {
{
name = 'birth_date',
label = 'Geburtsdatum',
type = 'date',
required = true,
placeholder = 'TT.MM.JJJJ'
},
{
name = 'address',
label = 'Adresse',
type = 'textarea',
required = true,
placeholder = 'Vollständige Adresse'
},
{
name = 'restrictions',
label = 'Beschränkungen',
type = 'textarea',
required = false,
placeholder = 'z.B. Brille erforderlich'
},
{
name = 'photo_url',
label = 'Foto-URL',
type = 'url',
required = false,
placeholder = 'https://example.com/photo.jpg'
}
}
},
['weapon_license'] = {
label = 'Waffenschein',
description = 'Berechtigung zum Führen von Waffen',
price = 500,
validity_days = 365, -- 1 Jahr
color = '#C73E1D',
icon = 'fas fa-crosshairs',
template = 'weapon_license',
custom_fields = {
{
name = 'birth_date',
label = 'Geburtsdatum',
type = 'date',
required = true,
placeholder = 'TT.MM.JJJJ'
},
{
name = 'weapon_type',
label = 'Waffentyp',
type = 'select',
required = true,
options = {
{value = 'pistol', label = 'Pistole'},
{value = 'rifle', label = 'Gewehr'},
{value = 'shotgun', label = 'Schrotflinte'},
{value = 'all', label = 'Alle Waffentypen'}
}
},
{
name = 'purpose',
label = 'Verwendungszweck',
type = 'select',
required = true,
options = {
{value = 'self_defense', label = 'Selbstverteidigung'},
{value = 'sport', label = 'Sport'},
{value = 'hunting', label = 'Jagd'},
{value = 'collection', label = 'Sammlung'},
{value = 'security', label = 'Sicherheitsdienst'}
}
},
{
name = 'restrictions',
label = 'Beschränkungen',
type = 'textarea',
required = false,
placeholder = 'Besondere Auflagen oder Beschränkungen'
},
{
name = 'photo_url',
label = 'Foto-URL',
type = 'url',
required = false,
placeholder = 'https://example.com/photo.jpg'
}
}
},
['pilot_license'] = {
label = 'Pilotenlizenz',
description = 'Berechtigung zum Führen von Luftfahrzeugen',
price = 1000,
validity_days = 730, -- 2 Jahre
color = '#6A994E',
icon = 'fas fa-plane',
template = 'pilot_license',
classes = {
['PPL'] = 'Private Pilot License',
['CPL'] = 'Commercial Pilot License',
['ATPL'] = 'Airline Transport Pilot License',
['HELI'] = 'Helicopter License'
},
custom_fields = {
{
name = 'birth_date',
label = 'Geburtsdatum',
type = 'date',
required = true,
placeholder = 'TT.MM.JJJJ'
},
{
name = 'medical_cert',
label = 'Medical Certificate',
type = 'text',
required = true,
placeholder = 'z.B. Class 1, Class 2'
},
{
name = 'flight_hours',
label = 'Flugstunden',
type = 'number',
required = true,
placeholder = 'Gesamte Flugstunden'
},
{
name = 'aircraft_types',
label = 'Luftfahrzeugtypen',
type = 'textarea',
required = false,
placeholder = 'Berechtigte Luftfahrzeugtypen'
},
{
name = 'restrictions',
label = 'Beschränkungen',
type = 'textarea',
required = false,
placeholder = 'z.B. nur bei Tageslicht'
},
{
name = 'photo_url',
label = 'Foto-URL',
type = 'url',
required = false,
placeholder = 'https://example.com/photo.jpg'
}
}
},
['business_license'] = {
label = 'Gewerbeschein',
description = 'Berechtigung zur Ausübung eines Gewerbes',
price = 300,
validity_days = 365, -- 1 Jahr
color = '#7209B7',
icon = 'fas fa-briefcase',
template = 'business_license',
custom_fields = {
{
name = 'business_name',
label = 'Firmenname',
type = 'text',
required = true,
placeholder = 'Name des Unternehmens'
},
{
name = 'business_type',
label = 'Gewerbetyp',
type = 'select',
required = true,
options = {
{value = 'retail', label = 'Einzelhandel'},
{value = 'restaurant', label = 'Gastronomie'},
{value = 'service', label = 'Dienstleistung'},
{value = 'manufacturing', label = 'Herstellung'},
{value = 'transport', label = 'Transport'},
{value = 'other', label = 'Sonstiges'}
}
},
{
name = 'business_address',
label = 'Geschäftsadresse',
type = 'textarea',
required = true,
placeholder = 'Vollständige Geschäftsadresse'
},
{
name = 'tax_number',
label = 'Steuernummer',
type = 'text',
required = false,
placeholder = 'z.B. 123/456/78901'
},
{
name = 'employees',
label = 'Anzahl Mitarbeiter',
type = 'number',
required = false,
placeholder = 'Geplante Mitarbeiterzahl'
},
{
name = 'logo_url',
label = 'Firmenlogo-URL',
type = 'url',
required = false,
placeholder = 'https://example.com/logo.jpg'
}
}
}
}
-- Datenbank-Einstellungen
Config.Database = {
['table_name'] = 'player_licenses',
['auto_cleanup'] = true, -- Alte Lizenzen automatisch löschen
['cleanup_days'] = 365 -- Nach wie vielen Tagen löschen
-- UI-Einstellungen
Config.UI = {
position = 'center', -- 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right'
animation = 'fade', -- 'fade', 'slide', 'zoom'
theme = 'dark', -- 'dark', 'light'
blur_background = true,
max_image_size = 5 * 1024 * 1024, -- 5MB
allowed_image_formats = {'jpg', 'jpeg', 'png', 'gif', 'webp'},
default_avatar = 'https://via.placeholder.com/150x200/cccccc/666666?text=Kein+Foto'
}
-- Validierung
Config.Validation = {
name_min_length = 2,
name_max_length = 50,
url_pattern = '^https?://.+',
date_pattern = '^%d%d%.%d%d%.%d%d%d%d$', -- DD.MM.YYYY
phone_pattern = '^%+?[%d%s%-%(%)]+$'
}

View file

@ -5,270 +5,190 @@
<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.4.0/css/all.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- Hauptcontainer für Lizenz-Anzeige -->
<div id="license-container" class="hidden">
<div class="license-overlay" onclick="closeLicense()"></div>
<div class="license-card" id="license-card">
<!-- Sicherheitselemente -->
<div class="security-strip"></div>
<div class="hologram"></div>
<div class="microtext">SECURE DOCUMENT • OFFICIAL USE ONLY • GOVERNMENT ISSUED</div>
<!-- Header der Lizenz -->
<div class="license-header">
<div class="header-left">
<div class="license-title" id="license-title">Personalausweis</div>
<div class="license-subtitle">Bundesrepublik Deutschland</div>
</div>
<div class="header-right">
<div class="license-logo">
<i class="license-icon" id="license-icon"></i>
</div>
<div class="government-seal">
<i class="fas fa-certificate"></i>
</div>
</div>
</div>
<!-- Hauptinhalt der Lizenz -->
<div class="license-content">
<!-- Foto-Bereich -->
<div class="license-photo-section">
<div class="license-photo" id="license-photo">
<img id="player-photo" class="hidden" alt="Spieler Foto">
<div id="photo-placeholder" class="photo-placeholder">
<i class="fas fa-user"></i>
</div>
</div>
<div class="photo-frame"></div>
</div>
<!-- Informations-Bereich -->
<div class="license-info">
<div class="info-section personal-info">
<h3 class="section-title">Persönliche Daten</h3>
<div class="info-row">
<span class="label">
<i class="fas fa-user"></i>
Name:
</span>
<span class="value" id="license-name">Max Mustermann</span>
</div>
<div class="info-row">
<span class="label">
<i class="fas fa-birthday-cake"></i>
Geburtsdatum:
</span>
<span class="value" id="license-birthday">01.01.1990</span>
</div>
<div class="info-row">
<span class="label">
<i class="fas fa-venus-mars"></i>
Geschlecht:
</span>
<span class="value" id="license-gender">Männlich</span>
</div>
</div>
<div class="info-section document-info">
<h3 class="section-title">Dokument-Informationen</h3>
<div class="info-row">
<span class="label">
<i class="fas fa-calendar-plus"></i>
Ausgestellt:
</span>
<span class="value" id="license-issue">01.01.2024</span>
</div>
<div class="info-row">
<span class="label">
<i class="fas fa-calendar-times"></i>
Gültig bis:
</span>
<span class="value" id="license-expire">01.01.2034</span>
</div>
<div class="info-row">
<span class="label">
<i class="fas fa-hashtag"></i>
Dokument-ID:
</span>
<span class="value" id="license-id">#000001</span>
</div>
<div class="info-row" id="license-classes-row">
<span class="label">
<i class="fas fa-list"></i>
Klassen:
</span>
<span class="value" id="license-classes">A, B, C</span>
</div>
<div class="info-row">
<span class="label">
<i class="fas fa-user-tie"></i>
Ausgestellt von:
</span>
<span class="value" id="license-issuer">Behörde</span>
</div>
</div>
</div>
</div>
<!-- Footer der Lizenz -->
<div class="license-footer">
<div class="footer-left">
<div class="license-status" id="license-status">
<i class="status-icon"></i>
<span class="status-text">Gültig</span>
</div>
<div class="validity-indicator" id="validity-indicator">
<div class="validity-bar">
<div class="validity-fill" id="validity-fill"></div>
</div>
<span class="validity-text" id="validity-text">Noch 365 Tage gültig</span>
</div>
</div>
<div class="footer-center">
<div class="qr-code" id="qr-code">
<div class="qr-pattern">
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
<div class="qr-dot"></div>
</div>
<span class="qr-label">QR</span>
</div>
</div>
<div class="footer-right">
<button class="action-btn view-btn" onclick="flipCard()" title="Rückseite anzeigen">
<i class="fas fa-sync-alt"></i>
<span>Drehen</span>
</button>
<button class="action-btn close-btn" onclick="closeLicense()" title="Schließen">
<i class="fas fa-times"></i>
<span>Schließen</span>
</button>
</div>
</div>
<!-- Rückseite der Lizenz (für Führerschein-Klassen etc.) -->
<div class="license-back hidden" id="license-back">
<div class="back-header">
<h3>Zusätzliche Informationen</h3>
</div>
<div class="back-content">
<div class="classes-grid" id="classes-grid">
<!-- Wird dynamisch befüllt -->
</div>
<div class="restrictions" id="restrictions">
<h4>Beschränkungen:</h4>
<ul id="restrictions-list">
<!-- Wird dynamisch befüllt -->
</ul>
</div>
<div class="notes" id="notes">
<h4>Bemerkungen:</h4>
<p id="notes-text">Keine besonderen Bemerkungen</p>
</div>
</div>
<div class="back-footer">
<button class="action-btn" onclick="flipCard()">
<i class="fas fa-arrow-left"></i>
Zurück
</button>
</div>
</div>
</div>
</div>
<!-- Kamera-Interface für Foto-Aufnahme -->
<div id="camera-container" class="hidden">
<div class="camera-overlay" onclick="closeCamera()"></div>
<div class="camera-interface">
<div class="camera-header">
<h3>Foto für Lizenz aufnehmen</h3>
<button class="camera-close" onclick="closeCamera()">
<!-- Hauptmenü -->
<div id="mainMenu" class="menu-container" style="display: none;">
<div class="menu-content">
<div class="menu-header">
<h2><i class="fas fa-id-card"></i> Lizenz-System</h2>
<button class="close-btn" onclick="closeMenu()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="camera-content">
<div class="camera-preview">
<video id="camera-video" autoplay playsinline></video>
<canvas id="camera-canvas" class="hidden"></canvas>
<div class="camera-overlay-guide">
<div class="face-guide">
<div class="guide-circle"></div>
<p>Gesicht hier positionieren</p>
</div>
<div class="menu-body">
<div class="menu-section">
<h3>Eigene Aktionen</h3>
<div class="button-grid">
<button class="menu-btn" onclick="requestMyLicense('id_card')">
<i class="fas fa-id-card"></i>
<span>Eigenen Ausweis zeigen</span>
</button>
<button class="menu-btn" onclick="requestMyLicense('driver_license')">
<i class="fas fa-car"></i>
<span>Eigenen Führerschein zeigen</span>
</button>
<button class="menu-btn" onclick="requestMyLicense('weapon_license')">
<i class="fas fa-crosshairs"></i>
<span>Eigenen Waffenschein zeigen</span>
</button>
<button class="menu-btn" onclick="requestMyLicense('pilot_license')">
<i class="fas fa-plane"></i>
<span>Eigene Pilotenlizenz zeigen</span>
</button>
</div>
</div>
<div class="camera-controls">
<button class="camera-btn capture-btn" onclick="takePhoto()">
<i class="fas fa-camera"></i>
Foto aufnehmen
</button>
<div class="menu-section" id="targetSection" style="display: none;">
<h3>Aktionen für nahestehende Person</h3>
<div class="target-info">
<p>Entfernung: <span id="targetDistance">-</span>m</p>
</div>
<div class="button-grid">
<button class="menu-btn" onclick="requestLicense()">
<i class="fas fa-search"></i>
<span>Lizenz anzeigen lassen</span>
</button>
<button class="menu-btn" onclick="requestPlayerLicenses()">
<i class="fas fa-list"></i>
<span>Alle Lizenzen anzeigen</span>
</button>
</div>
<button class="camera-btn cancel-btn" onclick="closeCamera()">
<i class="fas fa-ban"></i>
Abbrechen
</button>
<div class="license-creation">
<h4>Lizenz ausstellen</h4>
<div class="button-grid" id="licenseButtons">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading-Overlay -->
<div id="loading-overlay" class="hidden">
<div class="loading-spinner">
<div class="spinner"></div>
<p>Lizenz wird geladen...</p>
<!-- Erweiterte Lizenz-Erstellung -->
<div id="customLicenseForm" class="menu-container" style="display: none;">
<div class="menu-content large">
<div class="menu-header">
<h2><i class="fas fa-plus-circle"></i> <span id="formTitle">Lizenz erstellen</span></h2>
<button class="close-btn" onclick="closeCustomForm()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="menu-body">
<form id="licenseForm" class="custom-form">
<div class="form-grid">
<!-- Basis-Informationen -->
<div class="form-section">
<h3>Basis-Informationen</h3>
<div class="form-group">
<label for="holderName">Name des Inhabers</label>
<input type="text" id="holderName" name="holderName" readonly>
</div>
</div>
<!-- Benutzerdefinierte Felder -->
<div class="form-section">
<h3>Zusätzliche Informationen</h3>
<div id="customFields">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
<!-- Klassen (falls vorhanden) -->
<div class="form-section" id="classesSection" style="display: none;">
<h3>Klassen/Kategorien</h3>
<div id="classesContainer">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
<!-- Vorschau -->
<div class="form-section">
<h3>Vorschau</h3>
<div class="license-
<div class="license-preview" id="licensePreview">
<div class="preview-header">
<div class="preview-photo">
<img id="previewPhoto" src="" alt="Foto" onerror="this.src='https://via.placeholder.com/150x200/cccccc/666666?text=Kein+Foto'">
</div>
<div class="preview-info">
<h4 id="previewName">-</h4>
<p id="previewType">-</p>
</div>
</div>
<div class="preview-details" id="previewDetails">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="closeCustomForm()">
<i class="fas fa-times"></i> Abbrechen
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-check"></i> Lizenz ausstellen
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Notification-System -->
<div id="notification-container">
<!-- Notifications werden hier dynamisch eingefügt -->
<!-- Lizenz-Anzeige -->
<div id="licenseDisplay" class="menu-container" style="display: none;">
<div class="menu-content">
<div class="menu-header">
<h2><i class="fas fa-id-card"></i> <span id="licenseTitle">Lizenz</span></h2>
<button class="close-btn" onclick="closeLicenseDisplay()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="menu-body">
<div class="license-card" id="licenseCard">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>
</div>
<!-- Audio für Sound-Effekte -->
<audio id="card-flip-sound" preload="auto">
<source src="sounds/card_flip.mp3" type="audio/mpeg">
</audio>
<audio id="camera-shutter-sound" preload="auto">
<source src="sounds/camera_shutter.mp3" type="audio/mpeg">
</audio>
<!-- JavaScript -->
<!-- Spieler-Lizenzen Übersicht -->
<div id="playerLicensesDisplay" class="menu-container" style="display: none;">
<div class="menu-content large">
<div class="menu-header">
<h2><i class="fas fa-list"></i> Lizenzen von <span id="playerName">-</span></h2>
<button class="close-btn" onclick="closePlayerLicenses()">
<i class="fas fa-times"></i>
</button>
</div>
<div class="menu-body">
<div class="licenses-grid" id="licensesGrid">
<!-- Wird dynamisch gefüllt -->
</div>
<div class="no-licenses" id="noLicenses" style="display: none;">
<i class="fas fa-exclamation-triangle"></i>
<p>Keine Lizenzen gefunden</p>
</div>
</div>
</div>
</div>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="loading-overlay" style="display: none;">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin"></i>
<p>Lade...</p>
</div>
</div>
<script src="script.js"></script>
</body>
</html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -984,4 +984,191 @@ function table.count(t)
return count
end
debugPrint("License-System Server vollständig geladen (Status-Fix)")
-- Neuer Event-Handler für benutzerdefinierte Lizenzen
RegisterNetEvent('license-system:server:issueCustomLicense', function(targetId, licenseType, customData, classes)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
local TargetPlayer = QBCore.Functions.GetPlayer(targetId)
if not Player or not TargetPlayer then
debugPrint("Spieler nicht gefunden: " .. src .. " -> " .. targetId)
return
end
-- Berechtigung prüfen
if not isAuthorized(Player.PlayerData.job.name) then
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type)
return
end
-- Lizenz-Konfiguration prüfen
local config = Config.LicenseTypes[licenseType]
if not config then
debugPrint("Unbekannter Lizenztyp: " .. licenseType)
return
end
debugPrint("Erstelle benutzerdefinierte Lizenz: " .. licenseType .. " für " .. TargetPlayer.PlayerData.citizenid)
-- Benutzerdefinierte Daten validieren und bereinigen
local validatedData = {}
for _, field in ipairs(config.custom_fields or {}) do
local value = customData[field.name]
-- Pflichtfeld-Prüfung
if field.required and (not value or value == "") then
TriggerClientEvent('QBCore:Notify', src, "Feld '" .. field.label .. "' ist erforderlich", "error")
return
end
-- Wert bereinigen und validieren
if value and value ~= "" then
value = string.gsub(value, "'", "''") -- SQL-Injection Schutz
-- Typ-spezifische Validierung
if field.type == "url" and not string.match(value, "^https?://") then
TriggerClientEvent('QBCore:Notify', src, "Ungültige URL in Feld '" .. field.label .. "'", "error")
return
end
if field.type == "date" and not string.match(value, "^%d%d%.%d%d%.%d%d%d%d$") then
TriggerClientEvent('QBCore:Notify', src, "Ungültiges Datum in Feld '" .. field.label .. "'", "error")
return
end
validatedData[field.name] = value
end
end
-- Klassen validieren
local validatedClasses = {}
if config.classes and classes then
for _, class in ipairs(classes) do
if config.classes[class.key] then
table.insert(validatedClasses, class)
end
end
end
-- Lizenz in Datenbank speichern
local success = saveCustomLicenseToDB(
TargetPlayer.PlayerData.citizenid,
licenseType,
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname,
validatedData,
validatedClasses
)
if success then
debugPrint("Benutzerdefinierte Lizenz erfolgreich gespeichert")
-- Cache invalidieren
invalidateCache(TargetPlayer.PlayerData.citizenid, licenseType)
-- Benachrichtigungen
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_issued.message, Config.Notifications.license_issued.type)
TriggerClientEvent('QBCore:Notify', targetId, "Du hast eine " .. config.label .. " erhalten!", "success")
-- Events
TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType)
TriggerClientEvent('license-system:client:refreshMenu', src)
-- Log
debugPrint("Lizenz ausgestellt: " .. licenseType .. " von " .. Player.PlayerData.name .. " an " .. TargetPlayer.PlayerData.name)
else
debugPrint("Fehler beim Speichern der benutzerdefinierten Lizenz")
TriggerClientEvent('QBCore:Notify', src, "Fehler beim Ausstellen der Lizenz", "error")
end
end)
-- Funktion zum Speichern benutzerdefinierter Lizenzen
function saveCustomLicenseToDB(citizenid, licenseType, issuedBy, customData, classes)
local config = Config.LicenseTypes[licenseType]
if not config then return false end
-- Ablaufdatum berechnen
local expireDate = nil
if config.validity_days then
expireDate = os.date('%Y-%m-%d %H:%M:%S', os.time() + (config.validity_days * 24 * 60 * 60))
end
-- Holder-Name aus Custom-Data oder Standard
local holderName = customData.holder_name or "Unbekannt"
return safeDBOperation(function()
-- Alte Lizenzen deaktivieren
local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?"
MySQL.query.await(deactivateQuery, {citizenid, licenseType})
-- Neue Lizenz einfügen
local insertQuery = [[
INSERT INTO player_licenses
(citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, custom_data, holder_name)
VALUES (?, ?, ?, NOW(), ?, ?, 1, ?, ?, ?)
]]
local result = MySQL.insert.await(insertQuery, {
citizenid,
licenseType,
config.label,
expireDate,
issuedBy,
json.encode(classes or {}),
json.encode(customData or {}),
holderName
})
return result and result > 0
end, "Benutzerdefinierte Lizenz speichern")
end
-- Erweiterte Lizenz-Abruf-Funktion
function getLicenseFromDB(citizenid, licenseType, skipCache)
-- Cache prüfen
local cacheKey = citizenid .. "_" .. licenseType
if not skipCache and licenseCache[cacheKey] then
debugPrint("Lizenz aus Cache geladen: " .. licenseType)
return licenseCache[cacheKey]
end
local license = safeDBOperation(function()
local query = [[
SELECT *,
CASE
WHEN expire_date IS NULL THEN 1
WHEN expire_date > NOW() THEN 1
ELSE 0
END as is_valid
FROM player_licenses
WHERE citizenid = ? AND license_type = ? AND is_active = 1
ORDER BY created_at DESC
LIMIT 1
]]
local result = MySQL.query.await(query, {citizenid, licenseType})
return result and result[1] or nil
end, "Lizenz abrufen")
if license then
-- Custom-Data und Classes parsen
if license.custom_data then
license.custom_data_parsed = json.decode(license.custom_data)
end
if license.classes then
license.classes_parsed = json.decode(license.classes)
end
-- Cache speichern
licenseCache[cacheKey] = license
debugPrint("Lizenz in Cache gespeichert: " .. licenseType)
end
return license
end
debugPrint("License-System Server erweitert geladen (Custom License Support)")