- aether-medical: Stationen/Belegung, Intensivstation, Blutbank, Fuhrpark, Nachrichten/Aushänge, Berichte und Textbausteine (server + NUI) - aether-admin: vollständiges Server-Admin-Panel als echtes FiveM-Resource, an aether-core gekoppelt (Rechte über aether_users.admin_group), mit WebRTC-Live-Kameras (Bildschirmfreigabe) und separatem Media-Server (SFU) - Prototypen admin/ vervollständigt - SQL-Schema aether_admin.sql - Docker-Compose mit MariaDB und idempotenter Auto-Migration (schema_migrations) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
141 lines
9.2 KiB
Lua
141 lines
9.2 KiB
Lua
-- =====================================================================
|
||
-- Aether Shop — Warenkatalog, Laeden und Standorte
|
||
-- Der Server ist die einzige Quelle fuer Preise und Bestand.
|
||
-- Der Client bekommt den Katalog nur zum Anzeigen.
|
||
-- =====================================================================
|
||
|
||
Shop = Shop or {}
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Grundeinstellungen
|
||
-- ---------------------------------------------------------------------
|
||
Shop.INTERAKTION_WEITE = 2.0 -- Reichweite zum Oeffnen (Meter)
|
||
Shop.MAX_MENGE = 100 -- Hoechstmenge je Position im Warenkorb
|
||
|
||
-- Wird ein Waffenschein verlangt?
|
||
Shop.LizenzPflicht = true
|
||
|
||
-- Rueckfallregel, falls `aether-documents` nicht laeuft: dann kann niemand
|
||
-- einen Waffenschein nachweisen und lizenzpflichtige Waren waeren gesperrt.
|
||
-- Seit der Migration von aether-documents greift die echte Pruefung.
|
||
Shop.OhneDokumenteErlauben = false
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Produkt-Registry
|
||
-- item = Kennung im Inventar (aether-inventory)
|
||
-- menge = wie viele Stueck ein Kauf liefert (z. B. Munitionspackung)
|
||
-- rabatt = 0..1, macht ein Angebot
|
||
-- lizenz = Waffenschein noetig
|
||
-- ---------------------------------------------------------------------
|
||
Shop.Produkte = {
|
||
-- --- 24/7 ---
|
||
wasser = { name = 'Wasserflasche', kat = 'Getränke', icon = '🥤', preis = 4, item = 'wasser', bestand = 40, beschreibung = 'Löscht den Durst.' },
|
||
cola = { name = 'Cola', kat = 'Getränke', icon = '🥫', preis = 6, item = 'cola', bestand = 30, beschreibung = 'Eiskalt, koffeinhaltig.' },
|
||
energy = { name = 'Energydrink', kat = 'Getränke', icon = '⚡', preis = 9, item = 'energy', bestand = 24, beschreibung = 'Gibt Extra-Ausdauer.' },
|
||
kaffee = { name = 'Kaffee to go', kat = 'Getränke', icon = '☕', preis = 5, item = 'kaffee', bestand = 18, beschreibung = 'Wach werden.' },
|
||
sandwich = { name = 'Sandwich', kat = 'Snacks', icon = '🥪', preis = 8, item = 'sandwich', bestand = 25, beschreibung = 'Stillt den Hunger.' },
|
||
chips = { name = 'Chips', kat = 'Snacks', icon = '🍟', preis = 5, item = 'chips', bestand = 32, beschreibung = 'Knuspriger Snack.' },
|
||
schoko = { name = 'Schokoriegel', kat = 'Snacks', icon = '🍫', preis = 3, item = 'schoko', bestand = 50, beschreibung = 'Süße Energie.' },
|
||
burger = { name = 'Burger', kat = 'Snacks', icon = '🍔', preis = 12, item = 'burger', bestand = 15, rabatt = 0.25, beschreibung = 'Deftige Mahlzeit.' },
|
||
verband = { name = 'Verband', kat = 'Gesundheit', icon = '🩹', preis = 15, item = 'verband', bestand = 20, beschreibung = 'Stoppt Blutungen.' },
|
||
tablette = { name = 'Schmerztabletten', kat = 'Gesundheit', icon = '💊', preis = 20, item = 'tablette', bestand = 16, beschreibung = 'Lindert Schmerzen.' },
|
||
medkit = { name = 'Erste-Hilfe-Kit', kat = 'Gesundheit', icon = '🧳', preis = 120, item = 'medkit', bestand = 8, beschreibung = 'Behandelt schwere Wunden.' },
|
||
repkit = { name = 'Reparaturkit', kat = 'Werkzeug', icon = '🧰', preis = 250, item = 'reparaturkit', bestand = 10, beschreibung = 'Repariert Fahrzeuge.' },
|
||
dietrich = { name = 'Dietrich', kat = 'Werkzeug', icon = '🗝️', preis = 45, item = 'dietrich', bestand = 22, beschreibung = 'Öffnet einfache Schlösser.' },
|
||
kabel = { name = 'Kabelbinder', kat = 'Werkzeug', icon = '🔗', preis = 30, item = 'kabel', bestand = 40, beschreibung = 'Vielseitig einsetzbar.' },
|
||
lampe = { name = 'Taschenlampe', kat = 'Werkzeug', icon = '🔦', preis = 35, item = 'lampe', bestand = 14, beschreibung = 'Leuchtet den Weg.' },
|
||
handy = { name = 'Smartphone', kat = 'Elektronik', icon = '📱', preis = 450, item = 'handy', bestand = 6, beschreibung = 'Modernes Handy.' },
|
||
sim = { name = 'SIM-Karte', kat = 'Elektronik', icon = '📶', preis = 60, item = 'sim', bestand = 30, beschreibung = 'Fürs Handy nötig.' },
|
||
akku = { name = 'Akku', kat = 'Elektronik', icon = '🔋', preis = 40, item = 'akku', bestand = 45, beschreibung = 'Ersatz-Akku fürs Handy.' },
|
||
funk = { name = 'Funkgerät', kat = 'Elektronik', icon = '📻', preis = 180, item = 'funk', bestand = 9, beschreibung = 'Kurzstrecken-Funk.' },
|
||
boerse = { name = 'Börse', kat = 'Zubehör', icon = '👛', preis = 90, item = 'boerse', bestand = 12, beschreibung = 'Fasst bis zu 10 Dokumente.' },
|
||
rucksack = { name = 'Rucksack', kat = 'Zubehör', icon = '🎒', preis = 300, item = 'rucksack', bestand = 7, rabatt = 0.15, beschreibung = '+ Tragekapazität.' },
|
||
|
||
-- --- Ammu-Nation ---
|
||
w_pistole = { name = 'Pistole P99', kat = 'Waffen', icon = '🔫', preis = 3500, item = 'pistole', bestand = 5, lizenz = true, beschreibung = '9mm Halbautomatik.' },
|
||
w_smg = { name = 'MP5 (MPX)', kat = 'Waffen', icon = '🔫', preis = 8500, item = 'smg', bestand = 3, lizenz = true, beschreibung = 'Maschinenpistole.' },
|
||
w_flinte = { name = 'Schrotflinte 870', kat = 'Waffen', icon = '🔫', preis = 5200, item = 'flinte', bestand = 4, lizenz = true, beschreibung = 'Pump-Action Shotgun.' },
|
||
a_9mm = { name = '9mm Munition (×30)', kat = 'Munition', icon = '🧨', preis = 120, item = 'munition9', menge = 30, bestand = 60, beschreibung = 'Patronen für 9mm.' },
|
||
a_12 = { name = 'Schrot 12/70 (×10)', kat = 'Munition', icon = '🧨', preis = 90, item = 'schrot12', menge = 10, bestand = 40, beschreibung = 'Schrotpatronen.' },
|
||
at_sd = { name = 'Schalldämpfer', kat = 'Aufsätze', icon = '🔇', preis = 900, item = 'schalldaempfer', bestand = 10, lizenz = true, beschreibung = 'Dämpft den Mündungsknall.' },
|
||
at_lampe = { name = 'Waffenlampe', kat = 'Aufsätze', icon = '🔦', preis = 400, item = 'waffenlampe', bestand = 12, beschreibung = 'Taktische Beleuchtung.' },
|
||
at_laser = { name = 'Laservisier', kat = 'Aufsätze', icon = '🎯', preis = 650, item = 'laservisier', bestand = 8, beschreibung = 'Bessere Zielerfassung.' },
|
||
s_weste = { name = 'Schutzweste', kat = 'Schutz', icon = '🦺', preis = 1200, item = 'weste', bestand = 6, beschreibung = 'Reduziert Schaden.' },
|
||
s_helm = { name = 'Ballistikhelm', kat = 'Schutz', icon = '⛑️', preis = 800, item = 'helm', bestand = 8, beschreibung = 'Schützt den Kopf.' },
|
||
}
|
||
|
||
-- ---------------------------------------------------------------------
|
||
-- Laeden — nutzen dieselbe Oberflaeche, listen nur andere Produkte
|
||
-- ---------------------------------------------------------------------
|
||
Shop.Laeden = {
|
||
['247'] = {
|
||
name = 'LS 24/7', logo = '🛒', tl = 'Alles für den Alltag',
|
||
accent = '#4ade80', ink = '#06210f', warnung = nil,
|
||
blip = { sprite = 52, farbe = 2 },
|
||
produkte = {
|
||
'wasser', 'cola', 'energy', 'kaffee', 'sandwich', 'chips', 'schoko', 'burger',
|
||
'verband', 'tablette', 'medkit', 'repkit', 'dietrich', 'kabel', 'lampe',
|
||
'handy', 'sim', 'akku', 'funk', 'boerse', 'rucksack',
|
||
},
|
||
-- Standorte der 24/7-Filialen
|
||
standorte = {
|
||
vector3(25.7, -1347.3, 29.5),
|
||
vector3(-3038.9, 585.9, 7.9),
|
||
vector3(-3241.9, 1001.4, 12.8),
|
||
vector3(1729.2, 6414.1, 35.0),
|
||
vector3(1697.9, 4924.4, 42.1),
|
||
vector3(547.4, 2671.7, 42.1),
|
||
vector3(2679.0, 3280.1, 55.2),
|
||
vector3(1961.4, 3740.6, 32.3),
|
||
vector3(293.8, -1447.0, 29.9),
|
||
vector3(-2968.2, 390.9, 15.0),
|
||
},
|
||
},
|
||
['ammu'] = {
|
||
name = 'Ammu-Nation', logo = '🔫', tl = 'Waffen & Ausrüstung',
|
||
accent = '#f0684a', ink = '#2a0c05',
|
||
warnung = 'Für Waffen & bestimmte Aufsätze ist ein gültiger Waffenschein erforderlich.',
|
||
blip = { sprite = 110, farbe = 1 },
|
||
produkte = {
|
||
'w_pistole', 'w_smg', 'w_flinte', 'a_9mm', 'a_12',
|
||
'at_sd', 'at_lampe', 'at_laser', 's_weste', 's_helm',
|
||
},
|
||
standorte = {
|
||
vector3(22.1, -1107.4, 29.8),
|
||
vector3(810.2, -2157.3, 29.6),
|
||
vector3(1693.4, 3760.2, 34.7),
|
||
vector3(-330.2, 6083.9, 31.5),
|
||
vector3(252.6, -50.0, 69.9),
|
||
vector3(-662.1, -935.3, 21.8),
|
||
vector3(-1305.3, -393.5, 36.7),
|
||
vector3(-1118.9, 2698.9, 18.6),
|
||
vector3(2567.7, 294.4, 108.7),
|
||
},
|
||
},
|
||
}
|
||
|
||
--- Liefert den effektiven Preis eines Produkts (mit Rabatt)
|
||
--- @param produkt table Produktdefinition
|
||
--- @return number Preis in Euro
|
||
function Shop.Preis(produkt)
|
||
if not produkt then return 0 end
|
||
local preis = produkt.preis or 0
|
||
if produkt.rabatt then
|
||
preis = math.floor(preis * (1 - produkt.rabatt) + 0.5)
|
||
end
|
||
return preis
|
||
end
|
||
|
||
--- Prueft, ob ein Laden ein bestimmtes Produkt fuehrt
|
||
--- @param ladenId string Kennung des Ladens
|
||
--- @param produktId string Kennung des Produkts
|
||
--- @return boolean
|
||
function Shop.FuehrtProdukt(ladenId, produktId)
|
||
local laden = Shop.Laeden[ladenId]
|
||
if not laden then return false end
|
||
for _, kennung in ipairs(laden.produkte) do
|
||
if kennung == produktId then return true end
|
||
end
|
||
return false
|
||
end
|