264 lines
8.7 KiB
Lua
264 lines
8.7 KiB
Lua
-- ═══════════════════════════════════════════════════════════════
|
||
-- MercyV Bike – Client
|
||
-- ═══════════════════════════════════════════════════════════════
|
||
|
||
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']:SendNotification(msg, type or "info")
|
||
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)
|
||
ClosePanel()
|
||
Citizen.Wait(300)
|
||
local ped = PlayerPedId()
|
||
local coords = GetEntityCoords(ped)
|
||
local heading = GetEntityHeading(ped)
|
||
|
||
Notify("Geh zur NPC-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 -- E
|
||
exports['hex_4_hud']:HideHelpNotify()
|
||
local c = GetEntityCoords(PlayerPedId())
|
||
local h = GetEntityHeading(PlayerPedId())
|
||
-- Panel wieder öffnen mit erfassten Koordinaten
|
||
SetNuiFocus(true, true)
|
||
exports['hex_4_hud']:HideHud(true)
|
||
PanelOpen = true
|
||
SendNUIMessage({
|
||
action = "OPEN_ADMIN",
|
||
isAdmin = IsAdmin,
|
||
captured = { x = c.x, y = c.y, z = c.z, heading = h },
|
||
})
|
||
break
|
||
elseif IsControlJustPressed(0, 322) then -- ESC
|
||
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)
|
||
local model = GetHashKey(bikeModel)
|
||
RequestModel(model)
|
||
local t = GetGameTimer() + 5000
|
||
while not HasModelLoaded(model) do
|
||
if GetGameTimer() > t then
|
||
Notify("Fahrrad konnte nicht gespawnt werden.", "error")
|
||
return
|
||
end
|
||
Citizen.Wait(100)
|
||
end
|
||
|
||
local ped = PlayerPedId()
|
||
local coords = GetEntityCoords(ped)
|
||
local heading = GetEntityHeading(ped)
|
||
local rad = math.rad(heading)
|
||
local spawnX = coords.x + math.sin(-rad) * Config.SpawnOffset
|
||
local spawnY = coords.y + math.cos(-rad) * Config.SpawnOffset
|
||
|
||
local bike = CreateVehicle(model, spawnX, spawnY, coords.z, heading, true, false)
|
||
local tw = GetGameTimer() + 3000
|
||
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)
|
||
-- Schlüssel geben
|
||
local ks = Config.VehicleKeySystem
|
||
if ks == 'jaksam' then
|
||
TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys',
|
||
GetVehicleNumberPlateText(bike))
|
||
end
|
||
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)
|