1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-04 09:35:37 +02:00
parent 4f8d916728
commit 4d24104e50
6 changed files with 3089 additions and 2383 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,347 +1,317 @@
Config = {} Config = {}
-- Debug-Modus -- Allgemeine Einstellungen
Config.Debug = true Config.Debug = true
Config.UseBackgroundImages = true
Config.MaxLicenseAge = 365 -- Tage bis Ablauf
Config.RenewalDays = 30 -- Tage vor Ablauf für Verlängerung
-- Berechtigte Jobs -- Berechtigte Jobs
Config.AuthorizedJobs = { Config.AuthorizedJobs = {
['police'] = true, ['police'] = true,
['sheriff'] = true, ['sheriff'] = true,
['government'] = true, ['government'] = true,
['doj'] = true, ['judge'] = true,
['ambulance'] = true, ['lawyer'] = true,
['mechanic'] = true ['ambulance'] = true, -- Für medizinische Lizenzen
['mechanic'] = true -- Für Fahrzeug-Lizenzen
} }
-- Benachrichtigungen -- Jobs that can reactivate specific license types
Config.Notifications = { Config.ReactivationPermissions = {
no_permission = { ['police'] = {'weapon_license', 'drivers_license'},
message = "Du hast keine Berechtigung dafür!", ['admin'] = {'id_card', 'passport', 'business_license'},
type = "error" ['ambulance'] = {'medical_license'},
}, ['driving_school'] = {'drivers_license'},
license_issued = { ['harbor'] = {'boat_license'},
message = "Lizenz erfolgreich ausgestellt!", ['airport'] = {'pilot_license'}
type = "success"
},
license_revoked = {
message = "Lizenz erfolgreich entzogen!",
type = "success"
}
} }
-- Lizenz-Typen (ERWEITERT mit benutzerdefinierten Feldern)
-- Lizenz-Typen
Config.LicenseTypes = { Config.LicenseTypes = {
['id_card'] = { ['id_card'] = {
label = 'Personalausweis', label = 'Personalausweis',
description = 'Offizieller Personalausweis',
price = 50,
validity_days = nil, -- Unbegrenzt gültig
color = '#2E86AB',
icon = 'fas fa-id-card', icon = 'fas fa-id-card',
template = 'id_card', color = '#667eea',
custom_fields = { price = 50,
{ required_items = {},
name = 'birth_date', can_expire = true,
label = 'Geburtsdatum', validity_days = 3650, -- 10 Jahre
type = 'date', required_job = nil,
required = true, description = 'Offizieller Personalausweis'
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'] = { ['drivers_license'] = {
label = 'Führerschein', label = 'Führerschein',
description = 'Führerschein für Kraftfahrzeuge',
price = 150,
validity_days = 1825, -- 5 Jahre
color = '#F18F01',
icon = 'fas fa-car', icon = 'fas fa-car',
template = 'driver_license', 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 = { classes = {
['A'] = 'Motorräder', 'A', 'A1', 'A2', 'B', 'BE', 'C', 'CE', 'D', 'DE'
['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'] = { ['weapon_license'] = {
label = 'Waffenschein', label = 'Waffenschein',
description = 'Berechtigung zum Führen von Waffen',
price = 500,
validity_days = 365, -- 1 Jahr
color = '#C73E1D',
icon = 'fas fa-crosshairs', icon = 'fas fa-crosshairs',
template = 'weapon_license', color = '#4facfe',
custom_fields = { price = 2500,
{ required_items = {'weapon_course_certificate', 'psychological_evaluation'},
name = 'birth_date', can_expire = true,
label = 'Geburtsdatum', validity_days = 1095, -- 3 Jahre
type = 'date', required_job = 'police',
required = true, description = 'Berechtigung zum Führen von Schusswaffen',
placeholder = 'TT.MM.JJJJ' restrictions = {
}, 'Nur für registrierte Waffen',
{ 'Regelmäßige Überprüfung erforderlich',
name = 'weapon_type', 'Nicht übertragbar'
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'] = { ['passport'] = {
label = 'Pilotenlizenz', label = 'Reisepass',
description = 'Berechtigung zum Führen von Luftfahrzeugen', icon = 'fas fa-passport',
price = 1000, color = '#43e97b',
validity_days = 730, -- 2 Jahre price = 150,
color = '#6A994E', required_items = {'birth_certificate', 'id_card'},
icon = 'fas fa-plane', can_expire = true,
template = 'pilot_license', validity_days = 3650, -- 10 Jahre
classes = { required_job = 'government',
['PPL'] = 'Private Pilot License', description = 'Internationales Reisedokument'
['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'] = { ['business_license'] = {
label = 'Gewerbeschein', label = 'Gewerbeschein',
description = 'Berechtigung zur Ausübung eines Gewerbes',
price = 300,
validity_days = 365, -- 1 Jahr
color = '#7209B7',
icon = 'fas fa-briefcase', icon = 'fas fa-briefcase',
template = 'business_license', color = '#fa709a',
custom_fields = { price = 1000,
{ required_items = {'business_plan', 'tax_certificate'},
name = 'business_name', can_expire = true,
label = 'Firmenname', validity_days = 1825, -- 5 Jahre
type = 'text', required_job = 'government',
required = true, description = 'Berechtigung zur Ausübung eines Gewerbes'
placeholder = 'Name des Unternehmens' },
}, ['pilot_license'] = {
{ label = 'Pilotenlizenz',
name = 'business_type', icon = 'fas fa-plane',
label = 'Gewerbetyp', color = '#667eea',
type = 'select', price = 5000,
required = true, required_items = {'flight_hours_log', 'medical_certificate'},
options = { can_expire = true,
{value = 'retail', label = 'Einzelhandel'}, validity_days = 730, -- 2 Jahre
{value = 'restaurant', label = 'Gastronomie'}, required_job = 'airport',
{value = 'service', label = 'Dienstleistung'}, description = 'Berechtigung zum Führen von Luftfahrzeugen'
{value = 'manufacturing', label = 'Herstellung'}, },
{value = 'transport', label = 'Transport'}, ['boat_license'] = {
{value = 'other', label = 'Sonstiges'} label = 'Bootsführerschein',
} icon = 'fas fa-ship',
}, color = '#00f2fe',
{ price = 800,
name = 'business_address', required_items = {'boat_course_certificate'},
label = 'Geschäftsadresse', can_expire = true,
type = 'textarea', validity_days = 1825, -- 5 Jahre
required = true, required_job = 'harbor',
placeholder = 'Vollständige Geschäftsadresse' description = 'Berechtigung zum Führen von Wasserfahrzeugen'
}, },
{ ['medical_license'] = {
name = 'tax_number', label = 'Approbation',
label = 'Steuernummer', icon = 'fas fa-user-md',
type = 'text', color = '#ff6b6b',
required = false, price = 0, -- Kostenlos für Ärzte
placeholder = 'z.B. 123/456/78901' required_items = {'medical_degree', 'medical_exam'},
}, can_expire = false,
{ validity_days = nil,
name = 'employees', required_job = 'ambulance',
label = 'Anzahl Mitarbeiter', description = 'Berechtigung zur Ausübung der Heilkunde'
type = 'number', },
required = false, ['hunting_license'] = {
placeholder = 'Geplante Mitarbeiterzahl' label = 'Jagdschein',
}, icon = 'fas fa-crosshairs',
{ color = '#8b5a3c',
name = 'logo_url', price = 300,
label = 'Firmenlogo-URL', required_items = {'hunting_course_certificate'},
type = 'url', can_expire = true,
required = false, validity_days = 1095, -- 3 Jahre
placeholder = 'https://example.com/logo.jpg' required_job = 'police',
} 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 = 'police',
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)
} }
} }
} }
-- UI-Einstellungen -- Kommandos
Config.UI = { Config.Commands = {
position = 'center', -- 'center', 'top-left', 'top-right', 'bottom-left', 'bottom-right' ['license'] = {
animation = 'fade', -- 'fade', 'slide', 'zoom' name = 'lizenz',
theme = 'dark', -- 'dark', 'light' help = 'Lizenz-System öffnen',
blur_background = true, restricted = true -- Nur für berechtigte Jobs
max_image_size = 5 * 1024 * 1024, -- 5MB },
allowed_image_formats = {'jpg', 'jpeg', 'png', 'gif', 'webp'}, ['mylicense'] = {
default_avatar = 'https://via.placeholder.com/150x200/cccccc/666666?text=Kein+Foto' 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
}
} }
-- Validierung -- Keybinds
Config.Validation = { Config.Keybinds = {
name_min_length = 2, ['open_license_menu'] = {
name_max_length = 50, key = 'F6',
url_pattern = '^https?://.+', command = 'lizenz',
date_pattern = '^%d%d%.%d%d%.%d%d%d%d$', -- DD.MM.YYYY description = 'Lizenz-System öffnen'
phone_pattern = '^%+?[%d%s%-%(%)]+$' },
['show_my_licenses'] = {
key = 'F7',
command = 'meinelizenz',
description = 'Meine Lizenzen anzeigen'
}
}
-- Benachrichtigungen
Config.Notifications = {
['no_permission'] = {
message = 'Du hast keine Berechtigung!',
type = 'error'
},
['no_players_nearby'] = {
message = 'Keine Spieler in der Nähe!',
type = 'error'
},
['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'
}
}
-- Sounds
Config.Sounds = {
['card_flip'] = 'sounds/card_flip.mp3',
['camera_shutter'] = 'sounds/camera_shutter.mp3',
['notification'] = 'sounds/notification.mp3'
}
-- 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
} }

View file

@ -5,190 +5,270 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>License System</title> <title>License System</title>
<link rel="stylesheet" href="style.css"> <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"> <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">
</head> </head>
<body> <body>
<!-- Hauptmenü --> <!-- Hauptcontainer für Lizenz-Anzeige -->
<div id="mainMenu" class="menu-container" style="display: none;"> <div id="license-container" class="hidden">
<div class="menu-content"> <div class="license-overlay" onclick="closeLicense()"></div>
<div class="menu-header">
<h2><i class="fas fa-id-card"></i> Lizenz-System</h2> <div class="license-card" id="license-card">
<button class="close-btn" onclick="closeMenu()"> <!-- Sicherheitselemente -->
<i class="fas fa-times"></i> <div class="security-strip"></div>
</button> <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> </div>
<div class="menu-body"> <!-- Hauptinhalt der Lizenz -->
<div class="menu-section"> <div class="license-content">
<h3>Eigene Aktionen</h3> <!-- Foto-Bereich -->
<div class="button-grid"> <div class="license-photo-section">
<button class="menu-btn" onclick="requestMyLicense('id_card')"> <div class="license-photo" id="license-photo">
<i class="fas fa-id-card"></i> <img id="player-photo" class="hidden" alt="Spieler Foto">
<span>Eigenen Ausweis zeigen</span> <div id="photo-placeholder" class="photo-placeholder">
</button> <i class="fas fa-user"></i>
<button class="menu-btn" onclick="requestMyLicense('driver_license')"> </div>
<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="photo-frame"></div>
</div> </div>
<div class="menu-section" id="targetSection" style="display: none;"> <!-- Informations-Bereich -->
<h3>Aktionen für nahestehende Person</h3> <div class="license-info">
<div class="target-info"> <div class="info-section personal-info">
<p>Entfernung: <span id="targetDistance">-</span>m</p> <h3 class="section-title">Persönliche Daten</h3>
</div>
<div class="button-grid"> <div class="info-row">
<button class="menu-btn" onclick="requestLicense()"> <span class="label">
<i class="fas fa-search"></i> <i class="fas fa-user"></i>
<span>Lizenz anzeigen lassen</span> Name:
</button> </span>
<button class="menu-btn" onclick="requestPlayerLicenses()"> <span class="value" id="license-name">Max Mustermann</span>
<i class="fas fa-list"></i>
<span>Alle Lizenzen anzeigen</span>
</button>
</div>
<div class="license-creation">
<h4>Lizenz ausstellen</h4>
<div class="button-grid" id="licenseButtons">
<!-- Wird dynamisch gefüllt -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 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> </div>
<!-- Benutzerdefinierte Felder --> <div class="info-row">
<div class="form-section"> <span class="label">
<h3>Zusätzliche Informationen</h3> <i class="fas fa-birthday-cake"></i>
<div id="customFields"> Geburtsdatum:
<!-- Wird dynamisch gefüllt --> </span>
</div> <span class="value" id="license-birthday">01.01.1990</span>
</div> </div>
<!-- Klassen (falls vorhanden) --> <div class="info-row">
<div class="form-section" id="classesSection" style="display: none;"> <span class="label">
<h3>Klassen/Kategorien</h3> <i class="fas fa-venus-mars"></i>
<div id="classesContainer"> Geschlecht:
<!-- Wird dynamisch gefüllt --> </span>
</div> <span class="value" id="license-gender">Männlich</span>
</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> </div>
<div class="form-actions"> <div class="info-section document-info">
<button type="button" class="btn btn-secondary" onclick="closeCustomForm()"> <h3 class="section-title">Dokument-Informationen</h3>
<i class="fas fa-times"></i> Abbrechen
</button> <div class="info-row">
<button type="submit" class="btn btn-primary"> <span class="label">
<i class="fas fa-check"></i> Lizenz ausstellen <i class="fas fa-calendar-plus"></i>
</button> 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>
</form>
</div>
</div>
</div>
<!-- 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>
</div>
</div>
<!-- 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"> <!-- Footer der Lizenz -->
<div class="licenses-grid" id="licensesGrid"> <div class="license-footer">
<!-- Wird dynamisch gefüllt --> <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>
<div class="no-licenses" id="noLicenses" style="display: none;"> <div class="footer-center">
<i class="fas fa-exclamation-triangle"></i> <div class="qr-code" id="qr-code">
<p>Keine Lizenzen gefunden</p> <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>
</div> </div>
</div> </div>
<!-- Loading Overlay --> <!-- Kamera-Interface für Foto-Aufnahme -->
<div id="loadingOverlay" class="loading-overlay" style="display: none;"> <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()">
<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>
</div>
<div class="camera-controls">
<button class="camera-btn capture-btn" onclick="takePhoto()">
<i class="fas fa-camera"></i>
Foto aufnehmen
</button>
<button class="camera-btn cancel-btn" onclick="closeCamera()">
<i class="fas fa-ban"></i>
Abbrechen
</button>
</div>
</div>
</div>
</div>
<!-- Loading-Overlay -->
<div id="loading-overlay" class="hidden">
<div class="loading-spinner"> <div class="loading-spinner">
<i class="fas fa-spinner fa-spin"></i> <div class="spinner"></div>
<p>Lade...</p> <p>Lizenz wird geladen...</p>
</div> </div>
</div> </div>
<!-- Notification-System -->
<div id="notification-container">
<!-- Notifications werden hier dynamisch eingefügt -->
</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 -->
<script src="script.js"></script> <script src="script.js"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -984,191 +984,379 @@ function table.count(t)
return count return count
end end
-- Neuer Event-Handler für benutzerdefinierte Lizenzen -- Add to server/main.lua
RegisterNetEvent('license-system:server:issueCustomLicense', function(targetId, licenseType, customData, classes) RegisterNetEvent('license-system:server:reactivateLicense', function(targetId, licenseType)
local src = source local src = source
local Player = QBCore.Functions.GetPlayer(src) debugPrint("=== Event: reactivateLicense ===")
local TargetPlayer = QBCore.Functions.GetPlayer(targetId) debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType)
if not Player or not TargetPlayer then -- Check if player has permission to reactivate this license type
debugPrint("Spieler nicht gefunden: " .. src .. " -> " .. targetId) local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local job = Player.PlayerData.job.name
local canReactivate = false
-- Check if job can reactivate this license type
if Config.ReactivationPermissions and Config.ReactivationPermissions[job] then
for _, allowedType in ipairs(Config.ReactivationPermissions[job]) do
if allowedType == licenseType then
canReactivate = true
break
end
end
end
if not canReactivate then
TriggerClientEvent('QBCore:Notify', src, 'Du darfst diesen Lizenztyp nicht reaktivieren!', 'error')
return return
end end
-- Berechtigung prüfen -- Get target player
if not isAuthorized(Player.PlayerData.job.name) then local targetPlayer = QBCore.Functions.GetPlayer(targetId)
if not targetPlayer then
TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error')
return
end
local targetCitizenId = targetPlayer.PlayerData.citizenid
-- Find the most recent license of this type (even if inactive)
local query = [[
SELECT * FROM player_licenses
WHERE citizenid = ? AND license_type = ?
ORDER BY created_at DESC
LIMIT 1
]]
local result = MySQL.query.await(query, {targetCitizenId, licenseType})
if not result or #result == 0 then
TriggerClientEvent('QBCore:Notify', src, 'Keine Lizenz dieses Typs gefunden!', 'error')
return
end
-- Reactivate the license
local updateQuery = "UPDATE player_licenses SET is_active = 1 WHERE id = ?"
local success = MySQL.update.await(updateQuery, {result[1].id})
if success then
-- Invalidate cache
invalidateCache(targetCitizenId, licenseType)
local targetName = getPlayerName(targetId)
local issuerName = getPlayerName(src)
local config = Config.LicenseTypes[licenseType]
-- Notifications
TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich reaktiviert für ' .. targetName, 'success')
TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (config.label or licenseType) .. ' wurde reaktiviert!', 'success')
-- Events
TriggerClientEvent('license-system:client:licenseReactivated', src, targetId, licenseType)
TriggerClientEvent('license-system:client:refreshMenu', src)
-- Log
debugPrint("Lizenz " .. licenseType .. " reaktiviert von " .. issuerName .. " für " .. targetName)
else
TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Reaktivieren der Lizenz!', 'error')
end
end)
-- Add to client/main.lua
-- Add a reactivation option to the player license menu
local function openReactivateLicenseMenu(targetId, targetName)
debugPrint("Öffne Lizenz-Reaktivierungs-Menü für: " .. targetName)
-- Request all licenses including inactive ones
TriggerServerEvent('license-system:server:requestAllPlayerLicenses', targetId, true)
end
-- New event to receive all licenses including inactive ones
RegisterNetEvent('license-system:client:receiveAllPlayerLicenses', function(licenses, targetId, targetName)
debugPrint("=== Event: receiveAllPlayerLicenses ===")
debugPrint("Erhaltene Lizenzen: " .. #licenses)
local menuOptions = {}
if licenses and #licenses > 0 then
for _, license in ipairs(licenses) do
if license.is_active == 0 then -- Only show inactive licenses
local licenseConfig = Config.LicenseTypes[license.license_type] or {
label = license.license_type,
icon = 'fas fa-id-card',
color = '#667eea'
}
table.insert(menuOptions, {
title = licenseConfig.label .. '',
description = 'Status: Ungültig | Ausgestellt: ' .. (license.issue_date or 'Unbekannt'),
icon = licenseConfig.icon,
onSelect = function()
-- Confirmation dialog
lib.registerContext({
id = 'confirm_reactivate_license',
title = 'Lizenz reaktivieren bestätigen',
options = {
{
title = 'Spieler: ' .. targetName,
disabled = true,
icon = 'fas fa-user'
},
{
title = 'Lizenztyp: ' .. licenseConfig.label,
disabled = true,
icon = licenseConfig.icon
},
{
title = '─────────────────',
disabled = true
},
{
title = '✅ Bestätigen',
description = 'Lizenz jetzt reaktivieren',
icon = 'fas fa-check',
onSelect = function()
debugPrint("Sende Lizenz-Reaktivierung an Server...")
TriggerServerEvent('license-system:server:reactivateLicense', targetId, license.license_type)
lib.hideContext()
end
},
{
title = '❌ Abbrechen',
description = 'Vorgang abbrechen',
icon = 'fas fa-times',
onSelect = function()
openReactivateLicenseMenu(targetId, targetName)
end
}
}
})
lib.showContext('confirm_reactivate_license')
end
})
end
end
end
if #menuOptions == 0 then
table.insert(menuOptions, {
title = 'Keine inaktiven Lizenzen',
description = 'Dieser Spieler hat keine inaktiven Lizenzen',
icon = 'fas fa-exclamation-triangle',
disabled = true
})
end
table.insert(menuOptions, {
title = '← Zurück',
icon = 'fas fa-arrow-left',
onSelect = function()
openPlayerLicenseMenu(targetId, targetName)
end
})
lib.registerContext({
id = 'reactivate_license',
title = 'Lizenz reaktivieren: ' .. targetName,
options = menuOptions
})
lib.showContext('reactivate_license')
end)
-- Add this option to the player license menu
-- In openPlayerLicenseMenu function, add:
table.insert(menuOptions, {
title = 'Lizenz reaktivieren',
description = 'Eine inaktive Lizenz wieder aktivieren',
icon = 'fas fa-redo',
onSelect = function()
openReactivateLicenseMenu(targetId, targetName)
end
})
-- Add to server/main.lua
RegisterNetEvent('license-system:server:issueManualLicense', function(targetId, licenseData)
local src = source
debugPrint("=== Event: issueManualLicense ===")
debugPrint("Source: " .. src .. ", Target: " .. targetId)
if not hasPermission(src) then
debugPrint("Keine Berechtigung für Spieler: " .. src)
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type)
return return
end end
-- Lizenz-Konfiguration prüfen local targetPlayer = QBCore.Functions.GetPlayer(targetId)
local config = Config.LicenseTypes[licenseType] if not targetPlayer then
if not config then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId)
debugPrint("Unbekannter Lizenztyp: " .. licenseType) TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error')
return return
end end
debugPrint("Erstelle benutzerdefinierte Lizenz: " .. licenseType .. " für " .. TargetPlayer.PlayerData.citizenid) local issuerPlayer = QBCore.Functions.GetPlayer(src)
if not issuerPlayer then
-- Benutzerdefinierte Daten validieren und bereinigen debugPrint("Aussteller nicht gefunden: " .. src)
local validatedData = {} return
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 end
-- Klassen validieren local targetCitizenId = targetPlayer.PlayerData.citizenid
local validatedClasses = {} local issuerCitizenId = issuerPlayer.PlayerData.citizenid
if config.classes and classes then
for _, class in ipairs(classes) do -- Check if license type is valid
if config.classes[class.key] then if not Config.LicenseTypes[licenseData.license_type] then
table.insert(validatedClasses, class) TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error')
end return
end
end end
-- Lizenz in Datenbank speichern -- Save photo if provided
local success = saveCustomLicenseToDB( if licenseData.photo_url then
TargetPlayer.PlayerData.citizenid, -- Here you would save the photo to your storage system
licenseType, -- For example, to a folder or database
Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname, -- This is just a placeholder
validatedData, debugPrint("Foto für Lizenz vorhanden")
validatedClasses end
)
if success then -- Insert into database with manual data
debugPrint("Benutzerdefinierte Lizenz erfolgreich gespeichert") local query = [[
INSERT INTO player_licenses
(citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, photo_url, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?)
]]
local createdAt = os.time()
local insertData = {
targetCitizenId,
licenseData.license_type,
licenseData.name,
licenseData.birthday,
licenseData.gender,
licenseData.issue_date,
licenseData.expire_date,
issuerCitizenId,
licenseData.photo_url or '',
createdAt
}
-- First deactivate any existing licenses of this type
local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?"
MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type})
-- Then insert the new license
local result = MySQL.insert.await(query, insertData)
if result then
-- Invalidate cache
invalidateCache(targetCitizenId)
-- Cache invalidieren local targetName = getPlayerName(targetId)
invalidateCache(TargetPlayer.PlayerData.citizenid, licenseType) local issuerName = getPlayerName(src)
local config = Config.LicenseTypes[licenseData.license_type]
-- Benachrichtigungen -- Notifications
TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_issued.message, Config.Notifications.license_issued.type) TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success')
TriggerClientEvent('QBCore:Notify', targetId, "Du hast eine " .. config.label .. " erhalten!", "success") TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success')
-- Events -- Events
TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType) TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseData.license_type)
TriggerClientEvent('license-system:client:refreshMenu', src) TriggerClientEvent('license-system:client:refreshMenu', src)
-- Log -- Log
debugPrint("Lizenz ausgestellt: " .. licenseType .. " von " .. Player.PlayerData.name .. " an " .. TargetPlayer.PlayerData.name) debugPrint("Lizenz " .. licenseData.license_type .. " manuell ausgestellt von " .. issuerName .. " für " .. targetName)
else else
debugPrint("Fehler beim Speichern der benutzerdefinierten Lizenz") TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error')
TriggerClientEvent('QBCore:Notify', src, "Fehler beim Ausstellen der Lizenz", "error")
end end
end) end)
-- Funktion zum Speichern benutzerdefinierter Lizenzen -- Add a new event to get all licenses including inactive ones
function saveCustomLicenseToDB(citizenid, licenseType, issuedBy, customData, classes) RegisterNetEvent('license-system:server:requestAllPlayerLicenses', function(targetId, includeInactive)
local config = Config.LicenseTypes[licenseType] local src = source
if not config then return false end debugPrint("=== Event: requestAllPlayerLicenses ===")
debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", IncludeInactive: " .. tostring(includeInactive))
-- Ablaufdatum berechnen if not hasPermission(src) then
local expireDate = nil debugPrint("Keine Berechtigung für Spieler: " .. src)
if config.validity_days then TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt")
expireDate = os.date('%Y-%m-%d %H:%M:%S', os.time() + (config.validity_days * 24 * 60 * 60)) return
end end
-- Holder-Name aus Custom-Data oder Standard local targetPlayer = QBCore.Functions.GetPlayer(targetId)
local holderName = customData.holder_name or "Unbekannt" if not targetPlayer then
debugPrint("Ziel-Spieler nicht gefunden: " .. targetId)
return safeDBOperation(function() TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt")
-- Alte Lizenzen deaktivieren return
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 end
local license = safeDBOperation(function() local citizenid = targetPlayer.PlayerData.citizenid
local query = [[ local targetName = getPlayerName(targetId)
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 -- Query to get all licenses, including inactive ones if requested
-- Custom-Data und Classes parsen local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC"
if license.custom_data then
license.custom_data_parsed = json.decode(license.custom_data) local result = MySQL.query.await(query, {citizenid})
if not result or #result == 0 then
debugPrint("Keine Lizenzen gefunden für: " .. citizenid)
TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, targetName)
return
end
-- Process licenses
local licenses = {}
local seenTypes = {}
for _, license in ipairs(result) do
-- If includeInactive is true, add all licenses
-- Otherwise, only add the newest license per type
local shouldAdd = includeInactive or not seenTypes[license.license_type]
if shouldAdd then
seenTypes[license.license_type] = true
-- Add holder name
license.holder_name = targetName
-- Add issuer name
if license.issued_by then
local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?"
local issuerResult = MySQL.query.await(issuerQuery, {license.issued_by})
if issuerResult and #issuerResult > 0 then
license.issued_by_name = extractPlayerName(issuerResult[1].charinfo)
else
license.issued_by_name = "System"
end
else
license.issued_by_name = "System"
end
-- Parse classes
if license.classes then
local success, classes = pcall(json.decode, license.classes)
if success and type(classes) == "table" then
license.classes = classes
else
license.classes = {}
end
else
license.classes = {}
end
-- Normalize is_active
if license.is_active == nil then
license.is_active = 1
end
table.insert(licenses, license)
end 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 end
return license debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName)
end TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, licenses, targetId, targetName)
end)
debugPrint("License-System Server erweitert geladen (Custom License Support)")
debugPrint("License-System Server vollständig geladen (Status-Fix)")