aether-framework/resources/[aether]/aether-jobs/shared/jobs.lua
Jerrit Fritzsche e4b82dd685 Aether-Framework: Fachmodule, Admin-Panel und Docker-Datenbank
- 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>
2026-07-27 18:55:32 +02:00

272 lines
12 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =====================================================================
-- Aether Jobs — Jobdefinitionen, Arbeitsorte und Auftragsgenerator
--
-- Jeder Arbeitsschritt hat einen ECHTEN Ort in der Welt. Der Spieler
-- muss hinfahren; der Server prueft die Entfernung, bevor er bezahlt.
-- =====================================================================
Jobs = Jobs or {}
Jobs.XP_PRO_LEVEL = 120 -- Erfahrung bis zum naechsten Level
Jobs.MAX_LEVEL = 20 -- Hoechstlevel je Job
-- Wie nah man am Arbeitsort stehen muss (Meter)
Jobs.ORT_RADIUS = 4.0
-- Reichweite zum Jobcenter (Meter)
Jobs.INTERAKTION_WEITE = 2.0
-- Standort des Jobcenters (Innenstadt)
Jobs.JOBCENTER = vector3(-265.0, -963.5, 31.2)
-- ---------------------------------------------------------------------
-- Abgabeorte im Stadtgebiet
-- Werden fuer Zustellungen, Taxifahrten und Frachten zufaellig gewaehlt.
-- ---------------------------------------------------------------------
Jobs.Abgabeorte = {
{ name = 'Legion Square', pos = vector3(195.9, -933.2, 30.7) },
{ name = 'Vinewood Blvd', pos = vector3(297.4, 180.6, 104.4) },
{ name = 'Vespucci Beach', pos = vector3(-1233.6, -1490.3, 4.4) },
{ name = 'Grove Street', pos = vector3(105.8, -1940.0, 21.3) },
{ name = 'Sandy Shores', pos = vector3(1961.0, 3740.5, 32.3) },
{ name = 'Del Perro', pos = vector3(-1305.6, -394.2, 36.7) },
{ name = 'Mirror Park', pos = vector3(1136.5, -982.3, 46.0) },
{ name = 'Downtown', pos = vector3(-247.5, -883.4, 31.2) },
{ name = 'Rockford Hills', pos = vector3(-1108.5, -455.2, 36.2) },
{ name = 'Paleto Bay', pos = vector3(-127.0, 6386.0, 31.5) },
}
-- Zufallsdaten fuer Fang und Erz
Jobs.Fische = {
{ n = 'alter Stiefel', w = 5 }, { n = 'Hering', w = 55 }, { n = 'Makrele', w = 85 },
{ n = 'Wolfsbarsch', w = 130 }, { n = 'Lachs', w = 190 }, { n = 'Thunfisch', w = 340 },
}
Jobs.Erze = {
{ n = 'Kohle', w = 45 }, { n = 'Eisenerz', w = 80 }, { n = 'Kupfererz', w = 120 },
{ n = 'Silbererz', w = 220 }, { n = 'Goldnugget', w = 480 },
}
-- ---------------------------------------------------------------------
-- Jobs
--
-- Je Schritt:
-- text Aufgabenbeschreibung
-- btn Beschriftung der Aktion
-- ort fester vector3 ODER 'zufaellig' (aus Jobs.Abgabeorte)
-- ortName Anzeigename bei festem Ort
-- dauer Wie lange die Handlung dauert (ms)
-- szenario GTA-Szenario, das dabei gespielt wird (optional)
--
-- fahrzeug: Dienstfahrzeug, das beim Antreten gestellt wird
-- ---------------------------------------------------------------------
Jobs.Liste = {
{
id = 'liefer', name = 'Lieferdienst', icon = '📦', kat = 'Service', farbe = '#4aa8ff',
beschreibung = 'Pakete am Depot laden und im Stadtgebiet ausliefern.',
lohnVon = 120, lohnBis = 180, xp = 30,
fahrzeug = { modell = 'boxville2', spawn = vector4(-424.9, -2789.5, 6.0, 90.0) },
schritte = {
{ text = 'Fahre zum Depot und lade das Paket ein.', btn = '📦 Paket laden',
ort = vector3(-419.5, -2792.3, 6.0), ortName = 'Depot Terminal',
dauer = 5000, szenario = 'PROP_HUMAN_BUM_BIN' },
{ text = 'Liefere das Paket beim Kunden aus.', btn = '✅ Zustellen',
ort = 'zufaellig', dauer = 4000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'muell', name = 'Müllabfuhr', icon = '🗑️', kat = 'Service', farbe = '#7bc96f',
beschreibung = 'Mülltonnen einsammeln und am Wertstoffhof entladen.',
lohnVon = 90, lohnBis = 140, xp = 26,
fahrzeug = { modell = 'trash', spawn = vector4(-343.1, -1560.2, 25.5, 270.0) },
schritte = {
{ text = 'Fahre die Route ab und sammle die Tonnen ein.', btn = '🗑️ Tonnen leeren',
ort = 'zufaellig', dauer = 6000, szenario = 'PROP_HUMAN_BUM_BIN' },
{ text = 'Entlade am Wertstoffhof.', btn = '♻️ Entladen',
ort = vector3(-351.6, -1546.1, 25.2), ortName = 'Wertstoffhof',
dauer = 5000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'taxi', name = 'Taxi', icon = '🚕', kat = 'Transport', farbe = '#facc15',
beschreibung = 'Fahrgäste abholen und ans Ziel bringen.',
lohnVon = 80, lohnBis = 250, xp = 22,
fahrzeug = { modell = 'taxi', spawn = vector4(895.8, -179.1, 74.7, 240.0) },
schritte = {
{ text = 'Hole den Fahrgast ab.', btn = '🧍 Fahrgast aufnehmen',
ort = 'zufaellig', dauer = 3000 },
{ text = 'Bringe den Fahrgast ans Ziel.', btn = '🏁 Fahrgast absetzen',
ort = 'zufaellig', dauer = 3000 },
},
},
{
id = 'trucker', name = 'Trucker', icon = '🚛', kat = 'Transport', farbe = '#f0684a',
beschreibung = 'Fracht über Land transportieren — gut bezahlt.',
lohnVon = 420, lohnBis = 680, xp = 45,
fahrzeug = { modell = 'mule3', spawn = vector4(151.6, -3211.0, 5.9, 90.0) },
schritte = {
{ text = 'Beladung am Lagerhaus vorbereiten.', btn = '📦 Beladen',
ort = vector3(158.7, -3212.5, 5.9), ortName = 'Hafenlager',
dauer = 8000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
{ text = 'Fahre die Fracht zum Zielort.', btn = '🛣️ Ausliefern',
ort = 'zufaellig', dauer = 6000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'tankwart', name = 'Tankwart', icon = '⛽', kat = 'Service', farbe = '#f5a742',
beschreibung = 'Kunden an der Zapfsäule bedienen und abkassieren.',
lohnVon = 100, lohnBis = 160, xp = 24,
schritte = {
{ text = 'Bediene die Zapfsäule für den Kunden.', btn = '⛽ Betanken',
ort = vector3(265.1, -1261.3, 29.3), ortName = 'Tankstelle Strawberry',
dauer = 6000, szenario = 'WORLD_HUMAN_VEHICLE_MECHANIC' },
{ text = 'Kassiere an der Kasse.', btn = '💳 Kassieren',
ort = vector3(263.9, -1256.4, 29.6), ortName = 'Kasse',
dauer = 3000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'angeln', name = 'Angeln', icon = '🎣', kat = 'Natur', farbe = '#3fc7e0',
beschreibung = 'Am Pier angeln und den Fang am Stand verkaufen.',
lohnVon = 5, lohnBis = 340, xp = 18,
schritte = {
{ text = 'Wirf die Angel aus und warte auf einen Biss.', btn = '🎣 Angeln',
ort = vector3(-1850.1, -1245.9, 8.6), ortName = 'Pier Vespucci',
dauer = 12000, szenario = 'WORLD_HUMAN_STAND_FISHING' },
{ text = 'Verkaufe den Fang am Fischstand.', btn = '💰 Verkaufen',
ort = vector3(-1817.5, -1193.6, 14.3), ortName = 'Fischstand',
dauer = 3000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'holz', name = 'Holzfäller', icon = '🪓', kat = 'Natur', farbe = '#a3703f',
beschreibung = 'Bäume fällen und die Stämme zum Sägewerk bringen.',
lohnVon = 105, lohnBis = 280, xp = 28,
schritte = {
{ text = 'Fälle den markierten Baum.', btn = '🪓 Baum fällen',
ort = vector3(-552.6, 5324.9, 73.6), ortName = 'Wald bei Paleto',
dauer = 10000, szenario = 'WORLD_HUMAN_HAMMERING' },
{ text = 'Bringe die Stämme zum Sägewerk.', btn = '🪵 Abliefern',
ort = vector3(-501.3, 5305.5, 79.5), ortName = 'Sägewerk',
dauer = 5000, szenario = 'WORLD_HUMAN_CLIPBOARD' },
},
},
{
id = 'bergbau', name = 'Bergbau', icon = '⛏️', kat = 'Natur', farbe = '#9a9aa0',
beschreibung = 'Erz in der Mine abbauen, einschmelzen und verkaufen.',
lohnVon = 45, lohnBis = 480, xp = 30,
schritte = {
{ text = 'Baue das Erz in der Mine ab.', btn = '⛏️ Erz abbauen',
ort = vector3(2949.6, 2792.2, 40.9), ortName = 'Mine Sandy Shores',
dauer = 12000, szenario = 'WORLD_HUMAN_HAMMERING' },
{ text = 'Schmelze das Erz ein und verkaufe es.', btn = '🔥 Einschmelzen',
ort = vector3(1109.4, -2007.9, 31.0), ortName = 'Schmelze Cypress Flats',
dauer = 8000, szenario = 'WORLD_HUMAN_WELDING' },
},
},
}
--- Liefert die Definition eines Jobs
--- @param jobId string Job-Kennung
--- @return table|nil
function Jobs.Hole(jobId)
for _, job in ipairs(Jobs.Liste) do
if job.id == jobId then return job end
end
return nil
end
local function zufall(a, b) return math.random(a, b) end
local function waehle(liste) return liste[math.random(#liste)] end
--- Loest die Orte aller Schritte eines Auftrags zu echten Koordinaten auf.
--- Wird nur serverseitig aufgerufen.
--- @param job table Jobdefinition
--- @return table Liste { position = {x,y,z}, name = ... } je Schritt
local function LoeseOrteAuf(job)
local orte = {}
local bereitsGewaehlt = nil
for _, schritt in ipairs(job.schritte) do
if schritt.ort == 'zufaellig' then
-- Nicht zweimal denselben Ort im selben Auftrag verwenden
local ziel
repeat ziel = waehle(Jobs.Abgabeorte)
until not bereitsGewaehlt or ziel.name ~= bereitsGewaehlt or #Jobs.Abgabeorte < 2
bereitsGewaehlt = ziel.name
orte[#orte + 1] = {
position = { x = ziel.pos.x, y = ziel.pos.y, z = ziel.pos.z },
name = ziel.name,
}
else
orte[#orte + 1] = {
position = { x = schritt.ort.x, y = schritt.ort.y, z = schritt.ort.z },
name = schritt.ortName or 'Arbeitsort',
}
end
end
return orte
end
--- Erzeugt einen Auftrag fuer einen Job.
--- Wird ausschliesslich serverseitig aufgerufen — Lohn und Orte duerfen
--- nie vom Client kommen.
--- @param jobId string Job-Kennung
--- @param level number Aktuelles Level des Spielers
--- @return table|nil Auftrag { ziel, geld, xp, info?, fang?, orte }
function Jobs.ErzeugeAuftrag(jobId, level)
local job = Jobs.Hole(jobId)
if not job then return nil end
local bonus = 1 + (math.max(1, level) - 1) * 0.05
local orte = LoeseOrteAuf(job)
local auftrag
if jobId == 'taxi' then
auftrag = {
ziel = orte[1].name .. ' → ' .. orte[2].name,
geld = math.floor(zufall(80, 250) * bonus + 0.5),
xp = job.xp,
}
elseif jobId == 'angeln' then
local fisch = waehle(Jobs.Fische)
auftrag = {
ziel = 'Pier Vespucci',
geld = math.floor(fisch.w * (1 + (level - 1) * 0.06) + 0.5),
xp = job.xp,
fang = fisch.n,
}
elseif jobId == 'holz' then
local anzahl = zufall(3, 8)
auftrag = {
ziel = 'Wald bei Paleto',
geld = math.floor(anzahl * 35 * bonus + 0.5),
xp = job.xp,
fang = anzahl .. ' Stämme',
}
elseif jobId == 'bergbau' then
local erz = waehle(Jobs.Erze)
local anzahl = zufall(2, 6)
auftrag = {
ziel = 'Mine Sandy Shores',
geld = math.floor(erz.w * anzahl * 0.5 * bonus + 0.5),
xp = job.xp,
fang = anzahl .. '× ' .. erz.n,
}
else
local vorsatz = {
liefer = 'Zustellung: ',
muell = 'Route: ',
trucker = 'Fracht nach ',
tankwart = 'Schicht an der ',
}
-- Beim Standardauftrag benennt der letzte Ort das Ziel
auftrag = {
ziel = (vorsatz[jobId] or '') .. orte[#orte].name,
geld = math.floor(zufall(job.lohnVon, job.lohnBis) * bonus + 0.5),
xp = job.xp,
}
end
auftrag.orte = orte
return auftrag
end