202 lines
6.1 KiB
Lua
202 lines
6.1 KiB
Lua
-- 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)
|