-- ===================================================================== -- Aether Garage — Clientlogik -- Erzeugt Fahrzeuge erst nach Freigabe durch den Server und meldet -- beim Einparken den tatsaechlichen Zustand zurueck. -- ===================================================================== local istOffen = false local istGeladen = false local aktuelleGarage = nil -- Garage, an der der Spieler gerade steht -- Taste E local TASTE_BENUTZEN = 38 --- Schickt eine Nachricht an die Oberflaeche local function SendeAnNui(nachricht) SendNuiMessage(json.encode(nachricht)) end --- Zeigt eine Meldung oben links im Spiel local function ZeigeSpielmeldung(text) if not text or text == '' then return end BeginTextCommandThefeedPost('STRING') AddTextComponentSubstringPlayerName(text) EndTextCommandThefeedPostTicker(false, true) end --- Schliesst die Garage local function Schliessen() if not istOffen then return end istOffen = false SetNuiFocus(false, false) SendeAnNui({ aktion = 'schliessen' }) pcall(function() exports['aether-hud']:SetzeSichtbar(true) end) end --- Wandelt eine Hex-Farbe in RGB-Werte um --- @param hex string z. B. '#3563a8' --- @return number, number, number local function HexZuRgb(hex) hex = (hex or '#20232a'):gsub('#', '') return tonumber(hex:sub(1, 2), 16) or 32, tonumber(hex:sub(3, 4), 16) or 35, tonumber(hex:sub(5, 6), 16) or 42 end --- Sucht ein Fahrzeug mit einem bestimmten Kennzeichen in der Naehe --- @param kennzeichen string Kennzeichen --- @return number|nil Fahrzeug-Handle local function FindeFahrzeug(kennzeichen) local position = GetEntityCoords(PlayerPedId()) for fahrzeug in EnumerateVehicles() do if DoesEntityExist(fahrzeug) then local schild = GetVehicleNumberPlateText(fahrzeug) if schild and schild:gsub('%s', '') == kennzeichen:gsub('%s', '') then if #(position - GetEntityCoords(fahrzeug)) < 30.0 then return fahrzeug end end end end return nil end --- Durchlaeuft alle Fahrzeuge in der Naehe function EnumerateVehicles() return coroutine.wrap(function() local handle, fahrzeug = FindFirstVehicle() local erfolgreich repeat coroutine.yield(fahrzeug) erfolgreich, fahrzeug = FindNextVehicle(handle) until not erfolgreich EndFindVehicle(handle) end) end -- --------------------------------------------------------------------- -- Karten-Markierungen -- --------------------------------------------------------------------- CreateThread(function() for _, garage in pairs(Garage.Garagen) do if garage.typ == 'oeffentlich' then local blip = AddBlipForCoord(garage.position.x, garage.position.y, garage.position.z) SetBlipSprite(blip, 357) SetBlipColour(blip, 3) SetBlipScale(blip, 0.75) SetBlipAsShortRange(blip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Garage ' .. garage.name) EndTextCommandSetBlipName(blip) end end end) -- --------------------------------------------------------------------- -- Ereignisse -- --------------------------------------------------------------------- AddEventHandler('aether:client:bereit', function() istGeladen = true end) RegisterNetEvent('aether:garage:zeige', function(zustand) istOffen = true SetNuiFocus(true, true) pcall(function() exports['aether-hud']:SetzeSichtbar(false) end) zustand.aktion = 'oeffnen' SendeAnNui(zustand) end) RegisterNetEvent('aether:garage:zustand', function(zustand) if not istOffen then ZeigeSpielmeldung(zustand.hinweis) return end zustand.aktion = 'zustand' SendeAnNui(zustand) end) RegisterNetEvent('aether:garage:fehler', function(text) if istOffen then SendeAnNui({ aktion = 'fehler', text = text }) else ZeigeSpielmeldung(text) end end) -- --------------------------------------------------------------------- -- Eigenschaften anwenden, die nur clientseitig gesetzt werden koennen -- Der Server erzeugt das Fahrzeug und legt die Werte im State Bag ab; -- hier werden sie uebernommen, sobald das Fahrzeug beim Spieler auftaucht. -- --------------------------------------------------------------------- AddStateBagChangeHandler('aether', nil, function(bagName, _, werte) if type(werte) ~= 'table' then return end local netId = tonumber(bagName:match('entity:(%d+)')) if not netId then return end CreateThread(function() -- Warten, bis die Entitaet lokal existiert local versuche = 0 while not NetworkDoesEntityExistWithNetworkId(netId) and versuche < 100 do Wait(50) versuche = versuche + 1 end if not NetworkDoesEntityExistWithNetworkId(netId) then return end local fahrzeug = NetToVeh(netId) if not fahrzeug or fahrzeug == 0 or not DoesEntityExist(fahrzeug) then return end if werte.kennzeichen then SetVehicleNumberPlateText(fahrzeug, werte.kennzeichen) end if werte.farbe then local r, g, b = HexZuRgb(werte.farbe) SetVehicleCustomPrimaryColour(fahrzeug, r, g, b) SetVehicleCustomSecondaryColour(fahrzeug, r, g, b) end if werte.tank then SetVehicleFuelLevel(fahrzeug, werte.tank + 0.0) end if werte.motor then SetVehicleEngineHealth(fahrzeug, werte.motor + 0.0) end if werte.karosserie then SetVehicleBodyHealth(fahrzeug, werte.karosserie + 0.0) end end) end) --- Server hat das Fahrzeug erzeugt: einsteigen RegisterNetEvent('aether:garage:einsteigen', function(netId, kennzeichen) Schliessen() -- Warten, bis das Fahrzeug beim Spieler angekommen ist local versuche = 0 while not NetworkDoesEntityExistWithNetworkId(netId) and versuche < 100 do Wait(50) versuche = versuche + 1 end if not NetworkDoesEntityExistWithNetworkId(netId) then ZeigeSpielmeldung('Fahrzeug konnte nicht geladen werden.') return end local fahrzeug = NetToVeh(netId) if fahrzeug and DoesEntityExist(fahrzeug) then SetVehicleEngineOn(fahrzeug, true, true, false) SetPedIntoVehicle(PlayerPedId(), fahrzeug, -1) ZeigeSpielmeldung('Fahrzeug ausgeparkt · ' .. kennzeichen) end end) -- --------------------------------------------------------------------- -- Kilometerzaehler -- GTA fuehrt keinen Tachostand, deshalb zaehlen wir die gefahrene -- Strecke je Kennzeichen selbst mit (in Metern). -- --------------------------------------------------------------------- local gefahreneMeter = {} -- [kennzeichen] = Meter seit dem Ausparken local letztePosition = nil -- Position im letzten Durchlauf local seitMeldung = 0 -- Sekunden seit der letzten Zustandsmeldung CreateThread(function() while true do Wait(1000) local ped = PlayerPedId() if istGeladen and IsPedInAnyVehicle(ped, false) then local fahrzeug = GetVehiclePedIsIn(ped, false) local position = GetEntityCoords(fahrzeug) local kennzeichen = (GetVehicleNumberPlateText(fahrzeug) or ''):gsub('%s', '') if letztePosition and kennzeichen ~= '' then local strecke = #(position - letztePosition) -- Sprünge durch Teleport oder Nachladen ignorieren if strecke < 200.0 then gefahreneMeter[kennzeichen] = (gefahreneMeter[kennzeichen] or 0) + strecke end end -- Zustand regelmaessig melden, damit er auch bei einem -- unerwarteten Serverstopp nicht verloren geht seitMeldung = seitMeldung + 1 if seitMeldung >= 30 and kennzeichen ~= '' then seitMeldung = 0 TriggerServerEvent('aether:garage:zustandMelden', kennzeichen, { tank = GetVehicleFuelLevel(fahrzeug), motor = GetVehicleEngineHealth(fahrzeug), karosserie = GetVehicleBodyHealth(fahrzeug), }) end letztePosition = position else letztePosition = nil end end end) -- --------------------------------------------------------------------- -- Garage in der Naehe erkennen -- --------------------------------------------------------------------- CreateThread(function() while true do local pause = 800 aktuelleGarage = nil if istGeladen and not istOffen then local position = GetEntityCoords(PlayerPedId()) for garageId, garage in pairs(Garage.Garagen) do if #(position - garage.position) <= Garage.INTERAKTION_WEITE then aktuelleGarage = garageId pause = 0 SetTextFont(4) SetTextScale(0.34, 0.34) SetTextColour(255, 255, 255, 215) SetTextCentre(true) SetTextOutline() SetTextEntry('STRING') AddTextComponentString('[E] Garage ' .. garage.name) SetDrawOrigin(garage.position.x, garage.position.y, garage.position.z + 0.9, 0) DrawText(0.0, 0.0) ClearDrawOrigin() if IsControlJustReleased(0, TASTE_BENUTZEN) then TriggerServerEvent('aether:garage:oeffnen', garageId) end break end end end Wait(pause) end end) -- --------------------------------------------------------------------- -- NUI-Callbacks -- --------------------------------------------------------------------- RegisterNUICallback('schliessen', function(_, rueckgabe) Schliessen() rueckgabe({ ok = true }) end) RegisterNUICallback('ausparken', function(daten, rueckgabe) TriggerServerEvent('aether:garage:ausparken', daten.kennzeichen, daten.standort) rueckgabe({ ok = true }) end) RegisterNUICallback('einparken', function(daten, rueckgabe) -- Tatsaechlichen Zustand des Fahrzeugs ermitteln und mitschicken local fahrzeug = FindeFahrzeug(daten.kennzeichen) local zustand = {} if fahrzeug and DoesEntityExist(fahrzeug) then local schluessel = (daten.kennzeichen or ''):gsub('%s', '') zustand.tank = GetVehicleFuelLevel(fahrzeug) zustand.motor = GetVehicleEngineHealth(fahrzeug) zustand.karosserie = GetVehicleBodyHealth(fahrzeug) zustand.meter = math.floor(gefahreneMeter[schluessel] or 0) gefahreneMeter[schluessel] = 0 -- Zaehler nach dem Einparken zuruecksetzen else SendeAnNui({ aktion = 'fehler', text = 'Das Fahrzeug ist nicht in der Nähe.' }) rueckgabe({ ok = false }) return end TriggerServerEvent('aether:garage:einparken', daten.kennzeichen, daten.standort, zustand) rueckgabe({ ok = true }) end) RegisterNUICallback('ausloesen', function(daten, rueckgabe) TriggerServerEvent('aether:garage:ausloesen', daten.kennzeichen, daten.standort) rueckgabe({ ok = true }) end) RegisterNUICallback('orten', function(daten, rueckgabe) local fahrzeug = FindeFahrzeug(daten.kennzeichen) if fahrzeug and DoesEntityExist(fahrzeug) then local position = GetEntityCoords(fahrzeug) SetNewWaypoint(position.x, position.y) SendeAnNui({ aktion = 'hinweis', text = 'Markierung auf der Karte gesetzt.' }) else SendeAnNui({ aktion = 'fehler', text = 'Das Fahrzeug ist nicht in der Nähe.' }) end rueckgabe({ ok = true }) end) -- --------------------------------------------------------------------- -- Aufraeumen -- --------------------------------------------------------------------- AddEventHandler('onResourceStop', function(ressource) if ressource ~= GetCurrentResourceName() then return end SetNuiFocus(false, false) end)