-- client/player_police.lua print("^2[turfwar]^7 client/player_police.lua loaded (police tracking)") local PP = Config.PlayerPolice or {} local POLICE_GANG_ID = tonumber(PP.POLICE_GANG_ID) or 3 local isPolice = false local myStars = 0 local myInterior = false -- targets[serverId] = {stars, interior, lastPos, circleBlip, realtimeBlip, lastPingAt} local targets = {} local function vec3(x,y,z) return vector3(x+0.0, y+0.0, z+0.0) end local function InInterior(ped) return (GetInteriorFromEntity(ped) ~= 0) end local function ClearBlipSafe(blip) if blip and DoesBlipExist(blip) then RemoveBlip(blip) end end local function EnsureTarget(src) if not targets[src] then targets[src] = { stars = 0, interior = false, lastPos = nil, circleBlip = nil, realtimeBlip = nil, lastPingAt = 0 } end return targets[src] end local function ClearTarget(src) local t = targets[src] if not t then return end ClearBlipSafe(t.circleBlip) ClearBlipSafe(t.realtimeBlip) targets[src] = nil end local function UpdateVisual(src) if not isPolice then return end local t = targets[src] if not t then return end local stars = tonumber(t.stars) or 0 local interior = (t.interior == true) -- 4-5 stars realtime, but interior forces "2-star mode" local effective = stars if stars >= 4 and interior then effective = 2 end if stars <= 0 then ClearBlipSafe(t.circleBlip); t.circleBlip = nil ClearBlipSafe(t.realtimeBlip); t.realtimeBlip = nil return end if not t.lastPos then return end -- clear visuals that don't match mode if effective <= 3 then ClearBlipSafe(t.realtimeBlip); t.realtimeBlip = nil end if effective >= 4 then ClearBlipSafe(t.circleBlip); t.circleBlip = nil end if effective == 1 or effective == 2 then local radius = (effective == 1) and (PP.CIRCLE_RADIUS_1STAR or 220.0) or (PP.CIRCLE_RADIUS_2STAR or 160.0) ClearBlipSafe(t.circleBlip) t.circleBlip = AddBlipForRadius(t.lastPos.x, t.lastPos.y, t.lastPos.z, radius) SetBlipAlpha(t.circleBlip, 90) elseif effective == 3 then -- ping exact position every update (client sends every 2s) local now = GetGameTimer() if (now - (t.lastPingAt or 0)) > 250 then t.lastPingAt = now local blip = AddBlipForCoord(t.lastPos.x, t.lastPos.y, t.lastPos.z) SetBlipSprite(blip, PP.PING_BLIP_SPRITE or 161) SetBlipScale(blip, PP.PING_BLIP_SCALE or 1.0) BeginTextCommandSetBlipName("STRING") AddTextComponentString("Wanted Suspect (Ping)") EndTextCommandSetBlipName(blip) CreateThread(function() Wait(PP.PING_BLIP_LIFETIME_MS or 1500) ClearBlipSafe(blip) end) end elseif effective >= 4 then -- realtime blip if not t.realtimeBlip or not DoesBlipExist(t.realtimeBlip) then t.realtimeBlip = AddBlipForCoord(t.lastPos.x, t.lastPos.y, t.lastPos.z) SetBlipSprite(t.realtimeBlip, PP.REALTIME_BLIP_SPRITE or 1) SetBlipScale(t.realtimeBlip, PP.REALTIME_BLIP_SCALE or 0.9) BeginTextCommandSetBlipName("STRING") AddTextComponentString("Wanted Suspect") EndTextCommandSetBlipName(t.realtimeBlip) else SetBlipCoords(t.realtimeBlip, t.lastPos.x, t.lastPos.y, t.lastPos.z) end end end -- Police-only chat from server RegisterNetEvent('turfwar:pp:chat', function(msg) if not isPolice then return end TriggerEvent('chat:addMessage', { args = { msg } }) end) -- Police-only tracking updates RegisterNetEvent('turfwar:pp:update', function(offenderSrc, data) if not isPolice then return end if type(data) ~= "table" then return end local t = EnsureTarget(offenderSrc) if data.type == "mode" then t.stars = tonumber(data.stars) or 0 t.interior = (data.interior == true) UpdateVisual(offenderSrc) return end if data.type == "pos" then t.stars = tonumber(data.stars) or 0 t.interior = (data.interior == true) t.lastPos = vec3(data.x, data.y, data.z) UpdateVisual(offenderSrc) return end end) RegisterNetEvent('turfwar:pp:clear', function(offenderSrc) ClearTarget(offenderSrc) end) -- Detect if THIS player is police (from your existing broadcast) RegisterNetEvent('turfwar:playerGang', function(src, gangId) if src ~= GetPlayerServerId(PlayerId()) then return end isPolice = (tonumber(gangId) or 0) == POLICE_GANG_ID if not isPolice then for id, t in pairs(targets) do ClearBlipSafe(t.circleBlip) ClearBlipSafe(t.realtimeBlip) end targets = {} end end) -- How often offenders send updates (based on their own stars) local function CurrentInterval(stars) if stars <= 0 then return 1000 end if stars == 1 then return PP.UPDATE_MS_1STAR or 5000 end if stars == 2 then return PP.UPDATE_MS_2STAR or 2000 end if stars == 3 then return PP.UPDATE_MS_3STAR or 2000 end return PP.UPDATE_MS_45STAR_REALTIME or 500 end -- Watch stars/interior changes (all players) CreateThread(function() while true do Wait(500) local ped = PlayerPedId() local stars = GetPlayerWantedLevel(PlayerId()) or 0 local interior = InInterior(ped) if stars ~= myStars or interior ~= myInterior then myStars = stars myInterior = interior TriggerServerEvent('turfwar:pp:starsChanged', myStars, myInterior) end end end) -- Stream offender position updates (all players with stars) CreateThread(function() while true do Wait(CurrentInterval(myStars)) if myStars > 0 then local ped = PlayerPedId() local c = GetEntityCoords(ped) local h = GetEntityHeading(ped) local interior = InInterior(ped) myInterior = interior TriggerServerEvent('turfwar:pp:posUpdate', myStars, interior, c.x, c.y, c.z, h) end end end)