2026-04-15 22:45:02 +02:00

287 lines
9.5 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.

-- ═══════════════════════════════════════════════════════════════
-- 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)