-- ═══════════════════════════════════════════════════════════════ -- MercyV Bike – Client -- ═══════════════════════════════════════════════════════════════ -- NUI beim Start sicher versteckt halten AddEventHandler('onClientResourceStart', function(res) if res ~= GetCurrentResourceName() then return end SetNuiFocus(false, false) SendNUIMessage({ action = "CLOSE" }) end) local NPCEntity = nil local NPCData = nil local IsAdmin = false local AlreadyClaimed = false local PanelOpen = false -- ── Hilfsfunktionen ──────────────────────────────────────────── local function normPlate(p) return string.lower(string.gsub(p or '', '%s+', '')) end local function ShowHint(text) if Config.TextUIHandler == 'custom' then exports['hex_4_hud']:ShowHelpNotify(text, "E") end end local function HideHint() if Config.TextUIHandler == 'custom' then exports['hex_4_hud']:HideHelpNotify() end end local function Notify(msg, type) exports['hex_4_hud']:Notify("Fahrrad", msg, type or "info", 3000) end -- ── NPC spawnen ──────────────────────────────────────────────── local function SpawnNPC(data) if not data or data.x == 0 then return end -- Alten NPC löschen if NPCEntity and DoesEntityExist(NPCEntity) then DeleteEntity(NPCEntity) NPCEntity = nil end local model = GetHashKey(data.model or 'a_m_m_beach_01') RequestModel(model) local t = GetGameTimer() + 5000 while not HasModelLoaded(model) do if GetGameTimer() > t then return end Citizen.Wait(100) end NPCEntity = CreatePed(4, model, data.x, data.y, data.z - 1.0, data.heading or 0.0, false, true) Citizen.Wait(200) PlaceObjectOnGroundProperly(NPCEntity) SetEntityInvincible(NPCEntity, true) SetBlockingOfNonTemporaryEvents(NPCEntity, true) FreezeEntityPosition(NPCEntity, true) SetPedFleeAttributes(NPCEntity, 0, false) SetModelAsNoLongerNeeded(model) end -- ── NPC-Sync vom Server ──────────────────────────────────────── RegisterNetEvent('mercyv-bike:syncNPC', function(data) NPCData = data SpawnNPC(data) end) RegisterNetEvent('mercyv-bike:setAdminStatus', function(status) IsAdmin = status SendNUIMessage({ action = "SET_ADMIN", isAdmin = IsAdmin }) end) RegisterNetEvent('mercyv-bike:claimStatus', function(claimed, model) AlreadyClaimed = claimed SendNUIMessage({ action = "SET_CLAIM_STATUS", claimed = claimed, model = model }) end) -- ── Benachrichtigung ────────────────────────────────────────── RegisterNetEvent('mercyv-bike:notify', function(msg, type) Notify(msg, type) end) -- ── Panel öffnen/schließen ───────────────────────────────────── local function OpenPanel() if PanelOpen then return end PanelOpen = true -- Claim-Status vom Server holen TriggerServerEvent('mercyv-bike:checkClaim') SetNuiFocus(true, true) exports['hex_4_hud']:HideHud(true) SendNUIMessage({ action = "OPEN", bikes = Config.Bikes, isAdmin = IsAdmin, }) end local function ClosePanel() PanelOpen = false SetNuiFocus(false, false) exports['hex_4_hud']:HideHud(false) SendNUIMessage({ action = "CLOSE" }) end -- ── NUI Callbacks ────────────────────────────────────────────── RegisterNUICallback('close', function(data, cb) ClosePanel() cb({}) end) RegisterNUICallback('claimBike', function(data, cb) if not data.model then cb({}); return end TriggerServerEvent('mercyv-bike:claim', data.model) ClosePanel() cb({}) end) -- Admin: NPC-Position speichern RegisterNUICallback('saveNPC', function(data, cb) TriggerServerEvent('mercyv-bike:saveNPC', data) cb({}) end) -- Admin: Aktuelle Position erfassen RegisterNUICallback('capturePos', function(data, cb) local field = data.field or 'npc' ClosePanel() Citizen.Wait(300) Notify("Geh zur Position und drücke E.", "info") exports['hex_4_hud']:ShowHelpNotify("Position erfassen", "E") Citizen.CreateThread(function() while true do Citizen.Wait(0) if IsControlJustPressed(0, 38) then exports['hex_4_hud']:HideHelpNotify() local coords = GetEntityCoords(PlayerPedId()) local heading = GetEntityHeading(PlayerPedId()) SetNuiFocus(true, true) exports['hex_4_hud']:HideHud(true) PanelOpen = true SendNUIMessage({ action = "OPEN_ADMIN", isAdmin = IsAdmin, captured = { x = coords.x, y = coords.y, z = coords.z, heading = heading, field = field }, }) break elseif IsControlJustPressed(0, 322) then exports['hex_4_hud']:HideHelpNotify() OpenPanel() break end end end) cb({}) end) -- ── Interaktions-Loop ────────────────────────────────────────── Citizen.CreateThread(function() while not NetworkIsPlayerActive(PlayerId()) do Citizen.Wait(500) end Citizen.Wait(2000) TriggerServerEvent('mercyv-bike:clientReady') end) Citizen.CreateThread(function() local hintShown = false while true do local sleep = 500 if NPCData and NPCData.x ~= 0 then local ped = PlayerPedId() local pos = GetEntityCoords(ped) local dist = #(pos - vector3(NPCData.x, NPCData.y, NPCData.z)) if dist < 8.0 then sleep = 0 if dist < Config.NPC.radius and not PanelOpen then if not hintShown then hintShown = true ShowHint("Fahrrad abholen") end if IsControlJustPressed(0, 38) then OpenPanel() end else if hintShown then HideHint() hintShown = false end end else if hintShown then HideHint() hintShown = false end end end Citizen.Wait(sleep) end end) -- ── Fahrrad spawnen ──────────────────────────────────────────── RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel, spawnData) print('[mercyv-bike] Spawne Fahrrad: ' .. tostring(bikeModel)) local model = GetHashKey(bikeModel) if model == 0 then Notify("Unbekanntes Fahrrad-Modell: " .. tostring(bikeModel), "error") return end RequestModel(model) local t = GetGameTimer() + 8000 while not HasModelLoaded(model) do if GetGameTimer() > t then Notify("Fahrrad-Modell konnte nicht geladen werden.", "error") return end Citizen.Wait(100) end -- Spawn-Position: vom Server gesetzte Koordinaten oder vor Spieler local sx, sy, sz, sh if spawnData and spawnData.x and spawnData.x ~= 0 then sx = spawnData.x sy = spawnData.y sz = spawnData.z sh = spawnData.heading or 0 else local ped = PlayerPedId() local coords = GetEntityCoords(ped) local heading = GetEntityHeading(ped) local rad = math.rad(heading) sx = coords.x + math.sin(-rad) * Config.SpawnOffset sy = coords.y + math.cos(-rad) * Config.SpawnOffset sz = coords.z sh = heading end print(string.format('[mercyv-bike] Spawn bei %.1f, %.1f, %.1f', sx, sy, sz)) local bike = CreateVehicle(model, sx, sy, sz, sh, true, false) local tw = GetGameTimer() + 4000 while not DoesEntityExist(bike) do if GetGameTimer() > tw then break end Citizen.Wait(100) end if DoesEntityExist(bike) then SetEntityAsMissionEntity(bike, true, true) SetVehicleEngineOn(bike, true, true, false) SetModelAsNoLongerNeeded(model) TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys', GetVehicleNumberPlateText(bike)) Notify("Fahrrad erhalten! Viel Spaß!", "success") print('[mercyv-bike] Fahrrad erfolgreich gespawnt.') else Notify("Fahrrad konnte nicht gespawnt werden.", "error") end end) -- ── Admin-Befehl ─────────────────────────────────────────────── RegisterCommand('bikeadmin', function() if not IsAdmin then Notify(Config.Notify.NO_ACCESS, "error") return end OpenPanel() Citizen.Wait(200) SendNUIMessage({ action = "OPEN_ADMIN", isAdmin = true }) end, false)