-- ───────────────────────────────────────────────────────────────────────────── -- Interaction system – proximity detection, key prompt -- ───────────────────────────────────────────────────────────────────────────── Interaction = {} local _nearbyPC = nil -- current nearby PC config or nil local _inSession = false local _camCfg = nil -- aktive PC-Kamera-Config (coords/heading) oder nil (mobil) local _pcs = {} -- keyed by id, populated from server -- Sync-Liste vom Server empfangen RegisterNetEvent('pc-live:client:syncDevices', function(list) _pcs = {} for _, pc in ipairs(list) do pc.coords = vector3(pc.coords.x, pc.coords.y, pc.coords.z) _pcs[pc.id] = pc end end) -- Device-Liste beim Start anfordern AddEventHandler('onClientResourceStart', function(res) if res == GetCurrentResourceName() then TriggerServerEvent('pc-live:server:requestDevices') end end) local function DrawText3D(x, y, z, text) local onScreen, sx, sy = World3dToScreen2d(x, y, z) if not onScreen then return end SetTextScale(0.0, 0.45) SetTextFont(4) SetTextProportional(1) SetTextColour(255, 255, 255, 215) SetTextEntry("STRING") SetTextCentre(true) AddTextComponentString(text) DrawText(sx, sy) end local function DrawRect2D(x, y, width, height, r, g, b, a) DrawRect(x, y, width, height, r, g, b, a) end --- Main proximity loop. function Interaction.StartLoop() CreateThread(function() while true do local sleep = 1000 if not _inSession then local ped = PlayerPedId() local coords = GetEntityCoords(ped) local found = nil for _, pc in pairs(_pcs) do local dist = #(coords - pc.coords) if dist < Config.InteractRange + 8.0 then sleep = 0 if dist < Config.InteractRange then found = pc break end end end _nearbyPC = found else _nearbyPC = nil end Wait(sleep) end end) end --- Draw prompt and handle key. function Interaction.StartPromptLoop() CreateThread(function() while true do Wait(0) if _nearbyPC and not _inSession then -- Draw 3D label DrawText3D( _nearbyPC.coords.x, _nearbyPC.coords.y, _nearbyPC.coords.z + 1.1, ("[E] %s"):format(_nearbyPC.label) ) if IsControlJustReleased(0, Config.InteractKey) then Interaction.Open(_nearbyPC) end end end end) end --- Open PC – request session from server. ---@param pc table function Interaction.Open(pc) if _inSession then return end TriggerServerEvent('pc-live:server:openPC', pc.id) end --- Called when the session starts (from server confirmation). ---@param data table function Interaction.OnSessionStart(data) _inSession = true _nearbyPC = nil -- PC-Config finden; mobile Terminals (Streifenwagen) haben keinen Standort/Kamera. local pcCfg for _, pc in ipairs(Config.PCs) do if pc.id == data.pcId then pcCfg = pc; break end end local isMobile = pcCfg and pcCfg.mobile if Config.FreezeOnOpen and not isMobile then FreezeEntityPosition(PlayerPedId(), true) end if pcCfg and not isMobile then _camCfg = { coords = pcCfg.coords, heading = pcCfg.heading } Camera.FocusPC(pcCfg.coords, pcCfg.heading) else _camCfg = nil end end -- Interaktionsmodus: PC-Kamera lösen (Gameplay-Kamera zurück -> umsehen möglich) -- bzw. wieder auf den PC richten, wenn man zurückkehrt. function Interaction.EnterInteract() if _camCfg then Camera.Restore() end end function Interaction.ExitInteract() if _camCfg then Camera.FocusPC(_camCfg.coords, _camCfg.heading) end end --- Öffnet ein mobiles Terminal (z.B. Streifenwagen-PC) – kein Standort/Distanz nötig. ---@param pcId string|nil function Interaction.OpenMobile(pcId) if _inSession then return end TriggerServerEvent('pc-live:server:openPC', pcId or 'police_mobile') end -- Wird vom M-Menü (core-interaction) im Einsatzfahrzeug getriggert. AddEventHandler('pc-live:openMobileTerminal', function(pcId) Interaction.OpenMobile(pcId) end) --- Called when closing the session. function Interaction.OnSessionClose() _inSession = false if Config.FreezeOnOpen then FreezeEntityPosition(PlayerPedId(), false) end Camera.Restore() _camCfg = nil TriggerServerEvent('pc-live:server:closePC') end Interaction.StartLoop() Interaction.StartPromptLoop()