SHARE
    TWEET
    supinus

    Police

    Jun 25th, 2025 (edited)
    978
    0
    Never
    Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
    Lua 49.26 KB | Gaming | 0 0
    1. ac.log('Script: Police')
    2. local sim = ac.getSim()
    3. local car = ac.getCar(0) or error()
    4. if not car then return end
    5. local wheels = car.wheels or error()
    6. local uiState = ac.getUI()
    7. ui.setAsynchronousImagesLoading(true)
    8. local localTesting = ac.dirname() == 'C:\\Program Files (x86)\\Steam\\steamapps\\common\\assettocorsa\\extension\\lua\\online'
    9. local initialisation = true
    10. -- Constants --
    11. local STEAMID = const(ac.getUserSteamID())
    12. local CSP_VERSION = const(ac.getPatchVersionCode())
    13. local CSP_MIN_VERSION = const(3116)
    14. local SHARED_PLAYER_DATA = const('__ACP_SHARED_PLAYER_DATA')
    15. local SHARED_EVENT_KEY = const('__ACP_PLAYER_SHARED_UPDATE')
    16. local CAR_ID = const(ac.getCarID(0))
    17. local CAR_NAME = const(ac.getCarName(0))
    18. local POLICE_CAR = const({ "ids_2022_ford_crown", "r34police_acp24" })
    19. local DRIVER_NAME = const(ac.getDriverName(0))
    20. ---@param carID string
    21. local function isPoliceCar(carID)
    22. for _, carName in ipairs(POLICE_CAR) do
    23. if carID == carName then
    24. return true
    25. end
    26. end
    27. return false
    28. end
    29. if CSP_VERSION < CSP_MIN_VERSION or not isPoliceCar(CAR_ID) then return end
    30. local DRIVER_NATION_CODE = const(ac.getDriverNationCode(0))
    31. local UNIT = "km/h"
    32. local UNIT_MULT = 1
    33. if DRIVER_NATION_CODE == "USA" or DRIVER_NATION_CODE == "GBR" then
    34. UNIT = "mph"
    35. UNIT_MULT = 0.621371
    36. end
    37. -- URL --
    38. local FIREBASE_URL = const('https://acp-server-97674-default-rtdb.firebaseio.com/')
    39. -- UI --
    40. local WINDOW_WIDTH = const(sim.windowWidth / uiState.uiScale)
    41. local WIDTH_DIV = const({
    42. _2 = WINDOW_WIDTH / 2,
    43. _3 = WINDOW_WIDTH / 3,
    44. _4 = WINDOW_WIDTH / 4,
    45. _5 = WINDOW_WIDTH / 5,
    46. _6 = WINDOW_WIDTH / 6,
    47. _10 = WINDOW_WIDTH / 10,
    48. _12 = WINDOW_WIDTH / 12,
    49. _15 = WINDOW_WIDTH / 15,
    50. _20 = WINDOW_WIDTH / 20,
    51. _25 = WINDOW_WIDTH / 25,
    52. _32 = WINDOW_WIDTH / 32,
    53. })
    54. local WINDOW_HEIGHT = const(sim.windowHeight / uiState.uiScale)
    55. local HEIGHT_DIV = const({
    56. _2 = WINDOW_HEIGHT / 2,
    57. _3 = WINDOW_HEIGHT / 3,
    58. _4 = WINDOW_HEIGHT / 4,
    59. _12 = WINDOW_HEIGHT / 12,
    60. _20 = WINDOW_HEIGHT / 20,
    61. _40 = WINDOW_HEIGHT / 40,
    62. _50 = WINDOW_HEIGHT / 50,
    63. _60 = WINDOW_HEIGHT / 60,
    64. _70 = WINDOW_HEIGHT / 70,
    65. _80 = WINDOW_HEIGHT / 80,
    66. })
    67. local FONT_MULT = const(WINDOW_HEIGHT / 1440)
    68. local HUD_IMG = {}
    69. local IMAGES = const({
    70. police = {
    71. url = "https://github.com/ele-sage/ACP-apps/raw/refs/heads/master/images/police.zip",
    72. hud = {
    73. "base.png",
    74. "arrest.png",
    75. "cams.png",
    76. "logs.png",
    77. "lost.png",
    78. "menu.png",
    79. "radar.png",
    80. },
    81. },
    82. })
    83. ---@param key string
    84. local function loadImages(key)
    85. web.loadRemoteAssets(IMAGES[key].url, function(err, data)
    86. if err then
    87. ac.error('Failed to load welcome images:', err)
    88. return
    89. end
    90. local path = data .. '/' .. key .. '/'
    91. local files = io.scanDir(path, "*")
    92. for i, file in ipairs(files) do
    93. if table.contains(IMAGES.police.hud, file) then
    94. local k = file:match('(.+)%..+')
    95. HUD_IMG[k] = path .. file
    96. end
    97. end
    98. end)
    99. end
    100. loadImages("police")
    101. local CAMERAS = const({
    102. {
    103. name = "BOBs SCRAPYARD",
    104. pos = vec3(-3564, 31.5, -103),
    105. dir = -8,
    106. fov = 60,
    107. },
    108. {
    109. name = "ARENA",
    110. pos = vec3(-2283, 115.5, 3284),
    111. dir = 128,
    112. fov = 70,
    113. },
    114. {
    115. name = "BANK",
    116. pos = vec3(-716, 151, 3556.4),
    117. dir = 12,
    118. fov = 95,
    119. },
    120. {
    121. name = "STREET RUNNERS",
    122. pos = vec3(-57.3, 103.5, 2935.5),
    123. dir = 16,
    124. fov = 67,
    125. },
    126. {
    127. name = "ROAD CRIMINALS",
    128. pos = vec3(-2332, 101.1, 3119.2),
    129. dir = 121,
    130. fov = 60,
    131. },
    132. {
    133. name = "RECKLESS RENEGADES",
    134. pos = vec3(-2993.7, -24.4, -601.7),
    135. dir = -64,
    136. fov = 60,
    137. },
    138. {
    139. name = "MOTION MASTERS",
    140. pos = vec3(-2120.4, -11.8, -1911.5),
    141. dir = 102,
    142. fov = 60,
    143. },
    144. })
    145. local MSG_ARREST = const({
    146. "`NAME` has been arrested for Speeding. The individual was driving a `CAR`.",
    147. "We have apprehended `NAME` for Speeding. The suspect was behind the wheel of a `CAR`.",
    148. "The driver of a `CAR`, identified as `NAME`, has been arrested for Speeding.",
    149. "`NAME` has been taken into custody for Illegal Racing. The suspect was driving a `CAR`.",
    150. "We have successfully apprehended `NAME` for Illegal Racing. The individual was operating a `CAR`.",
    151. "The driver of a `CAR`, identified as `NAME`, has been arrested for Illegal Racing.",
    152. "`NAME` has been apprehended for Speeding. The suspect was operating a `CAR` at the time of the arrest.",
    153. "We have successfully detained `NAME` for Illegal Racing. The individual was driving a `CAR`.",
    154. "`NAME` driving a `CAR` has been arrested for Speeding",
    155. "`NAME` driving a `CAR` has been arrested for Illegal Racing."
    156. })
    157. local MSG_LOST = const({
    158. "We've lost sight of the suspect. The vehicle involved is described as a `CAR` driven by `NAME`.",
    159. "Attention all units, we have lost visual contact with the suspect. The vehicle involved is a `CAR` driven by `NAME`.",
    160. "We have temporarily lost track of the suspect. The vehicle description is a `CAR` with `NAME` as the driver.",
    161. "Visual contact with the suspect has been lost. The suspect is driving a `CAR` and identified as `NAME`.",
    162. "We have lost the suspect's visual trail. The vehicle in question is described as a `CAR` driven by `NAME`.",
    163. "Suspect have been lost, Vehicle Description:`CAR` driven by `NAME`",
    164. "Visual contact with the suspect has been lost. The suspect is driving a `CAR` and identified as `NAME`.",
    165. "We have lost the suspect's visual trail. The vehicle in question is described as a `CAR` driven by `NAME`.",
    166. })
    167. local MSG_ENGAGE = const({
    168. "Control! I am engaging on a `CAR` traveling at `SPEED`",
    169. "Pursuit in progress! I am chasing a `CAR` exceeding `SPEED`",
    170. "Control, be advised! Pursuit is active on a `CAR` driving over `SPEED`",
    171. "Attention! Pursuit initiated! Im following a `CAR` going above `SPEED`",
    172. "Pursuit engaged! `CAR` driving at a high rate of speed over `SPEED`",
    173. "Attention all units, we have a pursuit in progress! Suspect driving a `CAR` exceeding `SPEED`",
    174. "Attention units! We have a suspect fleeing in a `CAR` at high speed, pursuing now at `SPEED`",
    175. "Engaging on a high-speed chase! Suspect driving a `CAR` exceeding `SPEED`!",
    176. "Attention all units! we have a pursuit in progress! Suspect driving a `CAR` exceeding `SPEED`",
    177. "High-speed chase underway, suspect driving `CAR` over `SPEED`",
    178. "Control, `CAR` exceeding `SPEED`, pursuit active.",
    179. "Engaging on a `CAR` exceeding `SPEED`, pursuit initiated."
    180. })
    181. local dataLoaded = {}
    182. dataLoaded['Settings'] = false
    183. dataLoaded['PlayerData'] = false
    184. ---@param key string
    185. local function removeUtf8Char(key)
    186. local newKey = ''
    187. for i = 1, #key do
    188. local c = key:sub(i, i)
    189. if c:byte() < 128 then
    190. newKey = newKey .. c
    191. end
    192. end
    193. newKey = newKey:match('^%s*(.-)%s*$')
    194. return newKey
    195. end
    196. local CAR_NAME_NO_UTF8 = removeUtf8Char(CAR_NAME)
    197. --------- Utils ------------
    198. ---@param keys string[]
    199. ---@param t table
    200. local function hasKeys(keys, t)
    201. for i = 1, #keys do
    202. if not t[keys[i]] then
    203. ac.error('Missing key:', keys[i])
    204. return false
    205. end
    206. end
    207. return true
    208. end
    209. ---@param number number
    210. ---@param decimal integer
    211. ---@return number
    212. local function truncate(number, decimal)
    213. local power = 10 ^ decimal
    214. return math.floor(number * power) / power
    215. end
    216. ---@param t table
    217. local function tableToVec3(t)
    218. return vec3(t[1], t[2], t[3])
    219. end
    220. ---@param t table
    221. local function tableToVec2(t)
    222. return vec2(t[1], t[2])
    223. end
    224. ---@param t table
    225. local function tableToRGBM(t)
    226. return rgbm(t[1], t[2], t[3], t[4])
    227. end
    228. ---@param err string
    229. ---@param response WebResponse
    230. ---@return boolean
    231. local function canProcessRequest(err, response)
    232. if err then
    233. ac.error('Failed to process request:', err)
    234. return false
    235. end
    236. return response.status == 200 and response.body ~= ''
    237. end
    238. ---@param response WebResponse
    239. ---@return boolean
    240. local function hasExistingData(response)
    241. return response.status == 200 and response.body ~= 'null'
    242. end
    243. ---@param v vec3
    244. ---@return vec3
    245. local function snapToTrack(v)
    246. if physics.raycastTrack(v, vDown, 20, v) == -1 then
    247. physics.raycastTrack(v, vUp, 20, v)
    248. end
    249. return v
    250. end
    251. ---@param time number
    252. ---@return string
    253. local function formatTime(time)
    254. local minutes = math.floor(time / 60)
    255. local seconds = math.floor(time % 60)
    256. local milliseconds = math.floor((time % 1) * 1000)
    257. return ('%02d:%02d.%03d'):format(minutes, seconds, milliseconds)
    258. end
    259. local DEFAULT_SETTINGS = const({
    260. essentialSize = 20,
    261. policeSize = 20,
    262. hudOffset = vec2(0, 0),
    263. fontSize = 20,
    264. current = 1,
    265. colorHud = rgbm(1, 0, 0, 1),
    266. timeMsg = 10,
    267. msgOffset = vec2(WIDTH_DIV._2, 10),
    268. fontSizeMSG = 30,
    269. menuPos = vec2(0, 0),
    270. unit = UNIT,
    271. unitMult = UNIT_MULT,
    272. starsSize = 20,
    273. starsPos = vec2(WINDOW_WIDTH, 0),
    274. })
    275. ---@class Settings
    276. ---@field essentialSize number
    277. ---@field policeSize number
    278. ---@field hudOffset vec2
    279. ---@field fontSize number
    280. ---@field current number
    281. ---@field colorHud rgbm
    282. ---@field timeMsg number
    283. ---@field msgOffset vec2
    284. ---@field fontSizeMSG number
    285. ---@field menuPos vec2
    286. ---@field unit string
    287. ---@field unitMult number
    288. ---@field starsSize number
    289. ---@field starsPos vec2
    290. local Settings = class('Settings')
    291. ---@return Settings
    292. function Settings.new()
    293. local settings = table.clone(DEFAULT_SETTINGS, true)
    294. setmetatable(settings, { __index = Settings })
    295. return settings
    296. end
    297. ---@param data table
    298. ---@return Settings
    299. function Settings.tryParse(data)
    300. local hudOffset = data.hudOffset and tableToVec2(data.hudOffset) or vec2(0, 0)
    301. local colorHud = data.colorHud and tableToRGBM(data.colorHud) or rgbm(1, 0, 0, 1)
    302. local msgOffset = data.msgOffset and tableToVec2(data.msgOffset) or vec2(WIDTH_DIV._2, 10)
    303. local menuPos = data.menuPos and tableToVec2(data.menuPos) or vec2(0, 0)
    304. local starsPos = data.starsPos and tableToVec2(data.starsPos) or vec2(WINDOW_WIDTH, 0)
    305. local settings = {
    306. essentialSize = data.essentialSize or 20,
    307. policeSize = data.policeSize or 20,
    308. hudOffset = hudOffset,
    309. fontSize = data.fontSize or 20,
    310. current = data.current or 1,
    311. colorHud = colorHud,
    312. timeMsg = data.timeMsg or 10,
    313. msgOffset = msgOffset,
    314. fontSizeMSG = data.fontSizeMSG or 30,
    315. menuPos = menuPos,
    316. unit = data.unit or UNIT,
    317. unitMult = data.unitMult or UNIT_MULT,
    318. starsSize = data.starsSize or 20,
    319. starsPos = starsPos,
    320. }
    321. setmetatable(settings, { __index = Settings })
    322. return settings
    323. end
    324. ---@param url string
    325. ---@param callback function
    326. function Settings.fetch(url, callback)
    327. if localTesting then
    328. local currentPath = ac.getFolder(ac.FolderID.ScriptOrigin)
    329. local file = io.open(currentPath .. '/response/settingsResponse.json', 'r')
    330. if not file then
    331. ac.error('Failed to open response.json')
    332. callback(Settings.new())
    333. return
    334. end
    335. local data = JSON.parse(file:read('*a'))
    336. file:close()
    337. local settings = Settings.tryParse(data)
    338. callback(settings)
    339. else
    340. web.get(url, function(err, response)
    341. if canProcessRequest(err, response) then
    342. if hasExistingData(response) then
    343. local data = JSON.parse(response.body)
    344. if data then
    345. local settings = Settings.tryParse(data)
    346. callback(settings)
    347. else
    348. ac.error('Failed to parse settings data.')
    349. callback(Settings.new())
    350. end
    351. else
    352. callback(Settings.new())
    353. end
    354. else
    355. ac.error('Failed to fetch settings:', err)
    356. callback(Settings.new())
    357. end
    358. end)
    359. end
    360. end
    361. ---@param callback function
    362. function Settings.allocate(callback)
    363. local url = FIREBASE_URL .. 'Settings/' .. STEAMID .. '.json'
    364. ac.log('Loading settings')
    365. Settings.fetch(url, function(settings)
    366. callback(settings)
    367. end)
    368. end
    369. ---@return table
    370. function Settings:export()
    371. local data = {}
    372. if self.essentialSize ~= DEFAULT_SETTINGS.essentialSize then
    373. data.essentialSize = self.essentialSize
    374. end
    375. if self.policeSize ~= DEFAULT_SETTINGS.policeSize then
    376. data.policeSize = self.policeSize
    377. end
    378. if self.hudOffset ~= DEFAULT_SETTINGS.hudOffset then
    379. data.hudOffset = { self.hudOffset.x, self.hudOffset.y }
    380. end
    381. if self.fontSize ~= DEFAULT_SETTINGS.fontSize then
    382. data.fontSize = self.fontSize
    383. end
    384. if self.current ~= DEFAULT_SETTINGS.current then
    385. data.current = self.current
    386. end
    387. if self.colorHud ~= DEFAULT_SETTINGS.colorHud then
    388. data.colorHud = { self.colorHud.r, self.colorHud.g, self.colorHud.b, self.colorHud.mult }
    389. end
    390. if self.timeMsg ~= DEFAULT_SETTINGS.timeMsg then
    391. data.timeMsg = self.timeMsg
    392. end
    393. if self.msgOffset ~= DEFAULT_SETTINGS.msgOffset then
    394. data.msgOffset = { self.msgOffset.x, self.msgOffset.y }
    395. end
    396. if self.fontSizeMSG ~= DEFAULT_SETTINGS.fontSizeMSG then
    397. data.fontSizeMSG = self.fontSizeMSG
    398. end
    399. if self.menuPos ~= DEFAULT_SETTINGS.menuPos then
    400. data.menuPos = { self.menuPos.x, self.menuPos.y }
    401. end
    402. if self.unit ~= DEFAULT_SETTINGS.unit then
    403. data.unit = self.unit
    404. end
    405. if self.unitMult ~= DEFAULT_SETTINGS.unitMult then
    406. data.unitMult = self.unitMult
    407. end
    408. if self.starsSize ~= DEFAULT_SETTINGS.starsSize then
    409. data.starsSize = self.starsSize
    410. end
    411. if self.starsPos ~= DEFAULT_SETTINGS.starsPos then
    412. data.starsPos = { self.starsPos.x, self.starsPos.y }
    413. end
    414. return data
    415. end
    416. function Settings:save()
    417. if localTesting then return end
    418. local str = '{"' .. STEAMID .. '": ' .. JSON.stringify(self:export()) .. '}'
    419. web.request('PATCH', FIREBASE_URL .. "Settings.json", str, function(err, response)
    420. if err then
    421. ac.error(err)
    422. return
    423. end
    424. end)
    425. end
    426. local patchCount = 0
    427. ---@class SectorStats
    428. ---@field name string
    429. ---@field records table<string, number>
    430. local SectorStats = class('SectorStats')
    431. ---@param name string
    432. ---@param data table
    433. ---@return SectorStats
    434. function SectorStats.tryParse(name, data)
    435. local records = {}
    436. for carName, time in pairs(data) do
    437. local nameWithoutUtf8 = removeUtf8Char(carName)
    438. records[nameWithoutUtf8] = time
    439. end
    440. local sectorStats = {
    441. name = name,
    442. records = records,
    443. }
    444. setmetatable(sectorStats, { __index = SectorStats })
    445. return sectorStats
    446. end
    447. ---@param name string
    448. ---@param data table
    449. ---@return SectorStats|nil
    450. function SectorStats.allocate(name, data)
    451. if type(data) == 'table' then
    452. local sectorStats = SectorStats.tryParse(name, data)
    453. if not sectorStats then
    454. ac.error('Failed to allocate sector stat')
    455. return nil
    456. end
    457. return sectorStats
    458. end
    459. if type(data) == 'number' then
    460. local records = {}
    461. records[CAR_NAME_NO_UTF8] = data
    462. local sectorStats = {
    463. name = name,
    464. records = records,
    465. }
    466. setmetatable(sectorStats, { __index = SectorStats })
    467. return sectorStats
    468. end
    469. ac.error('Failed to allocate sector stat')
    470. return nil
    471. end
    472. ---@param time number
    473. ---@return boolean
    474. function SectorStats:addRecord(time)
    475. if not self.records[CAR_NAME_NO_UTF8] or self.records[CAR_NAME_NO_UTF8] > time then
    476. self.records[CAR_NAME_NO_UTF8] = time
    477. return true
    478. end
    479. return false
    480. end
    481. ---@return table
    482. function SectorStats:export()
    483. local records = {}
    484. for carName, time in pairs(self.records) do
    485. records[carName] = truncate(time, 3)
    486. end
    487. return {
    488. [self.name] = records
    489. }
    490. end
    491. local lastRegister = {
    492. kms = 0,
    493. time = os.clock(),
    494. }
    495. ---@class Player
    496. ---@field name string
    497. ---@field sectors SectorStats[]
    498. ---@field sectorsFormated table<string, table<string, string>>
    499. ---@field arrests integer
    500. ---@field getaways integer
    501. ---@field thefts integer
    502. ---@field heists integer
    503. ---@field deliveries integer
    504. ---@field overtake integer
    505. ---@field wins integer
    506. ---@field losses integer
    507. ---@field elo integer
    508. ---@field kms number
    509. ---@field time number
    510. local Player = class('Player')
    511. ---@type Player | nil
    512. local player = nil
    513. local sharedPlayerLayout = {
    514. ac.StructItem.key(SHARED_PLAYER_DATA),
    515. hudColor = ac.StructItem.rgbm(),
    516. name = ac.StructItem.string(24),
    517. sectorsFormated = ac.StructItem.array(ac.StructItem.struct({
    518. name = ac.StructItem.string(16),
    519. records = ac.StructItem.array(ac.StructItem.string(50), 20)
    520. }), 5),
    521. arrests = ac.StructItem.uint16(),
    522. getaways = ac.StructItem.uint16(),
    523. thefts = ac.StructItem.uint16(),
    524. heists = ac.StructItem.uint16(),
    525. deliveries = ac.StructItem.uint16(),
    526. overtake = ac.StructItem.uint32(),
    527. wins = ac.StructItem.uint16(),
    528. losses = ac.StructItem.uint16(),
    529. elo = ac.StructItem.uint16(),
    530. kms = ac.StructItem.float(),
    531. time = ac.StructItem.float(),
    532. }
    533. ---@type Settings | nil
    534. local settings = nil
    535. local sharedPlayerData = ac.connect(sharedPlayerLayout, true, ac.SharedNamespace.ServerScript)
    536. local function updateSharedPlayerData()
    537. if not player then return end
    538. local hudC = rgbm.colors.red
    539. if settings then
    540. hudC = settings.colorHud
    541. end
    542. sharedPlayerData.hudColor = hudC
    543. sharedPlayerData.name = player.name
    544. sharedPlayerData.arrests = player.arrests
    545. sharedPlayerData.getaways = player.getaways
    546. sharedPlayerData.thefts = player.thefts
    547. sharedPlayerData.heists = player.heists
    548. sharedPlayerData.deliveries = player.deliveries
    549. sharedPlayerData.overtake = player.overtake
    550. sharedPlayerData.wins = player.wins
    551. sharedPlayerData.losses = player.losses
    552. sharedPlayerData.elo = player.elo
    553. sharedPlayerData.kms = player.kms
    554. sharedPlayerData.time = player.time
    555. sharedPlayerData.sectorsFormated = {}
    556. local i = 1
    557. table.forEach(player.sectorsFormated, function(v, k)
    558. sharedPlayerData.sectorsFormated[i].name = k .. '0円'
    559. for j, entry in ipairs(v) do
    560. local carName = string.sub(entry[1], 1, 45)
    561. sharedPlayerData.sectorsFormated[i].records[j] = carName .. ' - ' .. entry[2] .. '0円'
    562. end
    563. i = i + 1
    564. end)
    565. end
    566. ---@return Player
    567. function Player.new()
    568. local _player = {
    569. name = DRIVER_NAME,
    570. sectors = {},
    571. sectorsFormated = {},
    572. arrests = 0,
    573. getaways = 0,
    574. thefts = 0,
    575. heists = 0,
    576. deliveries = 0,
    577. overtake = 0,
    578. wins = 0,
    579. losses = 0,
    580. elo = 1200,
    581. kms = 0,
    582. time = 0,
    583. }
    584. setmetatable(_player, { __index = Player })
    585. return _player
    586. end
    587. ---@param data table
    588. ---@return Player
    589. function Player.tryParse(data)
    590. if not data then
    591. return Player.new()
    592. end
    593. local sectors = {}
    594. if data.sectors then
    595. for sectorName, sectorData in pairs(data.sectors) do
    596. local sector = SectorStats.allocate(sectorName, sectorData)
    597. if sector then
    598. table.insert(sectors, sector)
    599. end
    600. end
    601. end
    602. local _player = {
    603. name = DRIVER_NAME,
    604. sectors = sectors,
    605. sectorsFormated = {},
    606. arrests = data.arrests or 0,
    607. getaways = data.getaways or 0,
    608. thefts = data.thefts or 0,
    609. heists = data.heists or 0,
    610. deliveries = data.deliveries or 0,
    611. overtake = data.overtake or 0,
    612. wins = data.wins or 0,
    613. losses = data.losses or 0,
    614. elo = data.elo or 1200,
    615. kms = data.kms or 0,
    616. time = data.time or 0,
    617. }
    618. setmetatable(_player, { __index = Player })
    619. return _player
    620. end
    621. ---@param url string
    622. ---@param callback function
    623. function Player.fetch(url, callback)
    624. if localTesting then
    625. local currentPath = ac.getFolder(ac.FolderID.ScriptOrigin)
    626. local file = io.open(currentPath .. '/response/playerResponse.json', 'r')
    627. if not file then
    628. ac.error('Failed to open playerResponse.json')
    629. callback(Player.new())
    630. return
    631. end
    632. local data = JSON.parse(file:read('*a'))
    633. file:close()
    634. local _player = Player.tryParse(data)
    635. callback(_player)
    636. else
    637. web.get(url, function(err, response)
    638. if canProcessRequest(err, response) then
    639. if hasExistingData(response) then
    640. local data = JSON.parse(response.body)
    641. if data then
    642. local _player = Player.tryParse(data)
    643. callback(_player)
    644. else
    645. ac.error('Failed to parse player data.')
    646. callback(Player.new())
    647. end
    648. else
    649. callback(Player.new())
    650. end
    651. else
    652. ac.error('Failed to fetch player:', err)
    653. callback(Player.new())
    654. end
    655. end)
    656. end
    657. end
    658. ---@param callback function
    659. function Player.allocate(callback)
    660. local url = FIREBASE_URL .. 'Players/' .. STEAMID .. '.json'
    661. Player.fetch(url, function(_player)
    662. callback(_player)
    663. end)
    664. end
    665. function Player:sortSectors()
    666. for _, sector in ipairs(self.sectors) do
    667. local entries = {}
    668. for carName, time in pairs(sector.records) do
    669. table.insert(entries, { carName, time })
    670. end
    671. table.sort(entries, function(a, b)
    672. return a[2] < b[2]
    673. end)
    674. for i, entry in ipairs(entries) do
    675. entries[i][2] = formatTime(entry[2])
    676. end
    677. self.sectorsFormated[sector.name] = entries
    678. end
    679. end
    680. ---@return table
    681. function Player:export()
    682. local kms = truncate(car.distanceDrivenSessionKm - lastRegister.kms + self.kms, 3)
    683. local time = math.round(os.clock() - lastRegister.time + self.time, 0)
    684. local data = { name = self.name }
    685. if self.arrests > 0 then
    686. data.arrests = self.arrests
    687. end
    688. if self.getaways > 0 then
    689. data.getaways = self.getaways
    690. end
    691. if self.thefts > 0 then
    692. data.thefts = self.thefts
    693. end
    694. if self.heists > 0 then
    695. data.heists = self.heists
    696. end
    697. if self.deliveries > 0 then
    698. data.deliveries = self.deliveries
    699. end
    700. if self.overtake > 0 then
    701. data.overtake = self.overtake
    702. end
    703. if self.wins > 0 then
    704. data.wins = self.wins
    705. end
    706. if self.losses > 0 then
    707. data.losses = self.losses
    708. end
    709. if self.elo ~= 1200 then
    710. data.elo = self.elo
    711. end
    712. if kms > 0 then
    713. data.kms = kms
    714. end
    715. if time > 0 then
    716. data.time = time
    717. end
    718. lastRegister.kms = car.distanceDrivenSessionKm
    719. lastRegister.time = os.clock()
    720. local sectors = {}
    721. for _, sector in ipairs(self.sectors) do
    722. if not sector then
    723. break
    724. end
    725. local sectorData = sector:export()
    726. for k, v in pairs(sectorData) do
    727. sectors[k] = v
    728. end
    729. end
    730. if next(sectors) then
    731. data.sectors = sectors
    732. end
    733. self:sortSectors()
    734. updateSharedPlayerData()
    735. return data
    736. end
    737. function Player:save()
    738. local str = '{"' .. STEAMID .. '": ' .. JSON.stringify(self:export()) .. '}'
    739. if localTesting or patchCount > 40 then return end
    740. patchCount = patchCount + 1
    741. web.request('PATCH', FIREBASE_URL .. "Players.json", str, function(err, response)
    742. if err then
    743. ac.error(err)
    744. return
    745. end
    746. end)
    747. end
    748. local canRun = false
    749. local function shouldRun()
    750. if canRun then return true end
    751. local isDataLoaded = dataLoaded['Settings'] and dataLoaded['PlayerData']
    752. local hasNecessaryData = settings and player
    753. local hasMinVersion = CSP_VERSION >= CSP_MIN_VERSION
    754. if isDataLoaded and hasMinVersion and hasNecessaryData and isPoliceCar(CAR_ID) then
    755. canRun = true
    756. end
    757. return canRun
    758. end
    759. local settingsOpen = false
    760. local arrestLogsOpen = false
    761. local camerasOpen = false
    762. local imageSize = vec2(0,0)
    763. local pursuit = {
    764. suspect = nil,
    765. enable = false,
    766. maxDistance = 250000,
    767. minDistance = 40000,
    768. nextMessage = 30,
    769. level = 1,
    770. id = -1,
    771. timerArrest = 0,
    772. hasArrested = false,
    773. startedTime = 0,
    774. timeLostSight = 0,
    775. lostSight = false,
    776. engage = false,
    777. }
    778. local arrestations = {}
    779. local playerInRangeUI = {
    780. fontSize = {
    781. div_2 = 20 / 2,
    782. div_2_5 = 20 / 2.5,
    783. div_3 = 20 / 3,
    784. div_1_5 = 20 / 1.5,
    785. div_1_2 = 20 / 1.2,
    786. },
    787. window = {
    788. pos = vec2(0, 0),
    789. size = vec2(0, 0),
    790. },
    791. box = {
    792. pos1 = vec2(0, 0),
    793. pos2 = vec2(0, 0),
    794. offsetY = 0,
    795. },
    796. text = {
    797. pos = vec2(0, 0),
    798. size = vec2(0, 0),
    799. offsetY = 0,
    800. },
    801. header = {
    802. radar = {
    803. pos = vec2(0, 0),
    804. },
    805. nearby = {
    806. pos = vec2(0, 0),
    807. },
    808. },
    809. color = rgbm(1, 1, 1, 0.5),
    810. }
    811. local iconPos = {}
    812. local function onSettingsChange()
    813. settings:save()
    814. ac.log('Settings updated')
    815. end
    816. ---------------------------------------------------------------------------------------------- Firebase ----------------------------------------------------------------------------------------------
    817. local acpPolice = ac.OnlineEvent({
    818. message = ac.StructItem.string(110),
    819. messageType = ac.StructItem.int16(),
    820. yourIndex = ac.StructItem.int16(),
    821. }, function (sender, data)
    822. if data.yourIndex == car.sessionID and data.messageType == 0 and pursuit.suspect ~= nil and sender == pursuit.suspect then
    823. pursuit.hasArrested = true
    824. end
    825. end)
    826. local starsUI = {
    827. starsPos = vec2(0, 0),
    828. starsSize = vec2(0, 0),
    829. startSpace = 0,
    830. full = "https://acstuff.ru/images/icons_24/star_full.png",
    831. empty = "https://acstuff.ru/images/icons_24/star_empty.png",
    832. }
    833. local function updateStarsPos()
    834. starsUI.starsPos = vec2(settings.starsPos.x - settings.starsSize / 2, settings.starsPos.y + settings.starsSize / 2)
    835. starsUI.starsSize = vec2(settings.starsPos.x - settings.starsSize * 2, settings.starsPos.y + settings.starsSize * 2)
    836. starsUI.startSpace = settings.starsSize / 1.5
    837. end
    838. local function updateHudPos()
    839. imageSize = vec2(WINDOW_HEIGHT/80 * settings.policeSize, WINDOW_HEIGHT/80 * settings.policeSize)
    840. iconPos.arrest1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/3.2)
    841. iconPos.arrest2 = vec2(imageSize.x/1.215, imageSize.y/5)
    842. iconPos.lost1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/2.35)
    843. iconPos.lost2 = vec2(imageSize.x/1.215, imageSize.y/3.2)
    844. iconPos.logs1 = vec2(imageSize.x/1.215, imageSize.y/1.88)
    845. iconPos.logs2 = vec2(imageSize.x/1.39, imageSize.y/2.35)
    846. iconPos.menu1 = vec2(imageSize.x - imageSize.x/12, imageSize.y/1.88)
    847. iconPos.menu2 = vec2(imageSize.x/1.215, imageSize.y/2.35)
    848. iconPos.cams1 = vec2(imageSize.x/1.215, imageSize.y/2.35)
    849. iconPos.cams2 = vec2(imageSize.x/1.39, imageSize.y/3.2)
    850. settings.fontSize = settings.policeSize * FONT_MULT
    851. playerInRangeUI.fontSize.div_2 = settings.fontSize / 2
    852. playerInRangeUI.fontSize.div_2_5 = settings.fontSize / 2.5
    853. playerInRangeUI.fontSize.div_3 = settings.fontSize / 3
    854. playerInRangeUI.fontSize.div_1_5 = settings.fontSize / 1.5
    855. playerInRangeUI.fontSize.div_1_2 = settings.fontSize / 1.2
    856. playerInRangeUI.window.pos = vec2(settings.hudOffset.x + imageSize.x / 9.5, settings.hudOffset.y + imageSize.y / 5.3)
    857. playerInRangeUI.window.size = vec2(imageSize.x * 3 / 5, imageSize.y / 2.8)
    858. playerInRangeUI.box.pos1 = vec2(playerInRangeUI.window.size.x / 20, playerInRangeUI.window.size.y / 20)
    859. playerInRangeUI.box.pos2 = vec2(playerInRangeUI.window.size.x - playerInRangeUI.window.size.x / 20, playerInRangeUI.window.size.y / 20)
    860. playerInRangeUI.box.offsetY = playerInRangeUI.window.size.y / 10 + playerInRangeUI.box.pos2.y
    861. ui.pushDWriteFont("Orbitron;Weight=Bold")
    862. playerInRangeUI.header.radar.pos = vec2((playerInRangeUI.window.size.x - ui.measureDWriteText("RADAR ACTIVE", playerInRangeUI.fontSize.div_1_5).x) / 2, 0)
    863. ui.popDWriteFont()
    864. ui.pushDWriteFont("Orbitron;Weight=Regular")
    865. playerInRangeUI.header.nearby.pos = vec2((playerInRangeUI.window.size.x - ui.measureDWriteText("NEARBY VEHICULE SPEED SCANNING", playerInRangeUI.fontSize.div_2_5).x) / 2, playerInRangeUI.fontSize.div_1_2)
    866. ui.popDWriteFont()
    867. playerInRangeUI.text.pos = vec2(playerInRangeUI.window.size.x / 20, 0)
    868. playerInRangeUI.text.size = vec2(playerInRangeUI.window.size.x - playerInRangeUI.window.size.x / 10, playerInRangeUI.window.size.y / 5)
    869. ui.pushDWriteFont("Orbitron;Weight=Regular")
    870. playerInRangeUI.text.offsetY = (playerInRangeUI.window.size.y / 20 - ui.measureDWriteText("Player In Range", settings.fontSize / 20).y) / 2
    871. ui.popDWriteFont()
    872. end
    873. local function showStarsPursuit()
    874. local starsColor = rgbm(1, 1, 1, os.clock()%2 + 0.3)
    875. updateStarsPos()
    876. for i = 1, 5 do
    877. if i > pursuit.level/2 then
    878. ui.drawIcon(ui.Icons.StarEmpty, starsUI.starsPos, starsUI.starsSize, rgbm(1, 1, 1, 0.2))
    879. else
    880. ui.drawIcon(ui.Icons.StarFull, starsUI.starsPos, starsUI.starsSize, starsColor)
    881. end
    882. starsUI.starsPos.x = starsUI.starsPos.x - settings.starsSize - starsUI.startSpace
    883. starsUI.starsSize.x = starsUI.starsSize.x - settings.starsSize - starsUI.startSpace
    884. end
    885. end
    886. local showPreviewMsg = false
    887. local showPreviewStars = false
    888. COLORSMSGBG = rgbm(0.5,0.5,0.5,0.5)
    889. local function initsettings()
    890. imageSize = vec2(WINDOW_HEIGHT/80 * settings.policeSize, WINDOW_HEIGHT/80 * settings.policeSize)
    891. updateHudPos()
    892. updateStarsPos()
    893. end
    894. local function previewMSG()
    895. ui.beginTransparentWindow("previewMSG", vec2(0, 0), vec2(WINDOW_WIDTH, WINDOW_HEIGHT))
    896. ui.pushDWriteFont("Orbitron;Weight=800")
    897. local tSize = ui.measureDWriteText("Messages from Police when being chased", settings.fontSizeMSG)
    898. local uiOffsetX = settings.msgOffset.x - tSize.x/2
    899. local uiOffsetY = settings.msgOffset.y
    900. ui.drawRectFilled(vec2(uiOffsetX - 5, uiOffsetY-5), vec2(uiOffsetX + tSize.x + 5, uiOffsetY + tSize.y + 5), COLORSMSGBG)
    901. ui.dwriteDrawText("Messages from Police when being chased", settings.fontSizeMSG, vec2(uiOffsetX, uiOffsetY), rgbm.colors.cyan)
    902. ui.popDWriteFont()
    903. ui.endTransparentWindow()
    904. end
    905. local function previewStars()
    906. ui.beginTransparentWindow("previewStars", vec2(0, 0), vec2(WINDOW_WIDTH, WINDOW_HEIGHT))
    907. showStarsPursuit()
    908. ui.endTransparentWindow()
    909. end
    910. local function uiTab()
    911. ui.text('On Screen Message : ')
    912. settings.timeMsg = ui.slider('##' .. 'Time Msg On Screen', settings.timeMsg, 1, 15, 'Time Msg On Screen' .. ': %.0fs')
    913. settings.fontSizeMSG = ui.slider('##' .. 'Font Size MSG', settings.fontSizeMSG, 10, 50, 'Font Size' .. ': %.0f')
    914. ui.text('Stars : ')
    915. settings.starsPos.x = ui.slider('##' .. 'Stars Offset X', settings.starsPos.x, 0, WINDOW_WIDTH, 'Stars Offset X' .. ': %.0f')
    916. settings.starsPos.y = ui.slider('##' .. 'Stars Offset Y', settings.starsPos.y, 0, WINDOW_HEIGHT, 'Stars Offset Y' .. ': %.0f')
    917. settings.starsSize = ui.slider('##' .. 'Stars Size', settings.starsSize, 10, 50, 'Stars Size' .. ': %.0f')
    918. ui.newLine()
    919. ui.text('Offset : ')
    920. settings.msgOffset.y = ui.slider('##' .. 'Msg On Screen Offset Y', settings.msgOffset.y, 0, WINDOW_HEIGHT, 'Msg On Screen Offset Y' .. ': %.0f')
    921. settings.msgOffset.x = ui.slider('##' .. 'Msg On Screen Offset X', settings.msgOffset.x, 0, WINDOW_WIDTH, 'Msg On Screen Offset X' .. ': %.0f')
    922. ui.newLine()
    923. ui.text('Preview : ')
    924. ui.sameLine()
    925. if ui.button('Message') then
    926. showPreviewMsg = not showPreviewMsg
    927. showPreviewStars = false
    928. end
    929. ui.sameLine()
    930. if ui.button('Stars') then
    931. showPreviewStars = not showPreviewStars
    932. showPreviewMsg = false
    933. end
    934. if showPreviewMsg then previewMSG()
    935. elseif showPreviewStars then previewStars() end
    936. if ui.button('Offset X to center') then settings.msgOffset.x = WINDOW_WIDTH/2 end
    937. ui.newLine()
    938. end
    939. local function settingsWindow()
    940. imageSize = vec2(WINDOW_HEIGHT/80 * settings.policeSize, WINDOW_HEIGHT/80 * settings.policeSize)
    941. ui.dwriteTextAligned("settings", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(WINDOW_WIDTH/6.5,60), false, rgbm.colors.white)
    942. ui.drawLine(vec2(0,60), vec2(WINDOW_WIDTH/6.5,60), rgbm.colors.white, 1)
    943. ui.newLine(20)
    944. ui.sameLine(10)
    945. ui.beginGroup()
    946. ui.text('Unit : ')
    947. ui.sameLine(160)
    948. if ui.selectable('mph', settings.unit == 'mph',_, ui.measureText('km/h')) then
    949. settings.unit = 'mph'
    950. settings.unitMult = 0.621371
    951. end
    952. ui.sameLine(200)
    953. if ui.selectable('km/h', settings.unit == 'km/h',_, ui.measureText('km/h')) then
    954. settings.unit = 'km/h'
    955. settings.unitMult = 1
    956. end
    957. ui.sameLine(WINDOW_WIDTH/6.5 - 120)
    958. if ui.button('Close', vec2(100, WINDOW_HEIGHT/50)) then
    959. settingsOpen = false
    960. onSettingsChange()
    961. end
    962. ui.text('HUD : ')
    963. settings.hudOffset.x = ui.slider('##' .. 'HUD Offset X', settings.hudOffset.x, 0, WINDOW_WIDTH, 'HUD Offset X' .. ': %.0f')
    964. settings.hudOffset.y = ui.slider('##' .. 'HUD Offset Y', settings.hudOffset.y, 0, WINDOW_HEIGHT, 'HUD Offset Y' .. ': %.0f')
    965. settings.policeSize = ui.slider('##' .. 'HUD Size', settings.policeSize, 10, 50, 'HUD Size' .. ': %.0f')
    966. settings.fontSize = settings.policeSize * FONT_MULT
    967. ui.setNextItemWidth(300)
    968. ui.newLine()
    969. uiTab()
    970. updateHudPos()
    971. ui.endGroup()
    972. end
    973. ---------------------------------------------------------------------------------------------- Utils ----------------------------------------------------------------------------------------------
    974. local function formatMessage(message)
    975. local msgToSend = message
    976. if pursuit.suspect == nil then
    977. msgToSend = string.gsub(msgToSend,"`CAR`", "No Car")
    978. msgToSend = string.gsub(msgToSend,"`NAME`", "No Name")
    979. msgToSend = string.gsub(msgToSend,"`SPEED`", "No Speed")
    980. return msgToSend
    981. end
    982. msgToSend = string.gsub(msgToSend,"`CAR`", string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", ""))
    983. msgToSend = string.gsub(msgToSend,"`NAME`", "@" .. ac.getDriverName(pursuit.suspect.index))
    984. msgToSend = string.gsub(msgToSend,"`SPEED`", string.format("%d ", ac.getCarSpeedKmh(pursuit.suspect.index) * settings.unitMult) .. settings.unit)
    985. return msgToSend
    986. end
    987. ---------------------------------------------------------------------------------------------- HUD ----------------------------------------------------------------------------------------------
    988. local policeLightsPos = {
    989. vec2(0,0),
    990. vec2(WINDOW_WIDTH/10,WINDOW_HEIGHT),
    991. vec2(WINDOW_WIDTH-WINDOW_WIDTH/10,0),
    992. vec2(WINDOW_WIDTH,WINDOW_HEIGHT)
    993. }
    994. local function showPoliceLights()
    995. local timing = math.floor(os.clock()*2 % 2)
    996. if timing == 0 then
    997. 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))
    998. 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))
    999. else
    1000. 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))
    1001. 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))
    1002. end
    1003. end
    1004. local chaseLVL = {
    1005. message = "",
    1006. messageTimer = 0,
    1007. color = rgbm.colors.white,
    1008. }
    1009. local function resetChase()
    1010. pursuit.enable = false
    1011. pursuit.nextMessage = 30
    1012. pursuit.lostSight = false
    1013. pursuit.timeLostSight = 2
    1014. end
    1015. local function lostSuspect()
    1016. resetChase()
    1017. pursuit.lostSight = false
    1018. pursuit.timeLostSight = 0
    1019. pursuit.level = 1
    1020. ac.sendChatMessage(formatMessage(MSG_LOST[math.random(#MSG_LOST)]))
    1021. pursuit.suspect = nil
    1022. ac.setExtraSwitch(0, false)
    1023. end
    1024. local iconsColorOn = {
    1025. [1] = rgbm.colors.red,
    1026. [2] = rgbm.colors.white,
    1027. [3] = rgbm.colors.white,
    1028. [4] = rgbm.colors.white,
    1029. [5] = rgbm.colors.white,
    1030. [6] = rgbm.colors.white,
    1031. }
    1032. local playersInRange = {}
    1033. local function drawImage()
    1034. iconsColorOn[2] = rgbm.colors.white
    1035. iconsColorOn[3] = rgbm.colors.white
    1036. iconsColorOn[4] = rgbm.colors.white
    1037. iconsColorOn[5] = rgbm.colors.white
    1038. iconsColorOn[6] = rgbm.colors.white
    1039. if ui.rectHovered(iconPos.arrest2, iconPos.arrest1) then
    1040. iconsColorOn[2] = rgbm.colors.red
    1041. if pursuit.suspect and pursuit.suspect.speedKmh < 50 and car.speedKmh < 20 and uiState.isMouseLeftKeyClicked then
    1042. pursuit.hasArrested = true
    1043. end
    1044. elseif ui.rectHovered(iconPos.cams2, iconPos.cams1) then
    1045. iconsColorOn[3] = rgbm.colors.red
    1046. if uiState.isMouseLeftKeyClicked then
    1047. if camerasOpen then camerasOpen = false
    1048. else
    1049. camerasOpen = true
    1050. arrestLogsOpen = false
    1051. if settingsOpen then
    1052. onSettingsChange()
    1053. settingsOpen = false
    1054. end
    1055. end
    1056. end
    1057. elseif ui.rectHovered(iconPos.lost2, iconPos.lost1) then
    1058. iconsColorOn[4] = rgbm.colors.red
    1059. if pursuit.suspect and uiState.isMouseLeftKeyClicked then
    1060. lostSuspect()
    1061. end
    1062. elseif ui.rectHovered(iconPos.logs2, iconPos.logs1) then
    1063. iconsColorOn[5] = rgbm.colors.red
    1064. if uiState.isMouseLeftKeyClicked then
    1065. if arrestLogsOpen then arrestLogsOpen = false
    1066. else
    1067. arrestLogsOpen = true
    1068. camerasOpen = false
    1069. if settingsOpen then
    1070. onSettingsChange()
    1071. settingsOpen = false
    1072. end
    1073. end
    1074. end
    1075. elseif ui.rectHovered(iconPos.menu2, iconPos.menu1) then
    1076. iconsColorOn[6] = rgbm.colors.red
    1077. if uiState.isMouseLeftKeyClicked then
    1078. if settingsOpen then
    1079. onSettingsChange()
    1080. settingsOpen = false
    1081. else
    1082. settingsOpen = true
    1083. arrestLogsOpen = false
    1084. camerasOpen = false
    1085. end
    1086. end
    1087. end
    1088. ui.image(HUD_IMG.base, imageSize, rgbm.colors.white)
    1089. ui.drawImage(HUD_IMG.radar, vec2(0,0), imageSize, iconsColorOn[1])
    1090. ui.drawImage(HUD_IMG.arrest, vec2(0,0), imageSize, iconsColorOn[2])
    1091. ui.drawImage(HUD_IMG.cams, vec2(0,0), imageSize, iconsColorOn[3])
    1092. ui.drawImage(HUD_IMG.lost, vec2(0,0), imageSize, iconsColorOn[4])
    1093. ui.drawImage(HUD_IMG.logs, vec2(0,0), imageSize, iconsColorOn[5])
    1094. ui.drawImage(HUD_IMG.menu, vec2(0,0), imageSize, iconsColorOn[6])
    1095. end
    1096. local function playerSelected(suspect)
    1097. if suspect.speedKmh > 50 then
    1098. pursuit.suspect = suspect
    1099. pursuit.nextMessage = 30
    1100. pursuit.level = 1
    1101. local msgToSend = "Officer " .. DRIVER_NAME .. " is chasing you. Run! "
    1102. pursuit.startedTime = settings.timeMsg
    1103. pursuit.engage = true
    1104. acpPolice{message = msgToSend, messageType = 0, yourIndex = ac.getCar(pursuit.suspect.index).sessionID}
    1105. ac.setExtraSwitch(0, true)
    1106. end
    1107. end
    1108. local function hudInChase()
    1109. ui.pushDWriteFont("Orbitron;Weight=Black")
    1110. ui.sameLine(20)
    1111. ui.beginGroup()
    1112. ui.newLine(1)
    1113. local textPursuit = "LVL : " .. math.floor(pursuit.level/2)
    1114. ui.dwriteTextWrapped(ac.getDriverName(pursuit.suspect.index) .. '\n'
    1115. .. string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", "")
    1116. .. '\n' .. string.format("Speed: %d ", pursuit.suspect.speedKmh * settings.unitMult) .. settings.unit
    1117. .. '\n' .. textPursuit, settings.fontSize/2, rgbm.colors.white)
    1118. ui.dummy(vec2(imageSize.x/5,imageSize.y/20))
    1119. ui.newLine(30)
    1120. ui.sameLine()
    1121. if ui.button('Cancel Chase', vec2(imageSize.x/5, imageSize.y/20)) then
    1122. lostSuspect()
    1123. end
    1124. ui.endGroup()
    1125. ui.popDWriteFont()
    1126. end
    1127. local function drawText()
    1128. ui.pushDWriteFont("Orbitron;Weight=Bold")
    1129. ui.dwriteDrawText("RADAR ACTIVE", playerInRangeUI.fontSize.div_1_5, playerInRangeUI.header.radar.pos, rgbm.colors.red)
    1130. ui.popDWriteFont()
    1131. ui.pushDWriteFont("Orbitron;Weight=Regular")
    1132. ui.dwriteDrawText("NEARBY VEHICULE SPEED SCANNING", playerInRangeUI.fontSize.div_2_5, playerInRangeUI.header.nearby.pos, rgbm.colors.white)
    1133. playerInRangeUI.box.pos1.y = playerInRangeUI.header.nearby.pos.y + playerInRangeUI.fontSize.div_1_2
    1134. playerInRangeUI.box.pos2.y = playerInRangeUI.box.pos1.y + playerInRangeUI.box.offsetY
    1135. for i = 1, #playersInRange do
    1136. playerInRangeUI.color = rgbm(1,1,1,0.7)
    1137. ui.drawRect(playerInRangeUI.box.pos1, playerInRangeUI.box.pos2, rgbm(1,1,1,0.1), 1)
    1138. if ui.rectHovered(playerInRangeUI.box.pos1, playerInRangeUI.box.pos2) then
    1139. playerInRangeUI.color = rgbm(1,0,0,1)
    1140. if ui.mouseClicked(0) then
    1141. playerSelected(playersInRange[i].player)
    1142. end
    1143. end
    1144. playerInRangeUI.text.pos.x = (playerInRangeUI.window.size.x - ui.measureDWriteText(playersInRange[i].text, playerInRangeUI.fontSize.div_2).x) / 2
    1145. playerInRangeUI.text.pos.y = playerInRangeUI.box.pos1.y + playerInRangeUI.text.offsetY
    1146. ui.dwriteDrawText(playersInRange[i].text, settings.fontSize/2, vec2(playerInRangeUI.text.pos), playerInRangeUI.color)
    1147. playerInRangeUI.box.pos1.y = playerInRangeUI.box.pos1.y + playerInRangeUI.box.offsetY
    1148. playerInRangeUI.box.pos2.y = playerInRangeUI.box.pos1.y + playerInRangeUI.box.offsetY
    1149. end
    1150. ui.dummy(playerInRangeUI.box.pos1)
    1151. ui.popDWriteFont()
    1152. end
    1153. local function radarUI()
    1154. ui.toolWindow('radarText', playerInRangeUI.window.pos, playerInRangeUI.window.size, true, true, function ()
    1155. if pursuit.suspect then hudInChase()
    1156. else drawText() end
    1157. end)
    1158. ui.transparentWindow('radar', vec2(settings.hudOffset.x, settings.hudOffset.y), imageSize, true, function ()
    1159. drawImage()
    1160. end)
    1161. end
    1162. local function hidePlayers()
    1163. local hideRange = 500
    1164. for i = ac.getSim().carsCount - 1, 0, -1 do
    1165. local playerCar = ac.getCar(i)
    1166. if playerCar and playerCar.isConnected and ac.getCarBrand(i) ~= "DZST traffic" then
    1167. if not isPoliceCar(ac.getCarID(i)) then
    1168. if playerCar.position.x > car.position.x - hideRange and playerCar.position.z > car.position.z - hideRange and playerCar.position.x < car.position.x + hideRange and playerCar.position.z < car.position.z + hideRange then
    1169. ac.hideCarLabels(i, false)
    1170. else
    1171. ac.hideCarLabels(i, true)
    1172. end
    1173. end
    1174. end
    1175. end
    1176. end
    1177. local RADAR_RANGE = 250
    1178. local function radarUpdate()
    1179. local previousSize = #playersInRange
    1180. local j = 1
    1181. for i, c in ac.iterateCars.serverSlots() do
    1182. if not c.isHidingLabels and c.isConnected and not isPoliceCar(c:id()) then
    1183. if c.position.x > car.position.x - RADAR_RANGE and c.position.z > car.position.z - RADAR_RANGE and c.position.x < car.position.x + RADAR_RANGE and c.position.z < car.position.z + RADAR_RANGE then
    1184. playersInRange[j] = {}
    1185. playersInRange[j].player = c
    1186. playersInRange[j].text = string.sub(ac.getDriverName(c.index), 1, 20) .. string.format(" - %d ", c.speedKmh * settings.unitMult) .. settings.unit
    1187. j = j + 1
    1188. if j == 9 then break end
    1189. end
    1190. end
    1191. end
    1192. for i = j, previousSize do playersInRange[i] = nil end
    1193. end
    1194. ---------------------------------------------------------------------------------------------- Chase ----------------------------------------------------------------------------------------------
    1195. local function inRange()
    1196. local distance_x = pursuit.suspect.position.x - car.position.x
    1197. local distance_z = pursuit.suspect.position.z - car.position.z
    1198. local distanceSquared = distance_x * distance_x + distance_z * distance_z
    1199. if(distanceSquared < pursuit.minDistance) then
    1200. pursuit.enable = true
    1201. pursuit.lostSight = false
    1202. pursuit.timeLostSight = 2
    1203. elseif (distanceSquared < pursuit.maxDistance) then resetChase()
    1204. else
    1205. if not pursuit.lostSight then
    1206. pursuit.lostSight = true
    1207. pursuit.timeLostSight = 2
    1208. else
    1209. pursuit.timeLostSight = pursuit.timeLostSight - ui.deltaTime()
    1210. if pursuit.timeLostSight < 0 then lostSuspect() end
    1211. end
    1212. end
    1213. end
    1214. local function sendChatToSuspect()
    1215. if pursuit.enable then
    1216. if 0 < pursuit.nextMessage then
    1217. pursuit.nextMessage = pursuit.nextMessage - ui.deltaTime()
    1218. elseif pursuit.nextMessage < 0 then
    1219. local nb = tostring(pursuit.level)
    1220. acpPolice{message = nb, messageType = 1, yourIndex = ac.getCar(pursuit.suspect.index).sessionID}
    1221. if pursuit.level < 10 then
    1222. pursuit.level = pursuit.level + 1
    1223. chaseLVL.messageTimer = settings.timeMsg
    1224. chaseLVL.message = "CHASE LEVEL " .. math.floor(pursuit.level/2)
    1225. if pursuit.level > 8 then
    1226. chaseLVL.color = rgbm.colors.red
    1227. elseif pursuit.level > 6 then
    1228. chaseLVL.color = rgbm.colors.orange
    1229. elseif pursuit.level > 4 then
    1230. chaseLVL.color = rgbm.colors.yellow
    1231. else
    1232. chaseLVL.color = rgbm.colors.white
    1233. end
    1234. end
    1235. pursuit.nextMessage = 30
    1236. end
    1237. end
    1238. end
    1239. local function showPursuitMsg()
    1240. local text = ""
    1241. if chaseLVL.messageTimer > 0 then
    1242. chaseLVL.messageTimer = chaseLVL.messageTimer - ui.deltaTime()
    1243. text = chaseLVL.message
    1244. end
    1245. if pursuit.startedTime > 0 then
    1246. if pursuit.suspect then
    1247. text = "You are chasing " .. ac.getDriverName(pursuit.suspect.index) .. " driving a " .. string.gsub(string.gsub(ac.getCarName(pursuit.suspect.index), "%W", " "), " ", "") .. " ! Get him! "
    1248. end
    1249. if pursuit.startedTime > 6 then showPoliceLights() end
    1250. if pursuit.engage and pursuit.startedTime < 8 then
    1251. ac.sendChatMessage(formatMessage(MSG_ENGAGE[math.random(#MSG_ENGAGE)]))
    1252. pursuit.engage = false
    1253. end
    1254. end
    1255. if text ~= "" then
    1256. local textLenght = ui.measureDWriteText(text, settings.fontSizeMSG)
    1257. local rectPos1 = vec2(settings.msgOffset.x - textLenght.x/2, settings.msgOffset.y)
    1258. local rectPos2 = vec2(settings.msgOffset.x + textLenght.x/2, settings.msgOffset.y + settings.fontSizeMSG)
    1259. local rectOffset = vec2(10, 10)
    1260. if ui.time() % 1 < 0.5 then
    1261. ui.drawRectFilled(rectPos1 - vec2(10,0), rectPos2 + rectOffset, COLORSMSGBG, 10)
    1262. else
    1263. ui.drawRectFilled(rectPos1 - vec2(10,0), rectPos2 + rectOffset, rgbm(0,0,0,0.5), 10)
    1264. end
    1265. ui.dwriteDrawText(text, settings.fontSizeMSG, rectPos1, chaseLVL.color)
    1266. end
    1267. end
    1268. local function arrestSuspect()
    1269. if pursuit.hasArrested and pursuit.suspect then
    1270. local msgToSend = formatMessage(MSG_ARREST[math.random(#MSG_ARREST)])
    1271. table.insert(arrestations, msgToSend .. os.date("\nDate of the Arrestation: %c"))
    1272. ac.sendChatMessage(msgToSend .. "\nPlease Get Back Pit, GG!")
    1273. pursuit.id = pursuit.suspect.sessionID
    1274. player.arrests = player.arrests and player.arrests + 1 or 1
    1275. pursuit.startedTime = 0
    1276. pursuit.suspect = nil
    1277. pursuit.timerArrest = 1
    1278. player:save()
    1279. elseif pursuit.hasArrested then
    1280. if pursuit.timerArrest > 0 then
    1281. pursuit.timerArrest = pursuit.timerArrest - ui.deltaTime()
    1282. else
    1283. acpPolice{message = "BUSTED!", messageType = 2, yourIndex = pursuit.id}
    1284. pursuit.timerArrest = 0
    1285. pursuit.suspect = nil
    1286. pursuit.id = -1
    1287. pursuit.hasArrested = false
    1288. pursuit.startedTime = 0
    1289. pursuit.enable = false
    1290. pursuit.level = 1
    1291. pursuit.nextMessage = 20
    1292. pursuit.lostSight = false
    1293. pursuit.timeLostSight = 0
    1294. end
    1295. end
    1296. end
    1297. local function chaseUpdate()
    1298. if pursuit.startedTime > 0 then pursuit.startedTime = pursuit.startedTime - ui.deltaTime()
    1299. else pursuit.startedTime = 0 end
    1300. if pursuit.suspect then
    1301. sendChatToSuspect()
    1302. inRange()
    1303. end
    1304. arrestSuspect()
    1305. end
    1306. ---------------------------------------------------------------------------------------------- Menu ----------------------------------------------------------------------------------------------
    1307. local function arrestLogsUI()
    1308. ui.dwriteTextAligned("Arrestation Logs", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(WINDOW_WIDTH/4,60), false, rgbm.colors.white)
    1309. ui.drawLine(vec2(0,60), vec2(WINDOW_WIDTH/4,60), rgbm.colors.white, 1)
    1310. ui.newLine(15)
    1311. ui.sameLine(10)
    1312. ui.beginGroup()
    1313. local allMsg = ""
    1314. ui.dwriteText("Click on the button next to the message you want to copy.", 15, rgbm.colors.white)
    1315. ui.sameLine(WINDOW_WIDTH/4 - 120)
    1316. if ui.button('Close', vec2(100, WINDOW_HEIGHT/50)) then arrestLogsOpen = false end
    1317. for i = 1, #arrestations do
    1318. if ui.smallButton("#" .. i .. ": ") then
    1319. ui.setClipboardText(arrestations[i])
    1320. end
    1321. ui.sameLine()
    1322. ui.dwriteTextWrapped(arrestations[i], 15, rgbm.colors.white)
    1323. end
    1324. if #arrestations == 0 then
    1325. ui.dwriteText("No arrestation logs yet.", 15, rgbm.colors.white)
    1326. end
    1327. ui.newLine()
    1328. if ui.button("Set all messages to ClipBoard") then
    1329. for i = 1, #arrestations do
    1330. allMsg = allMsg .. arrestations[i] .. "\n\n"
    1331. end
    1332. ui.setClipboardText(allMsg)
    1333. end
    1334. ui.endGroup()
    1335. end
    1336. local buttonPos = WINDOW_WIDTH/65
    1337. local function camerasUI()
    1338. ui.dwriteTextAligned("Surveillance Cameras", 40, ui.Alignment.Center, ui.Alignment.Center, vec2(WINDOW_WIDTH/6.5,60), false, rgbm.colors.white)
    1339. ui.drawLine(vec2(0,60), vec2(WINDOW_WIDTH/6.5,60), rgbm.colors.white, 1)
    1340. ui.newLine(20)
    1341. ui.beginGroup()
    1342. ui.sameLine(buttonPos)
    1343. if ui.button('Close', vec2(WINDOW_WIDTH/6.5 - buttonPos*2,30)) then camerasOpen = false end
    1344. ui.newLine()
    1345. for i = 1, #CAMERAS do
    1346. local h = math.rad(CAMERAS[i].dir + ac.getCompassAngle(vec3(0, 0, 1)))
    1347. ui.newLine()
    1348. ui.sameLine(buttonPos)
    1349. if ui.button(CAMERAS[i].name, vec2(WINDOW_WIDTH/6.5 - buttonPos*2,30)) then
    1350. ac.setCurrentCamera(ac.CameraMode.Free)
    1351. ac.setCameraPosition(CAMERAS[i].pos)
    1352. ac.setCameraDirection(vec3(math.sin(h), 0, math.cos(h)))
    1353. ac.setCameraFOV(CAMERAS[i].fov)
    1354. end
    1355. end
    1356. if ac.getSim().cameraMode == ac.CameraMode.Free then
    1357. ui.newLine()
    1358. ui.newLine()
    1359. ui.sameLine(buttonPos)
    1360. if ui.button('Police car camera', vec2(WINDOW_WIDTH/6.5 - buttonPos*2,30)) then ac.setCurrentCamera(ac.CameraMode.Cockpit) end
    1361. end
    1362. end
    1363. local menuSize = {vec2(WINDOW_WIDTH/4, WINDOW_HEIGHT/3), vec2(WINDOW_WIDTH/6.4, WINDOW_HEIGHT/2.2)}
    1364. local buttonPressed = false
    1365. local function moveMenu()
    1366. if ui.windowHovered() and ui.mouseDown() then buttonPressed = true end
    1367. if ui.mouseReleased() then buttonPressed = false end
    1368. if buttonPressed then settings.menuPos = settings.menuPos + ui.mouseDelta() end
    1369. end
    1370. ---------------------------------------------------------------------------------------------- updates ----------------------------------------------------------------------------------------------
    1371. local initUiSize = false
    1372. function script.drawUI()
    1373. if not shouldRun() then return end
    1374. if not initUiSize then
    1375. initsettings()
    1376. initUiSize = true
    1377. end
    1378. radarUI()
    1379. if pursuit.suspect then showStarsPursuit() end
    1380. showPursuitMsg()
    1381. if settingsOpen then
    1382. ui.toolWindow('settings', settings.menuPos, menuSize[2], true, function ()
    1383. ui.childWindow('childsettings', menuSize[2], true, function () settingsWindow() moveMenu() end)
    1384. end)
    1385. elseif arrestLogsOpen then
    1386. ui.toolWindow('ArrestLogs', settings.menuPos, menuSize[1], true, function ()
    1387. ui.childWindow('childArrestLogs', menuSize[1], true, function () arrestLogsUI() moveMenu() end)
    1388. end)
    1389. elseif camerasOpen then
    1390. ui.toolWindow('Cameras', settings.menuPos, menuSize[2], true, function ()
    1391. ui.childWindow('childCameras', menuSize[2], true, function () camerasUI() moveMenu() end)
    1392. end)
    1393. end
    1394. end
    1395. local function loadSettings()
    1396. Settings.allocate(function(allocatedSetting)
    1397. ac.log("Settings Allocated")
    1398. settings = allocatedSetting
    1399. dataLoaded['Settings'] = true
    1400. end)
    1401. end
    1402. local function loadPlayerData()
    1403. Player.allocate(function(allocatedPlayer)
    1404. if allocatedPlayer then
    1405. player = allocatedPlayer
    1406. player:sortSectors()
    1407. dataLoaded['PlayerData'] = true
    1408. updateSharedPlayerData()
    1409. ac.log(player.arrests)
    1410. end
    1411. end)
    1412. end
    1413. local delay = 1
    1414. function script.update(dt)
    1415. if initialisation then
    1416. initialisation = false
    1417. loadSettings()
    1418. loadPlayerData()
    1419. end
    1420. if not shouldRun() then return end
    1421. if delay > 0 then delay = delay - dt end
    1422. if delay < 0 then
    1423. delay = 0
    1424. updateSharedPlayerData()
    1425. ac.broadcastSharedEvent(SHARED_EVENT_KEY, 'update')
    1426. end
    1427. radarUpdate()
    1428. chaseUpdate()
    1429. end
    1430. ac.onCarJumped(0, function (carIndex)
    1431. if isPoliceCar(CAR_ID) then
    1432. if pursuit.suspect then lostSuspect() end
    1433. end
    1434. end)
    1435. ui.registerOnlineExtra(ui.Icons.Settings, "Settings", nil, settingsWindow, nil, ui.OnlineExtraFlags.Tool, 'ui.WindowFlags.AlwaysAutoResize')
    Advertisement
    Add Comment
    Please, Sign In to add comment
    Public Pastes
    We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
    Not a member of Pastebin yet?
    Sign Up, it unlocks many cool features!

    AltStyle によって変換されたページ (->オリジナル) /