ac.log("Police script") local sim = ac.getSim() local car = ac.getCar(0) local valideCar = {"audi_rs3_2022_LMC", "bmw_m340i_Police_HighSpeed", "police_r34_tiresarpi"} local carID = ac.getCarID(0) local windowWidth = sim.windowWidth local windowHeight = sim.windowHeight local settingsOpen = false local arrestLogsOpen = false local camerasOpen = false local cspVersion = ac.getPatchVersionCode() local cspMinVersion = 2144 local fontMultiplier = windowHeight/1440 local firstload = true local cspAboveP218 = cspVersion>= 2363 ac.log("Police script") if not(carID == valideCar[1] or carID == valideCar[2] or carID == valideCar[3]) or cspVersion < cspMinVersion then return end local msgArrest = { msg = {"`NAME` has been arrested for Speeding. The individual was driving a `CAR`.", "We have apprehended `NAME` for Speeding. The suspect was behind the wheel of a `CAR`.", "The driver of a `CAR`, identified as `NAME`, has been arrested for Speeding.", "`NAME` has been taken into custody for Illegal Racing. The suspect was driving a `CAR`.", "We have successfully apprehended `NAME` for Illegal Racing. The individual was operating a `CAR`.", "The driver of a `CAR`, identified as `NAME`, has been arrested for Illegal Racing.", "`NAME` has been apprehended for Speeding. The suspect was operating a `CAR` at the time of the arrest.", "We have successfully detained `NAME` for Illegal Racing. The individual was driving a `CAR`.", "`NAME` driving a `CAR` has been arrested for Speeding", "`NAME` driving a `CAR` has been arrested for Illegal Racing."} } local msgLost = { msg = {"We've lost sight of the suspect. The vehicle involved is described as a `CAR` driven by `NAME`.", "Attention all units, we have lost visual contact with the suspect. The vehicle involved is a `CAR` driven by `NAME`.", "We have temporarily lost track of the suspect. The vehicle description is a `CAR` with `NAME` as the driver.", "Visual contact with the suspect has been lost. The suspect is driving a `CAR` and identified as `NAME`.", "We have lost the suspect's visual trail. The vehicle in question is described as a `CAR` driven by `NAME`.", "Suspect have been lost, Vehicle Description:`CAR` driven by `NAME`", "Visual contact with the suspect has been lost. The suspect is driving a `CAR` and identified as `NAME`.", "We have lost the suspect's visual trail. The vehicle in question is described as a `CAR` driven by `NAME`.",} } local msgEngage = { msg = {"Control! I am engaging on a `CAR` traveling at `SPEED`","Pursuit in progress! I am chasing a `CAR` exceeding `SPEED`","Control, be advised! Pursuit is active on a `CAR` driving over `SPEED`","Attention! Pursuit initiated! Im following a `CAR` going above `SPEED`","Pursuit engaged! `CAR` driving at a high rate of speed over `SPEED`","Attention all units, we have a pursuit in progress! Suspect driving a `CAR` exceeding `SPEED`","Attention units! We have a suspect fleeing in a `CAR` at high speed, pursuing now at `SPEED`","Engaging on a high-speed chase! Suspect driving a `CAR` exceeding `SPEED`!","Attention all units! we have a pursuit in progress! Suspect driving a `CAR` exceeding `SPEED`","High-speed chase underway, suspect driving `CAR` over `SPEED`","Control, `CAR` exceeding `SPEED`, pursuit active.","Engaging on a `CAR` exceeding `SPEED`, pursuit initiated."} } ------------------------------------------------------------------------- JSON Utils ------------------------------------------------------------------------- local json = {} -- Internal functions. local function kind_of(obj) if type(obj) ~= 'table' then return type(obj) end local i = 1 for _ in pairs(obj) do if obj[i] ~= nil then i = i + 1 else return 'table' end end if i == 1 then return 'table' else return 'array' end end local function escape_str(s) local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} for i, c in ipairs(in_char) do s = s:gsub(c, '\\' .. out_char[i]) end return s end -- Returns pos, did_find; there are two cases: -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. -- 2. Delimiter not found: pos = pos after leading space; did_find = false. -- This throws an error if err_if_missing is true and the delim is not found. local function skip_delim(str, pos, delim, err_if_missing) pos = pos + #str:match('^%s*', pos) if str:sub(pos, pos) ~= delim then if err_if_missing then error('Expected ' .. delim .. ' near position ' .. pos) end return pos, false end return pos + 1, true end -- Expects the given pos to be the first character after the opening quote. -- Returns val, pos; the returned pos is after the closing quote character. local function parse_str_val(str, pos, val) val = val or '' local early_end_error = 'End of input found while parsing string.' if pos> #str then error(early_end_error) end local c = str:sub(pos, pos) if c == '"' then return val, pos + 1 end if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end -- We must have a \ character. local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} local nextc = str:sub(pos + 1, pos + 1) if not nextc then error(early_end_error) end return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) end -- Returns val, pos; the returned pos is after the number's final character. local function parse_num_val(str, pos) local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) local val = tonumber(num_str) if not val then error('Error parsing number at position ' .. pos .. '.') end return val, pos + #num_str end -- Public values and functions. function json.stringify(obj, as_key) local s = {} -- We'll build the string as an array of strings to be concatenated. local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise. if kind == 'array' then if as_key then error('Can\'t encode array as key.') end s[#s + 1] = '[' for i, val in ipairs(obj) do if i> 1 then s[#s + 1] = ', ' end s[#s + 1] = json.stringify(val) end s[#s + 1] = ']' elseif kind == 'table' then if as_key then error('Can\'t encode table as key.') end s[#s + 1] = '{' for k, v in pairs(obj) do if #s> 1 then s[#s + 1] = ', ' end s[#s + 1] = json.stringify(k, true) s[#s + 1] = ':' s[#s + 1] = json.stringify(v) end s[#s + 1] = '}' elseif kind == 'string' then return '"' .. escape_str(obj) .. '"' elseif kind == 'number' then if as_key then return '"' .. tostring(obj) .. '"' end return tostring(obj) elseif kind == 'boolean' then return tostring(obj) elseif kind == 'nil' then return 'null' else error('Unjsonifiable type: ' .. kind .. '.') end return table.concat(s) end json.null = {} -- This is a one-off table to represent the null value. function json.parse(str, pos, end_delim) pos = pos or 1 if pos> #str then error('Reached unexpected end of input.') end local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. local first = str:sub(pos, pos) if first == '{' then -- Parse an object. local obj, key, delim_found = {}, true, true pos = pos + 1 while true do key, pos = json.parse(str, pos, '}') if key == nil then return obj, pos end if not delim_found then error('Comma missing between object items.') end pos = skip_delim(str, pos, ':', true) -- true -> error if missing. obj[key], pos = json.parse(str, pos) pos, delim_found = skip_delim(str, pos, ',') end elseif first == '[' then -- Parse an array. local arr, val, delim_found = {}, true, true pos = pos + 1 while true do val, pos = json.parse(str, pos, ']') if val == nil then return arr, pos end if not delim_found then error('Comma missing between array items.') end arr[#arr + 1] = val pos, delim_found = skip_delim(str, pos, ',') end elseif first == '"' then -- Parse a string. return parse_str_val(str, pos + 1) elseif first == '-' or first:match('%d') then -- Parse a number. return parse_num_val(str, pos) elseif first == end_delim then -- End of an object or array. return nil, pos + 1 else -- Parse true, false, or null. local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} for lit_str, lit_val in pairs(literals) do local lit_end = pos + #lit_str - 1 if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end end local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) error('Invalid json syntax starting at ' .. pos_info_str) end end --return json of playerData with only the data needed for the leaderboard -- data are keys of the playerData table local function dataStringify(data) local str = '{"' .. ac.getUserSteamID() .. '": ' local name = ac.getDriverName(0) data['Name'] = name str = str .. json.stringify(data) .. '}' return str end ------------------------------------------------------------------------- Web Utils ------------------------------------------------------------------------- local settings = { essentialSize = 20, policeSize = 20, hudOffsetX = 0, hudOffsetY = 0, fontSize = 20, current = 1, colorHud = rgbm(1,0,0,1), timeMsg = 10, msgOffsetY = 10, msgOffsetX = windowWidth/2, fontSizeMSG = 30, menuPos = vec2(0, 0), unit = "km/h", unitMult = 1, starsSize = 20, starsPos = vec2(windowWidth, 0), } local settingsJSON = { essentialSize = 20, policeSize = 20, hudOffsetX = 0, hudOffsetY = 0, fontSize = 20, current = 1, colorHud = "1,0,0,1", timeMsg = 10, msgOffsetY = 10, msgOffsetX = "1280", fontSizeMSG = 30, menuPos = vec2(0, 0), unit = "km/h", unitMult = 1, starsSize = 20, starsPos = vec2(windowWidth, 0), } local function stringToVec2(str) if str == nil then return vec2(0, 0) end local x = string.match(str, "([^,]+)") local y = string.match(str, "[^,]+,(.+)") return vec2(tonumber(x), tonumber(y)) end local function vec2ToString(vec) return tostring(vec.x) .. ',' .. tostring(vec.y) end local function stringToRGBM(str) local r = string.match(str, "([^,]+)") local g = string.match(str, "[^,]+,([^,]+)") local b = string.match(str, "[^,]+,[^,]+,([^,]+)") local m = string.match(str, "[^,]+,[^,]+,[^,]+,(.+)") return rgbm(tonumber(r), tonumber(g), tonumber(b), tonumber(m)) end local function rgbmToString(rgbm) return tostring(rgbm.r) .. ',' .. tostring(rgbm.g) .. ',' .. tostring(rgbm.b) .. ',' .. tostring(rgbm.mult) end local function parsesettings(table) settings.essentialSize = table.essentialSize settings.policeSize = table.policeSize settings.hudOffsetX = table.hudOffsetX settings.hudOffsetY = table.hudOffsetY settings.fontSize = table.fontSize settings.current = table.current settings.colorHud = stringToRGBM(table.colorHud) settings.timeMsg = table.timeMsg settings.msgOffsetY = table.msgOffsetY settings.msgOffsetX = table.msgOffsetX settings.fontSizeMSG = table.fontSizeMSG settings.menuPos = stringToVec2(table.menuPos) settings.unit = table.unit settings.unitMult = table.unitMult settings.starsSize = table.starsSize or 20 if table.starsPos == nil then settings.starsPos = vec2(windowWidth, 0) else settings.starsPos = stringToVec2(table.starsPos) end end ui.setAsynchronousImagesLoading(true) local imageSize = vec2(0,0) local hudImg = { base = "https://i.ibb.co/8N4mNj6/zhud.png", arrest = "https://i.postimg.cc/DwJv2YgM/icon-Arrest.png", cams = "https://i.postimg.cc/15zRdzNP/iconCams.png", logs = "https://i.postimg.cc/VNXztr29/iconLogs.png", lost = "https://i.postimg.cc/DyYf3KqG/iconLost.png", menu = "https://i.postimg.cc/SxByj71N/iconMenu.png", radar = "https://i.postimg.cc/4dZsQ4TD/icon-Radar.png", } local cameras = { { name = "BOBs SCRAPYARD", pos = vec3(-3564, 31.5, -103), dir = -8, fov = 60, }, { name = "ARENA", pos = vec3(-2283, 115.5, 3284), dir = 128, fov = 70, }, { name = "BANK", pos = vec3(-716, 151, 3556.4), dir = 12, fov = 95, }, { name = "STREET RUNNERS", pos = vec3(-57.3, 103.5, 2935.5), dir = 16, fov = 67, }, { name = "ROAD CRIMINALS", pos = vec3(-2332, 101.1, 3119.2), dir = 121, fov = 60, }, { name = "RECKLESS RENEGADES", pos = vec3(-2993.7, -24.4, -601.7), dir = -64, fov = 60, }, { name = "MOTION MASTERS", pos = vec3(-2120.4, -11.8, -1911.5), dir = 102, fov = 60, }, } local pursuit = { suspect = nil, enable = false, maxDistance = 250000, minDistance = 40000, nextMessage = 30, level = 1, id = -1, timerArrest = 0, hasArrested = false, startedTime = 0, timeLostSight = 0, lostSight = false, engage = false, } local arrestations = {} local textSize = {} local textPos = {} local iconPos = {} local playerData = {} ---------------------------------------------------------------------------------------------- Firebase ---------------------------------------------------------------------------------------------- local urlAppScript = 'https://script.google.com/macros/s/AKfycbwenxjCAbfJA-S90VlV0y7mEH75qt3TuqAmVvlGkx-Y1TX8z5gHtvf5Vb8bOVNOA_9j/exec' local firebaseUrl = 'https://acp-server-97674-default-rtdb.firebaseio.com/' local firebaseUrlData = 'https://acp-server-97674-default-rtdb.firebaseio.com/PlayersData/' local firebaseUrlsettings = 'https://acp-server-97674-default-rtdb.firebaseio.com/Settings' local function updateSheets() local str = '{"category" : "Arrestations"}' web.post(urlAppScript, str, function(err, response) if err then print(err) return else print(response.body) end end) end local function addPlayerToDataBase() local steamID = ac.getUserSteamID() local name = ac.getDriverName(0) local str = '{"' .. steamID .. '": {"Name":"' .. name .. '","Getaway": 0,"Drift": 0,"Overtake": 0,"Wins": 0,"Losses": 0,"Busted": 0,"Arrests": 0,"Theft": 0}}' web.request('PATCH', firebaseUrl .. "Players.json", str, function(err, response) if err then print(err) return end end) end local function getFirebase() local url = firebaseUrl .. "Players/" .. ac.getUserSteamID() .. '.json' web.get(url, function(err, response) if err then print(err) return else if response.body == 'null' then addPlayerToDataBase(ac.getUserSteamID()) else local jString = response.body playerData = json.parse(jString) if playerData.Name ~= ac.getDriverName(0) then playerData.Name = ac.getDriverName(0) end end ac.log('Player data loaded') end end) end local function updatefirebase() local str = '{"' .. ac.getUserSteamID() .. '": ' .. json.stringify(playerData) .. '}' web.request('PATCH', firebaseUrl .. "Players.json", str, function(err, response) if err then print(err) return else print(response.body) end end) end local function updatefirebaseData(node, data) local str = dataStringify(data) web.request('PATCH', firebaseUrlData .. node .. ".json", str, function(err, response) if err then print(err) return else print(response.body) updateSheets() end end) end local function addPlayersettingsToDataBase(steamID) local str = '{"' .. steamID .. '": {"essentialSize":20,"policeSize":20,"hudOffsetX":0,"hudOffsetY":0,"fontSize":20,"current":1,"colorHud":"1,0,0,1","timeMsg":10,"msgOffsetY":10,"msgOffsetX":' .. windowWidth/2 .. ',"fontSizeMSG":30,"menuPos":"0,0","unit":"km/h","unitMult":1}}' web.request('PATCH', firebaseUrlsettings .. ".json", str, function(err, response) if err then print(err) return end end) end local function loadsettings() local url = firebaseUrlsettings .. "/" .. ac.getUserSteamID() .. '.json' web.get(url, function(err, response) if err then print(err) return else if response.body == 'null' then addPlayersettingsToDataBase(ac.getUserSteamID()) else ac.log("settings loaded") local jString = response.body local table = json.parse(jString) parsesettings(table) end end end) end local function updatesettings() local str = '{"' .. ac.getUserSteamID() .. '": ' .. json.stringify(settingsJSON) .. '}' web.request('PATCH', firebaseUrlsettings .. ".json", str, function(err, response) if err then print(err) return end end) end local function onsettingsChange() settingsJSON.colorHud = rgbmToString(settings.colorHud) settingsJSON.menuPos = vec2ToString(settings.menuPos) settingsJSON.essentialSize = settings.essentialSize settingsJSON.policeSize = settings.policeSize settingsJSON.hudOffsetX = settings.hudOffsetX settingsJSON.hudOffsetY = settings.hudOffsetY settingsJSON.fontSize = settings.fontSize settingsJSON.current = settings.current settingsJSON.timeMsg = settings.timeMsg settingsJSON.msgOffsetY = settings.msgOffsetY settingsJSON.msgOffsetX = settings.msgOffsetX settingsJSON.fontSizeMSG = settings.fontSizeMSG settingsJSON.unit = settings.unit settingsJSON.unitMult = settings.unitMult settingsJSON.starsSize = settings.starsSize settingsJSON.starsPos = vec2ToString(settings.starsPos) updatesettings() end ---------------------------------------------------------------------------------------------- settings ---------------------------------------------------------------------------------------------- local acpPolice = ac.OnlineEvent({ message = ac.StructItem.string(110), messageType = ac.StructItem.int16(), yourIndex = ac.StructItem.int16(), }, function (sender, data) if data.yourIndex == car.sessionID and data.messageType == 0 and pursuit.suspect ~= nil and sender == pursuit.suspect then pursuit.hasArrested = true ac.log("ZHD Police: Police received") end end) local starsUI = { starsPos = vec2(windowWidth - (settings.starsSize or 20)/2, settings.starsSize or 20)/2, starsSize = vec2(windowWidth - (settings.starsSize or 20)*2, (settings.starsSize or 20)*2), startSpace = (settings.starsSize or 20)/4, } local function resetStarsUI() if settings.starsPos == nil then settings.starsPos = vec2(windowWidth, 0) end if settings.starsSize == nil then settings.starsSize = 20 end starsUI.starsPos = vec2(settings.starsPos.x - settings.starsSize/2, settings.starsPos.y + settings.starsSize/2) starsUI.starsSize = vec2(settings.starsPos.x - settings.starsSize*2, settings.starsPos.y + settings.starsSize*2) starsUI.startSpace = settings.starsSize/1.5 end local function updatePos() imageSize = vec2(windowHeight/80 * settings.policeSize, windowHeight/80 * settings.policeSize) iconPos.arrest1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/3.2) iconPos.arrest2 = vec2(imageSize.x/1.215, imageSize.y/5) iconPos.lost1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/2.35) iconPos.lost2 = vec2(imageSize.x/1.215, imageSize.y/3.2) iconPos.logs1 = vec2(imageSize.x/1.215, imageSize.y/1.88) iconPos.logs2 = vec2(imageSize.x/1.39, imageSize.y/2.35) iconPos.menu1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/1.88) iconPos.menu2 = vec2(imageSize.x/1.215, imageSize.y/2.35) iconPos.cams1 = vec2(imageSize.x/1.215, imageSize.y/2.35) iconPos.cams2 = vec2(imageSize.x/1.39, imageSize.y/3.2) textSize.size = vec2(imageSize.x*3/5, settings.fontSize/2) textSize.box = vec2(imageSize.x*3/5, settings.fontSize/1.3) textSize.window1 = vec2(settings.hudOffsetX+imageSize.x/9.5, settings.hudOffsetY+imageSize.y/5.3) textSize.window2 = vec2(imageSize.x*3/5, imageSize.y/2.8) textPos.box1 = vec2(0, 0) textPos.box2 = vec2(textSize.size.x, textSize.size.y*1.8) textPos.addBox = vec2(0, textSize.size.y*1.8) settings.fontSize = settings.policeSize * fontMultiplier resetStarsUI() end local function showStarsPursuit() local starsColor = rgbm(1, 1, 1, os.clock()%2 + 0.3) resetStarsUI() for i = 1, 5 do if i> pursuit.level/2 then ui.drawIcon(ui.Icons.StarEmpty, starsUI.starsPos, starsUI.starsSize, rgbm(1, 1, 1, 0.2)) else ui.drawIcon(ui.Icons.StarFull, starsUI.starsPos, starsUI.starsSize, starsColor) end starsUI.starsPos.x = starsUI.starsPos.x - settings.starsSize - starsUI.startSpace starsUI.starsSize.x = starsUI.starsSize.x - settings.starsSize - starsUI.startSpace end end local showPreviewMsg = false local showPreviewStars = false COLORSMSGBG = rgbm(0.5,0.5,0.5,0.5) local function initsettings() if settings.unit then settings.fontSize = settings.policeSize * fontMultiplier if settings.unit ~= "km/h" then settings.unitMult = 0.621371 end settings.policeSize = settings.policeSize * windowHeight/1440 settings.fontSize = settings.policeSize * windowHeight/1440 imageSize = vec2(windowHeight/80 * settings.policeSize, windowHeight/80 * settings.policeSize) updatePos() end end local function previewMSG() ui.beginTransparentWindow("previewMSG", vec2(0, 0), vec2(windowWidth, windowHeight)) ui.pushDWriteFont("Orbitron;Weight=800") local tSize = ui.measureDWriteText("Messages from Police when being chased", settings.fontSizeMSG) local uiOffsetX = settings.msgOffsetX - tSize.x/2 local uiOffsetY = settings.msgOffsetY ui.drawRectFilled(vec2(uiOffsetX - 5, uiOffsetY-5), vec2(uiOffsetX + tSize.x + 5, uiOffsetY + tSize.y + 5), COLORSMSGBG) ui.dwriteDrawText("Messages from Police when being chased", settings.fontSizeMSG, vec2(uiOffsetX, uiOffsetY), rgbm.colors.cyan) ui.popDWriteFont() ui.endTransparentWindow() end local function previewStars() ui.beginTransparentWindow("previewStars", vec2(0, 0), vec2(windowWidth, windowHeight)) showStarsPursuit() ui.endTransparentWindow() end local function uiTab() ui.text('On Screen Message : ') settings.timeMsg = ui.slider('##' .. 'Time Msg On Screen', settings.timeMsg, 1, 15, 'Time Msg On Screen' .. ': %.0fs') settings.fontSizeMSG = ui.slider('##' .. 'Font Size MSG', settings.fontSizeMSG, 10, 50, 'Font Size' .. ': %.0f') ui.text('Stars : ') settings.starsPos.x = ui.slider('##' .. 'Stars Offset X', settings.starsPos.x, 0, windowWidth, 'Stars Offset X' .. ': %.0f') settings.starsPos.y = ui.slider('##' .. 'Stars Offset Y', settings.starsPos.y, 0, windowHeight, 'Stars Offset Y' .. ': %.0f') settings.starsSize = ui.slider('##' .. 'Stars Size', settings.starsSize, 10, 50, 'Stars Size' .. ': %.0f') ui.newLine() ui.text('Offset : ') settings.msgOffsetY = ui.slider('##' .. 'Msg On Screen Offset Y', settings.msgOffsetY, 0, windowHeight, 'Msg On Screen Offset Y' .. ': %.0f') settings.msgOffsetX = ui.slider('##' .. 'Msg On Screen Offset X', settings.msgOffsetX, 0, windowWidth, 'Msg On Screen Offset X' .. ': %.0f') ui.newLine() ui.text('Preview : ') ui.sameLine() if ui.button('Message') then showPreviewMsg = not showPreviewMsg showPreviewStars = false end ui.sameLine() if ui.button('Stars') then showPreviewStars = not showPreviewStars showPreviewMsg = false end if showPreviewMsg then previewMSG() elseif showPreviewStars then previewStars() end if ui.button('Offset X to center') then settings.msgOffsetX = windowWidth/2 end ui.newLine() end local function settingsWindow() imageSize = vec2(windowHeight/80 * settings.policeSize, windowHeight/80 * settings.policeSize) ui.dwriteTextAligned("settings", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(windowWidth/6.5,60), false, rgbm.colors.white) ui.drawLine(vec2(0,60), vec2(windowWidth/6.5,60), rgbm.colors.white, 1) ui.newLine(20) ui.sameLine(10) ui.beginGroup() ui.text('Unit : ') ui.sameLine(160) if ui.selectable('mph', settings.unit == 'mph',_, ui.measureText('km/h')) then settings.unit = 'mph' settings.unitMult = 0.621371 end ui.sameLine(200) if ui.selectable('km/h', settings.unit == 'km/h',_, ui.measureText('km/h')) then settings.unit = 'km/h' settings.unitMult = 1 end ui.sameLine(windowWidth/6.5 - 120) if ui.button('Close', vec2(100, windowHeight/50)) then settingsOpen = false onsettingsChange() end ui.text('HUD : ') settings.hudOffsetX = ui.slider('##' .. 'HUD Offset X', settings.hudOffsetX, 0, windowWidth, 'HUD Offset X' .. ': %.0f') settings.hudOffsetY = ui.slider('##' .. 'HUD Offset Y', settings.hudOffsetY, 0, windowHeight, 'HUD Offset Y' .. ': %.0f') settings.policeSize = ui.slider('##' .. 'HUD Size', settings.policeSize, 10, 50, 'HUD Size' .. ': %.0f') settings.fontSize = settings.policeSize * fontMultiplier ui.setNextItemWidth(300) ui.newLine() uiTab() updatePos() ui.endGroup() end ---------------------------------------------------------------------------------------------- Utils ---------------------------------------------------------------------------------------------- local function formatMessage(message) local msgToSend = message if pursuit.suspect == nil then msgToSend = string.gsub(msgToSend,"`CAR`", "No Car") msgToSend = string.gsub(msgToSend,"`NAME`", "No Name") msgToSend = string.gsub(msgToSend,"`SPEED`", "No Speed") return msgToSend end msgToSend = string.gsub(msgToSend,"`CAR`", string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", "")) msgToSend = string.gsub(msgToSend,"`NAME`", "@" .. ac.getDriverName(pursuit.suspect.index)) msgToSend = string.gsub(msgToSend,"`SPEED`", string.format("%d ", ac.getCarSpeedKmh(pursuit.suspect.index) * settings.unitMult) .. settings.unit) return msgToSend end ---------------------------------------------------------------------------------------------- HUD ---------------------------------------------------------------------------------------------- local policeLightsPos = { vec2(0,0), vec2(windowWidth/10,windowHeight), vec2(windowWidth-windowWidth/10,0), vec2(windowWidth,windowHeight) } local function showPoliceLights() local timing = math.floor(os.clock()*2 % 2) if timing == 0 then ui.drawRectFilledMultiColor(policeLightsPos[1], policeLightsPos[2], rgbm(1,0,0,0.5), rgbm(0,0,0,0), rgbm(0,0,0,0), rgbm(1,0,0,0.5)) ui.drawRectFilledMultiColor(policeLightsPos[3], policeLightsPos[4], rgbm(0,0,0,0), rgbm(0,0,1,0.5), rgbm(0,0,1,0.5), rgbm(0,0,0,0)) else ui.drawRectFilledMultiColor(policeLightsPos[1], policeLightsPos[2], rgbm(0,0,1,0.5), rgbm(0,0,0,0), rgbm(0,0,0,0), rgbm(0,0,1,0.5)) ui.drawRectFilledMultiColor(policeLightsPos[3], policeLightsPos[4], rgbm(0,0,0,0), rgbm(1,0,0,0.5), rgbm(1,0,0,0.5), rgbm(0,0,0,0)) end end local chaseLVL = { message = "", messageTimer = 0, color = rgbm(1,1,1,1), } local function resetChase() pursuit.enable = false pursuit.nextMessage = 30 pursuit.lostSight = false pursuit.timeLostSight = 2 end local function lostSuspect() resetChase() pursuit.lostSight = false pursuit.timeLostSight = 0 pursuit.level = 1 ac.sendChatMessage(formatMessage(msgLost.msg[math.random(#msgLost.msg)])) pursuit.suspect = nil if cspAboveP218 then ac.setExtraSwitch(0, false) end end local iconsColorOn = { [1] = rgbm(1,0,0,1), [2] = rgbm(1,1,1,1), [3] = rgbm(1,1,1,1), [4] = rgbm(1,1,1,1), [5] = rgbm(1,1,1,1), [6] = rgbm(1,1,1,1), } local playersInRange = {} local function drawImage() iconsColorOn[2] = rgbm(0.99,0.99,0.99,1) iconsColorOn[3] = rgbm(0.99,0.99,0.99,1) iconsColorOn[4] = rgbm(0.99,0.99,0.99,1) iconsColorOn[5] = rgbm(0.99,0.99,0.99,1) iconsColorOn[6] = rgbm(0.99,0.99,0.99,1) local uiStats = ac.getUI() if ui.rectHovered(iconPos.arrest2, iconPos.arrest1) then iconsColorOn[2] = rgbm(1,0,0,1) if pursuit.suspect and pursuit.suspect.speedKmh < 50 and car.speedKmh < 20 and uiStats.isMouseLeftKeyClicked then pursuit.hasArrested = true end elseif ui.rectHovered(iconPos.cams2, iconPos.cams1) then iconsColorOn[3] = rgbm(1,0,0,1) if uiStats.isMouseLeftKeyClicked then if camerasOpen then camerasOpen = false else camerasOpen = true arrestLogsOpen = false if settingsOpen then onsettingsChange() settingsOpen = false end end end elseif ui.rectHovered(iconPos.lost2, iconPos.lost1) then iconsColorOn[4] = rgbm(1,0,0,1) if pursuit.suspect and uiStats.isMouseLeftKeyClicked then lostSuspect() end elseif ui.rectHovered(iconPos.logs2, iconPos.logs1) then iconsColorOn[5] = rgbm(1,0,0,1) if uiStats.isMouseLeftKeyClicked then if arrestLogsOpen then arrestLogsOpen = false else arrestLogsOpen = true camerasOpen = false if settingsOpen then onsettingsChange() settingsOpen = false end end end elseif ui.rectHovered(iconPos.menu2, iconPos.menu1) then iconsColorOn[6] = rgbm(1,0,0,1) if uiStats.isMouseLeftKeyClicked then if settingsOpen then onsettingsChange() settingsOpen = false else settingsOpen = true arrestLogsOpen = false camerasOpen = false end end end ui.image(hudImg.base, imageSize, rgbm.colors.white) ui.drawImage(hudImg.radar, vec2(0,0), imageSize, iconsColorOn[1]) ui.drawImage(hudImg.arrest, vec2(0,0), imageSize, iconsColorOn[2]) ui.drawImage(hudImg.cams, vec2(0,0), imageSize, iconsColorOn[3]) ui.drawImage(hudImg.lost, vec2(0,0), imageSize, iconsColorOn[4]) ui.drawImage(hudImg.logs, vec2(0,0), imageSize, iconsColorOn[5]) ui.drawImage(hudImg.menu, vec2(0,0), imageSize, iconsColorOn[6]) end local function playerSelected(player) if player.speedKmh> 50 then pursuit.suspect = player pursuit.nextMessage = 30 pursuit.level = 1 local msgToSend = "Officer " .. ac.getDriverName(0) .. " is chasing you. Run! " pursuit.startedTime = settings.timeMsg pursuit.engage = true acpPolice{message = msgToSend, messageType = 0, yourIndex = ac.getCar(pursuit.suspect.index).sessionID} if cspAboveP218 then ac.setExtraSwitch(0, true) end end end local function hudInChase() ui.pushDWriteFont("Orbitron;Weight=Black") ui.sameLine(20) ui.beginGroup() ui.newLine(1) local textPursuit = "LVL : " .. math.floor(pursuit.level/2) ui.dwriteTextWrapped(ac.getDriverName(pursuit.suspect.index) .. '\n' .. string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", "") .. '\n' .. string.format("Speed: %d ", pursuit.suspect.speedKmh * settings.unitMult) .. settings.unit .. '\n' .. textPursuit, settings.fontSize/2, rgbm.colors.white) ui.dummy(vec2(imageSize.x/5,imageSize.y/20)) ui.newLine(30) ui.sameLine() if ui.button('Cancel Chase', vec2(imageSize.x/5, imageSize.y/20)) then lostSuspect() end ui.endGroup() ui.popDWriteFont() end local function drawText() local uiStats = ac.getUI() ui.pushDWriteFont("Orbitron;Weight=Bold") ui.dwriteDrawText("RADAR ACTIVE", settings.fontSize/2, vec2((textPos.box2.x - ui.measureDWriteText("RADAR ACTIVE", settings.fontSize/2).x)/2, 0), rgbm(1,0,0,1)) ui.popDWriteFont() ui.pushDWriteFont("Orbitron;Weight=Regular") ui.dwriteDrawText("NEARBY VEHICULE SPEED SCANNING", settings.fontSize/3, vec2((textPos.box2.x - ui.measureDWriteText("NEARBY VEHICULE SPEED SCANNING", settings.fontSize/3).x)/2, settings.fontSize/1.5), rgbm(1,0,0,1)) local colorText = rgbm(1,1,1,1) textPos.box1 = vec2(0, textSize.size.y*2.5) ui.dummy(vec2(textPos.box2.x,settings.fontSize)) for i = 1, #playersInRange do colorText = rgbm(1,1,1,1) ui.drawRect(vec2(textPos.box2.x/9,textPos.box1.y), vec2(textPos.box2.x*8/9, textPos.box1.y + textPos.box2.y), rgbm(1,1,1,0.1), 1) if ui.rectHovered(textPos.box1, textPos.box1 + textPos.box2) then colorText = rgbm(1,0,0,1) if uiStats.isMouseLeftKeyClicked then playerSelected(playersInRange[i].player) end end ui.dwriteDrawText(playersInRange[i].text, settings.fontSize/2, vec2((textPos.box2.x - ui.measureDWriteText(ac.getDriverName(playersInRange[i].player.index) .. " - 000 " .. settings.unit, settings.fontSize/2).x)/2, textPos.box1.y + textSize.size.y/5), colorText) textPos.box1 = textPos.box1 + textPos.addBox ui.dummy(vec2(textPos.box2.x, i * settings.fontSize/5)) end ui.popDWriteFont() end local function radarUI() ui.toolWindow('radarText', textSize.window1, textSize.window2, true, function () ui.childWindow('childradar', textSize.window2, true , function () if pursuit.suspect then hudInChase() else drawText() end end) end) ui.transparentWindow('radar', vec2(settings.hudOffsetX, settings.hudOffsetY), imageSize, true, function () drawImage() end) end local function hidePlayers() local hideRange = 500 for i = ac.getSim().carsCount - 1, 0, -1 do local player = ac.getCar(i) local playerCarID = ac.getCarID(i) if player.isConnected and ac.getCarBrand(i) ~= "traffic" then if playerCarID ~= valideCar[1] and playerCarID ~= valideCar[2] and playerCarID ~= valideCar[3] then if player.position.x> car.position.x - hideRange and player.position.z> car.position.z - hideRange and player.position.x < car.position.x + hideRange and player.position.z < car.position.z + hideRange then ac.hideCarLabels(i, false) else ac.hideCarLabels(i, true) end end end end end local function radarUpdate() if firstload and not pursuit.suspect then return end local radarRange = 250 local previousSize = #playersInRange local j = 1 for i = ac.getSim().carsCount - 1, 0, -1 do local player = ac.getCar(i) local playerCarID = ac.getCarID(i) if player.isConnected and ac.getCarBrand(i) ~= "traffic" then if playerCarID ~= valideCar[1] and playerCarID ~= valideCar[2] and playerCarID ~= valideCar[3] then if player.position.x> car.position.x - radarRange and player.position.z> car.position.z - radarRange and player.position.x < car.position.x + radarRange and player.position.z < car.position.z + radarRange then playersInRange[j] = {} playersInRange[j].player = player playersInRange[j].text = ac.getDriverName(player.index) .. string.format(" - %d ", player.speedKmh * settings.unitMult) .. settings.unit j = j + 1 if j == 9 then break end end end end end for i = j, previousSize do playersInRange[i] = nil end end ---------------------------------------------------------------------------------------------- Chase ---------------------------------------------------------------------------------------------- local function inRange() local distance_x = pursuit.suspect.position.x - car.position.x local distance_z = pursuit.suspect.position.z - car.position.z local distanceSquared = distance_x * distance_x + distance_z * distance_z if(distanceSquared < pursuit.minDistance) then pursuit.enable = true pursuit.lostSight = false pursuit.timeLostSight = 2 elseif (distanceSquared < pursuit.maxDistance) then resetChase() else if not pursuit.lostSight then pursuit.lostSight = true pursuit.timeLostSight = 2 else pursuit.timeLostSight = pursuit.timeLostSight - ui.deltaTime() if pursuit.timeLostSight < 0 then lostSuspect() end end end end local function sendChatToSuspect() if pursuit.enable then if 0 < pursuit.nextMessage then pursuit.nextMessage = pursuit.nextMessage - ui.deltaTime() elseif pursuit.nextMessage < 0 then local nb = tostring(pursuit.level) acpPolice{message = nb, messageType = 1, yourIndex = ac.getCar(pursuit.suspect.index).sessionID} if pursuit.level < 10 then pursuit.level = pursuit.level + 1 chaseLVL.messageTimer = settings.timeMsg chaseLVL.message = "CHASE LEVEL " .. math.floor(pursuit.level/2) if pursuit.level> 8 then chaseLVL.color = rgbm.colors.red elseif pursuit.level> 6 then chaseLVL.color = rgbm.colors.orange elseif pursuit.level> 4 then chaseLVL.color = rgbm.colors.yellow else chaseLVL.color = rgbm.colors.white end end pursuit.nextMessage = 30 end end end local function showPursuitMsg() local text = "" if chaseLVL.messageTimer> 0 then chaseLVL.messageTimer = chaseLVL.messageTimer - ui.deltaTime() text = chaseLVL.message end if pursuit.startedTime> 0 then if pursuit.suspect then text = "You are chasing " .. ac.getDriverName(pursuit.suspect.index) .. " driving a " .. string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", "") .. " ! Get him! " end if pursuit.startedTime> 6 then showPoliceLights() end if pursuit.engage and pursuit.startedTime < 8 then ac.sendChatMessage(formatMessage(msgEngage.msg[math.random(#msgEngage.msg)])) pursuit.engage = false end end if text ~= "" then local textLenght = ui.measureDWriteText(text, settings.fontSizeMSG) local rectPos1 = vec2(settings.msgOffsetX - textLenght.x/2, settings.msgOffsetY) local rectPos2 = vec2(settings.msgOffsetX + textLenght.x/2, settings.msgOffsetY + settings.fontSizeMSG) local rectOffset = vec2(10, 10) if ui.time() % 1 < 0.5 then ui.drawRectFilled(rectPos1 - vec2(10,0), rectPos2 + rectOffset, COLORSMSGBG, 10) else ui.drawRectFilled(rectPos1 - vec2(10,0), rectPos2 + rectOffset, rgbm(0,0,0,0.5), 10) end ui.dwriteDrawText(text, settings.fontSizeMSG, rectPos1, chaseLVL.color) end end local function arrestSuspect() if pursuit.hasArrested and pursuit.suspect then local msgToSend = formatMessage(msgArrest.msg[math.random(#msgArrest.msg)]) table.insert(arrestations, msgToSend .. os.date("\nDate of the Arrestation: %c")) ac.sendChatMessage(msgToSend .. "\nPlease Get Back Pit, GG!") pursuit.id = pursuit.suspect.sessionID if playerData.Arrests == nil then playerData.Arrests = 0 end playerData.Arrests = playerData.Arrests + 1 pursuit.startedTime = 0 pursuit.suspect = nil pursuit.timerArrest = 1 elseif pursuit.hasArrested then if pursuit.timerArrest> 0 then pursuit.timerArrest = pursuit.timerArrest - ui.deltaTime() else acpPolice{message = "BUSTED!", messageType = 2, yourIndex = pursuit.id} pursuit.timerArrest = 0 pursuit.suspect = nil pursuit.id = -1 pursuit.hasArrested = false pursuit.startedTime = 0 pursuit.enable = false pursuit.level = 1 pursuit.nextMessage = 20 pursuit.lostSight = false pursuit.timeLostSight = 0 local data = { ["Arrests"] = playerData.Arrests, } updatefirebase() updatefirebaseData("Arrests", data) end end end local function chaseUpdate() if pursuit.startedTime> 0 then pursuit.startedTime = pursuit.startedTime - ui.deltaTime() else pursuit.startedTime = 0 end if pursuit.suspect then sendChatToSuspect() inRange() end arrestSuspect() end ---------------------------------------------------------------------------------------------- Menu ---------------------------------------------------------------------------------------------- local function arrestLogsUI() ui.dwriteTextAligned("Arrestation Logs", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(windowWidth/4,60), false, rgbm.colors.white) ui.drawLine(vec2(0,60), vec2(windowWidth/4,60), rgbm.colors.white, 1) ui.newLine(15) ui.sameLine(10) ui.beginGroup() local allMsg = "" ui.dwriteText("Click on the button next to the message you want to copy.", 15, rgbm.colors.white) ui.sameLine(windowWidth/4 - 120) if ui.button('Close', vec2(100, windowHeight/50)) then arrestLogsOpen = false end for i = 1, #arrestations do if ui.smallButton("#" .. i .. ": ", vec2(0,10)) then ui.setClipboardText(arrestations[i]) end ui.sameLine() ui.dwriteTextWrapped(arrestations[i], 15, rgbm.colors.white) end if #arrestations == 0 then ui.dwriteText("No arrestation logs yet.", 15, rgbm.colors.white) end ui.newLine() if ui.button("Set all messages to ClipBoard") then for i = 1, #arrestations do allMsg = allMsg .. arrestations[i] .. "\n\n" end ui.setClipboardText(allMsg) end ui.endGroup() end local buttonPos = windowWidth/65 local function camerasUI() ui.dwriteTextAligned("Surveillance Cameras", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(windowWidth/6.5,60), false, rgbm.colors.white) ui.drawLine(vec2(0,60), vec2(windowWidth/6.5,60), rgbm.colors.white, 1) ui.newLine(20) ui.beginGroup() ui.sameLine(buttonPos) if ui.button('Close', vec2(windowWidth/6.5 - buttonPos*2,30)) then camerasOpen = false end ui.newLine() for i = 1, #cameras do local h = math.rad(cameras[i].dir + ac.getCompassAngle(vec3(0, 0, 1))) ui.newLine() ui.sameLine(buttonPos) if ui.button(cameras[i].name, vec2(windowWidth/6.5 - buttonPos*2,30)) then ac.setCurrentCamera(ac.CameraMode.Free) ac.setCameraPosition(cameras[i].pos) ac.setCameraDirection(vec3(math.sin(h), 0, math.cos(h))) ac.setCameraFOV(cameras[i].fov) end end if ac.getSim().cameraMode == ac.CameraMode.Free then ui.newLine() ui.newLine() ui.sameLine(buttonPos) if ui.button('Police car camera', vec2(windowWidth/6.5 - buttonPos*2,30)) then ac.setCurrentCamera(ac.CameraMode.Cockpit) end end end local initialized = false local menuSize = {vec2(windowWidth/4, windowHeight/3), vec2(windowWidth/6.4, windowHeight/2.2)} local buttonPressed = false local function moveMenu() if ui.windowHovered() and ui.mouseDown() then buttonPressed = true end if ui.mouseReleased() then buttonPressed = false end if buttonPressed then settings.menuPos = settings.menuPos + ui.mouseDelta() end end ---------------------------------------------------------------------------------------------- updates ---------------------------------------------------------------------------------------------- function script.drawUI() if carID ~= valideCar[1] and carID ~= valideCar[2] and carID ~= valideCar[3] or cspVersion < cspMinVersion then return end if initialized and settings.policeSize then if firstload then firstload = false initsettings() end radarUI() if pursuit.suspect then showStarsPursuit() end showPursuitMsg() if settingsOpen then ui.toolWindow('settings', settings.menuPos, menuSize[2], true, function () ui.childWindow('childsettings', menuSize[2], true, function () settingsWindow() moveMenu() end) end) elseif arrestLogsOpen then ui.toolWindow('ArrestLogs', settings.menuPos, menuSize[1], true, function () ui.childWindow('childArrestLogs', menuSize[1], true, function () arrestLogsUI() moveMenu() end) end) elseif camerasOpen then ui.toolWindow('Cameras', settings.menuPos, menuSize[2], true, function () ui.childWindow('childCameras', menuSize[2], true, function () camerasUI() moveMenu() end) end) end end end ac.onCarJumped(0, function (carid) if carID == valideCar[1] or carID == valideCar[2] or carID == valideCar[3]then if pursuit.suspect then lostSuspect() end end end) function script.update(dt) if carID ~= valideCar[1] and carID ~= valideCar[2] and carID ~= valideCar[3] or cspVersion < cspMinVersion then return end if not initialized then loadsettings() getFirebase() initialized = true else radarUpdate() chaseUpdate() --hidePlayers() end end if carID == valideCar[1] or carID == valideCar[2] or carID == valideCar[3] and cspVersion>= cspMinVersion then ui.registerOnlineExtra(ui.Icons.Settings, "Settings", nil, settingsWindow, nil, ui.OnlineExtraFlags.Tool, 'ui.WindowFlags.AlwaysAutoResize') end