├── LICENSE
├── README.md
├── client
├── alerts.lua
├── eventhandlers.lua
├── main.lua
└── utils.lua
├── fxmanifest.lua
├── html
├── index.css
├── index.html
└── index.js
├── locales
├── cs.json
├── de.json
├── en.json
├── es.json
├── fr.json
├── nl.json
├── pt-br.json
└── tr.json
├── server
└── main.lua
├── shared
└── config.lua
├── sounds
├── dispatch.ogg
├── panicbutton.ogg
└── robberysound.ogg
└── ui
├── .gitignore
├── .prettierrc
├── README.md
├── app.js
├── index.html
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── src
├── App.svelte
├── Tailwind.css
├── components
│ ├── Main.svelte
│ └── Menu.svelte
├── main.ts
├── providers
│ ├── AlwaysListener.svelte
│ ├── BackdropFix.svelte
│ ├── DebugBrowser.svelte
│ └── VisibilityProvider.svelte
├── store
│ └── stores.ts
├── typings
│ └── type.ts
├── utils
│ ├── ReceiveNUI.ts
│ ├── SendNUI.ts
│ ├── debugData.ts
│ ├── misc.ts
│ └── timeAgo.ts
└── vite-env.d.ts
├── style.css
├── svelte.config.js
├── tailwind.config.cjs
├── tsconfig.json
└── vite.config.js
/README.md:
--------------------------------------------------------------------------------
1 | # PS Dispatch
2 |
3 | Integrated with [ps-mdt](https://github.com/Project-Sloth/ps-mdt)
4 |
5 | For all support questions, ask in our [Discord](https://www.discord.gg/projectsloth) support chat.
6 | Do not create issues on GitHub if you need help. Issues are for bug reporting and new features only.
7 |
8 | # Depedency
9 | 1. [qb-core](https://github.com/qbcore-framework/qb-core)
10 | 2. [ox_lib](https://github.com/overextended/ox_lib)
11 | 3. [ps-mdt](https://github.com/Project-Sloth/ps-mdt) - Optional but highly recommended.
12 |
13 | # Installation
14 | * Download ZIP
15 | * Make sure your [qb-core](https://github.com/qbcore-framework/qb-core) is fully updated to the latest version.
16 | * Drag and drop resource into your server files
17 | * Start resource through server.cfg
18 | * Drag and drop sounds folder into interact-sound\client\html\sounds
19 | * Configure your [language](https://github.com/Project-Sloth/ps-dispatch#change-language)
20 | * Restart your server.
21 |
22 | # Preview
23 | ## Short Notifications
24 | Dispatch notifications are sent containing only the alert name, omitting additional details to help over populated servers. For more information, the dispatch menu can be accessed. Can be configured on [Config.ShortCalls](https://github.com/Project-Sloth/ps-dispatch/blob/40ffc466ec7ffa14faaf40a68e8b3a9a92c72db6/shared/config.lua#L3C1-L3C18), false by default.
25 |
26 |
27 |
28 | ## Long Notifications
29 |
30 |
31 |
32 |
33 |
34 | ## Dispatch Menu
35 |
36 |
37 | # Change Language.
38 |
39 | - Place this `setr ox:locale en` inside your `server.cfg`
40 | - Change the `en` to your desired language!
41 |
42 | **Supported Languages:**
43 | | **Alias** | **Language Names** |
44 | |--------------|---------------|
45 | |en |English |
46 | |de |German |
47 | |nl |Dutch |
48 | |cs |Czech |
49 | |pt-br |Brazilian Portuguese |
50 | |es |Spanish |
51 |
52 | # Preset Alert Exports.
53 |
54 | ```lua
55 | - exports['ps-dispatch']:ArtGalleryRobbery()
56 | - exports['ps-dispatch']:CarBoosting(vehicle)
57 | - exports['ps-dispatch']:CarJacking(vehicle)
58 | - exports['ps-dispatch']:CustomAlert()
59 | - exports['ps-dispatch']:DeceasedPerson()
60 | - exports['ps-dispatch']:DrugBoatRobbery()
61 | - exports['ps-dispatch']:DrugSale()
62 | - exports['ps-dispatch']:EmsDown()
63 | - exports['ps-dispatch']:Explosion()
64 | - exports['ps-dispatch']:Fight()
65 | - exports['ps-dispatch']:FleecaBankRobbery(camId)
66 | - exports['ps-dispatch']:HouseRobbery()
67 | - exports['ps-dispatch']:HumaneRobbery()
68 | - exports['ps-dispatch']:Hunting()
69 | - exports['ps-dispatch']:InjuriedPerson()
70 | - exports['ps-dispatch']:OfficerDown()
71 | - exports['ps-dispatch']:OfficerBackup()
72 | - exports['ps-dispatch']:OfficerInDistress()
73 | - exports['ps-dispatch']:PacificBankRobbery(camId)
74 | - exports['ps-dispatch']:PaletoBankRobbery(camId)
75 | - exports['ps-dispatch']:PrisonBreak()
76 | - exports['ps-dispatch']:Shooting()
77 | - exports['ps-dispatch']:SignRobbery()
78 | - exports['ps-dispatch']:SpeedingVehicle(vehicle)
79 | - exports['ps-dispatch']:StoreRobbery(camId)
80 | - exports['ps-dispatch']:SuspiciousActivity()
81 | - exports['ps-dispatch']:TrainRobbery()
82 | - exports['ps-dispatch']:UndergroundRobbery()
83 | - exports['ps-dispatch']:UnionRobbery()
84 | - exports['ps-dispatch']:VangelicoRobbery(camId)
85 | - exports['ps-dispatch']:VanRobbery()
86 | - exports['ps-dispatch']:VehicleShooting(vehicle)
87 | - exports['ps-dispatch']:VehicleTheft(vehicle)
88 | - exports['ps-dispatch']:YachtHeist()
89 | - exports['ps-dispatch']:BobcatSecurityHeist()
90 | ```
91 | # Steps to Create New Alert
92 | Add the following into your `alerts.lua` and change to your liking:
93 | ```
94 | local function TestAlert()
95 | local coords = GetEntityCoords(cache.ped)
96 | local vehicle = GetVehicleData(cache.vehicle)
97 |
98 | local dispatchData = {
99 | message = locale('testalert'), -- add this into your locale
100 | codeName = 'testalert', -- this should be the same as in config.lua
101 | code = '10-35',
102 | icon = 'fas fa-car-burst',
103 | priority = 2,
104 | coords = coords,
105 | street = GetStreetAndZone(coords),
106 | heading = GetPlayerHeading(),
107 | vehicle = vehicle.name,
108 | plate = vehicle.plate,
109 | color = vehicle.color,
110 | class = vehicle.class,
111 | doors = vehicle.doors,
112 | jobs = { 'leo' }
113 | }
114 |
115 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
116 | end
117 | exports('TestAlert', TestAlert)
118 | ```
119 | Add codeName in `config.lua` for the particular robbery to display the blip
120 | ["testalert"] is the codename you passed with the TriggerServerEvent in step 1
121 | ```
122 | ['testalert'] = { -- Need to match the codeName in alerts.lua
123 | radius = 0,
124 | sprite = 119,
125 | color = 1,
126 | scale = 1.5,
127 | length = 2,
128 | sound = 'Lose_1st',
129 | sound2 = 'GTAO_FM_Events_Soundset',
130 | offset = false,
131 | flash = false
132 | },
133 | ```
134 | Information about each parameter is in the `alerts.lua` file.
135 |
136 | # FAQ
137 | * There are no calls showing on dispatch or mdt list.
138 | - Make sure you have a job type specified in your qbcore/shared/jobs.lua like:
139 |
140 | 
141 |
142 | - Make sure that you're using the correct job type as leo and make sure your [qb-core](https://github.com/qbcore-framework/qb-core) is fully updated to the latest version.
143 | - On shared/config.lua make set Config.Debug = true to test calls as police officer.(ONLY to be used as testing, make sure to disable on live production)
144 |
145 | * How to change colors of the calls?
146 | - Priority 1 is red and priority 2 is normal on the config.
147 |
148 | * To increase the time that calls are shown on the screen, do the following:
149 | - Find the "alerts.lua" file in the client folder.
150 | - Open this file with a text editor or a development tool like Visual Studio Code.
151 | - Look for the code "alertTime = nil".
152 | - Replace "nil" with the number of seconds you want the calls to display. For example, setting "alertTime = 25" means calls will be shown for 25 seconds.
153 |
154 | # Credits
155 | * [OK1ez](https://github.com/OK1ez)
156 | * [Candrex](https://github.com/CandrexDev)
157 | * [Lenzh](https://github.com/Lenzh)
158 | * [LeSiiN](https://github.com/LeSiiN)
159 | * Project Sloth Team
160 |
--------------------------------------------------------------------------------
/client/alerts.lua:
--------------------------------------------------------------------------------
1 | local function CustomAlert(data)
2 | local coords = data.coords or vec3(0.0, 0.0, 0.0)
3 | local gender = GetPlayerGender()
4 | if not data.gender then gender = nil end
5 |
6 |
7 | local dispatchData = {
8 | message = data.message or "", -- Title of the alert
9 | codeName = data.dispatchCode or "NONE", -- Unique name for each alert
10 | code = data.code or '10-80', -- Code that is displayed before the title
11 | icon = data.icon or 'fas fa-question', -- Icon that is displaed after the title
12 | priority = data.priority or 2, -- Changes color of the alert ( 1 = red, 2 = default )
13 | coords = coords, -- Coords of the player
14 | gender = gender, -- Gender of the player
15 | street = GetStreetAndZone(coords), -- Street of the player
16 | camId = data.camId or nil, -- Cam ID ( for heists )
17 | color = data.firstColor or nil, -- Color of the vehicle
18 | callsign = data.callsign or nil, -- Callsigns
19 | name = data.name or nil, -- Name of either officer/ems or a player
20 | vehicle = data.model or nil, -- Vehicle name
21 | plate = data.plate or nil, -- Vehicle plate
22 | alertTime = data.alertTime or nil, -- How long it stays on the screen in seconds
23 | doorCount = data.doorCount or nil, -- How many doors on vehicle
24 | automaticGunfire = data.automaticGunfire or false, -- Automatic Gun or not
25 | alert = {
26 | radius = data.radius or 0, -- Radius around the blip
27 | sprite = data.sprite or 1, -- Sprite of the blip
28 | color = data.color or 1, -- Color of the blip
29 | scale = data.scale or 0.5, -- Scale of the blip
30 | length = data.length or 2, -- How long it stays on the map
31 | sound = data.sound or "Lose_1st", -- Alert sound
32 | sound2 = data.sound2 or "GTAO_FM_Events_Soundset", -- Alert sound
33 | offset = data.offset or false, -- Blip / radius offset
34 | flash = data.flash or false -- Blip flash
35 | },
36 | jobs = data.jobs or { 'leo' },
37 | }
38 |
39 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
40 | end
41 | exports('CustomAlert', CustomAlert)
42 |
43 | local function VehicleTheft()
44 | local coords = GetEntityCoords(cache.ped)
45 | local vehicle = GetVehicleData(cache.vehicle)
46 |
47 | local dispatchData = {
48 | message = locale('vehicletheft'),
49 | codeName = 'vehicletheft',
50 | code = '10-35',
51 | icon = 'fas fa-car-burst',
52 | priority = 2,
53 | coords = coords,
54 | street = GetStreetAndZone(coords),
55 | heading = GetPlayerHeading(),
56 | vehicle = vehicle.name,
57 | plate = vehicle.plate,
58 | color = vehicle.color,
59 | class = vehicle.class,
60 | doors = vehicle.doors,
61 | alertTime = nil,
62 | jobs = { 'leo' }
63 | }
64 |
65 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
66 | end
67 | exports('VehicleTheft', VehicleTheft)
68 |
69 | local function Shooting()
70 | local coords = GetEntityCoords(cache.ped)
71 |
72 | local dispatchData = {
73 | message = locale('shooting'),
74 | codeName = 'shooting',
75 | code = '10-11',
76 | icon = 'fas fa-gun',
77 | priority = 2,
78 | coords = coords,
79 | street = GetStreetAndZone(coords),
80 | gender = GetPlayerGender(),
81 | weapon = GetWeaponName(),
82 | alertTime = nil,
83 | jobs = { 'leo' }
84 | }
85 |
86 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
87 | end
88 | exports('Shooting', Shooting)
89 |
90 | local function Hunting()
91 | local coords = GetEntityCoords(cache.ped)
92 |
93 | local dispatchData = {
94 | message = locale('hunting'),
95 | codeName = 'hunting',
96 | code = '10-13',
97 | icon = 'fas fa-gun',
98 | priority = 2,
99 | weapon = GetWeaponName(),
100 | coords = coords,
101 | gender = GetPlayerGender(),
102 | street = GetStreetAndZone(coords),
103 | alertTime = nil,
104 | jobs = { 'leo' }
105 | }
106 |
107 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
108 | end
109 | exports('Hunting', Hunting)
110 |
111 | local function VehicleShooting()
112 | local coords = GetEntityCoords(cache.ped)
113 | local vehicle = GetVehicleData(cache.vehicle)
114 |
115 | local dispatchData = {
116 | message = locale('vehicleshots'),
117 | codeName = 'vehicleshots',
118 | code = '10-60',
119 | icon = 'fas fa-gun',
120 | priority = 2,
121 | coords = coords,
122 | weapon = GetWeaponName(),
123 | street = GetStreetAndZone(coords),
124 | heading = GetPlayerHeading(),
125 | vehicle = vehicle.name,
126 | plate = vehicle.plate,
127 | color = vehicle.color,
128 | class = vehicle.class,
129 | doors = vehicle.doors,
130 | alertTime = nil,
131 | jobs = { 'leo' }
132 | }
133 |
134 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
135 | end
136 | exports('VehicleShooting', VehicleShooting)
137 |
138 | local function SpeedingVehicle()
139 | local coords = GetEntityCoords(cache.ped)
140 | local vehicle = GetVehicleData(cache.vehicle)
141 |
142 | local dispatchData = {
143 | message = locale('speeding'),
144 | codeName = 'speeding',
145 | code = '10-11',
146 | icon = 'fas fa-car',
147 | priority = 2,
148 | coords = coords,
149 | street = GetStreetAndZone(coords),
150 | heading = GetPlayerHeading(),
151 | vehicle = vehicle.name,
152 | plate = vehicle.plate,
153 | color = vehicle.color,
154 | class = vehicle.class,
155 | doors = vehicle.doors,
156 | alertTime = nil,
157 | jobs = { 'leo' }
158 | }
159 |
160 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
161 | end
162 | exports('SpeedingVehicle', SpeedingVehicle)
163 |
164 | local function Fight()
165 | local coords = GetEntityCoords(cache.ped)
166 |
167 | local dispatchData = {
168 | message = locale('melee'),
169 | codeName = 'fight',
170 | code = '10-10',
171 | icon = 'fas fa-hand-fist',
172 | priority = 2,
173 | coords = coords,
174 | gender = GetPlayerGender(),
175 | street = GetStreetAndZone(coords),
176 | alertTime = nil,
177 | jobs = { 'leo' }
178 | }
179 |
180 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
181 | end
182 | exports('Fight', Fight)
183 |
184 | local function PrisonBreak()
185 | local coords = GetEntityCoords(cache.ped)
186 |
187 | local dispatchData = {
188 | message = locale('prisonbreak'),
189 | codeName = 'prisonbreak',
190 | code = '10-90',
191 | icon = 'fas fa-vault',
192 | priority = 2,
193 | coords = coords,
194 | gender = GetPlayerGender(),
195 | street = GetStreetAndZone(coords),
196 | alertTime = nil,
197 | jobs = { 'leo' }
198 | }
199 |
200 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
201 | end
202 | exports('PrisonBreak', PrisonBreak)
203 |
204 | local function StoreRobbery(camId)
205 | local coords = GetEntityCoords(cache.ped)
206 |
207 | local dispatchData = {
208 | message = locale('storerobbery'),
209 | codeName = 'storerobbery',
210 | code = '10-90',
211 | icon = 'fas fa-store',
212 | priority = 2,
213 | coords = coords,
214 | gender = GetPlayerGender(),
215 | street = GetStreetAndZone(coords),
216 | camId = camId,
217 | alertTime = nil,
218 | jobs = { 'leo' }
219 | }
220 |
221 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
222 | end
223 | exports('StoreRobbery', StoreRobbery)
224 |
225 | local function FleecaBankRobbery(camId)
226 | local coords = GetEntityCoords(cache.ped)
227 |
228 | local dispatchData = {
229 | message = locale('fleecabank'),
230 | codeName = 'bankrobbery',
231 | code = '10-90',
232 | icon = 'fas fa-vault',
233 | priority = 2,
234 | coords = coords,
235 | gender = GetPlayerGender(),
236 | street = GetStreetAndZone(coords),
237 | camId = camId,
238 | alertTime = nil,
239 | jobs = { 'leo' }
240 | }
241 |
242 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
243 | end
244 | exports('FleecaBankRobbery', FleecaBankRobbery)
245 |
246 | local function PaletoBankRobbery(camId)
247 | local coords = GetEntityCoords(cache.ped)
248 |
249 | local dispatchData = {
250 | message = locale('paletobank'),
251 | codeName = 'paletobankrobbery',
252 | code = '10-90',
253 | icon = 'fas fa-vault',
254 | priority = 2,
255 | coords = coords,
256 | gender = GetPlayerGender(),
257 | street = GetStreetAndZone(coords),
258 | camId = camId,
259 | alertTime = nil,
260 | jobs = { 'leo' }
261 | }
262 |
263 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
264 | end
265 | exports('PaletoBankRobbery', PaletoBankRobbery)
266 |
267 | local function PacificBankRobbery(camId)
268 | local coords = GetEntityCoords(cache.ped)
269 |
270 | local dispatchData = {
271 | message = locale('pacificbank'),
272 | codeName = 'pacificbankrobbery',
273 | code = '10-90',
274 | icon = 'fas fa-vault',
275 | priority = 2,
276 | coords = coords,
277 | gender = GetPlayerGender(),
278 | street = GetStreetAndZone(coords),
279 | camId = camId,
280 | alertTime = nil,
281 | jobs = { 'leo' }
282 | }
283 |
284 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
285 | end
286 | exports('PacificBankRobbery', PacificBankRobbery)
287 |
288 | local function VangelicoRobbery(camId)
289 | local coords = GetEntityCoords(cache.ped)
290 |
291 | local dispatchData = {
292 | message = locale('vangelico'),
293 | codeName = 'vangelicorobbery',
294 | code = '10-90',
295 | icon = 'fas fa-gem',
296 | priority = 2,
297 | coords = coords,
298 | gender = GetPlayerGender(),
299 | street = GetStreetAndZone(coords),
300 | camId = camId,
301 | alertTime = nil,
302 | jobs = { 'leo' }
303 | }
304 |
305 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
306 | end
307 | exports('VangelicoRobbery', VangelicoRobbery)
308 |
309 | local function HouseRobbery()
310 | local coords = GetEntityCoords(cache.ped)
311 |
312 | local dispatchData = {
313 | message = locale('houserobbery'),
314 | codeName = 'houserobbery',
315 | code = '10-90',
316 | icon = 'fas fa-house',
317 | priority = 2,
318 | coords = coords,
319 | gender = GetPlayerGender(),
320 | street = GetStreetAndZone(coords),
321 | alertTime = nil,
322 | jobs = { 'leo' }
323 | }
324 |
325 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
326 | end
327 | exports('HouseRobbery', HouseRobbery)
328 |
329 | local function YachtHeist()
330 | local coords = GetEntityCoords(cache.ped)
331 |
332 | local dispatchData = {
333 | message = locale('yachtheist'),
334 | codeName = 'yachtheist',
335 | code = '10-65',
336 | icon = 'fas fa-house',
337 | priority = 2,
338 | coords = coords,
339 | gender = GetPlayerGender(),
340 | street = GetStreetAndZone(coords),
341 | alertTime = nil,
342 | jobs = { 'leo' }
343 | }
344 |
345 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
346 | end
347 | exports('YachtHeist', YachtHeist)
348 |
349 | local function DrugSale()
350 | local coords = GetEntityCoords(cache.ped)
351 |
352 | local dispatchData = {
353 | message = locale('drugsell'),
354 | codeName = 'suspicioushandoff',
355 | code = '10-13',
356 | icon = 'fas fa-tablets',
357 | priority = 2,
358 | coords = coords,
359 | gender = GetPlayerGender(),
360 | street = GetStreetAndZone(coords),
361 | alertTime = nil,
362 | jobs = { 'leo' }
363 | }
364 |
365 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
366 | end
367 | exports('DrugSale', DrugSale)
368 |
369 | local function SuspiciousActivity()
370 | local coords = GetEntityCoords(cache.ped)
371 |
372 | local dispatchData = {
373 | message = locale('susactivity'),
374 | codeName = 'susactivity',
375 | code = '10-66',
376 | icon = 'fas fa-tablets',
377 | priority = 2,
378 | coords = coords,
379 | gender = GetPlayerGender(),
380 | street = GetStreetAndZone(coords),
381 | alertTime = nil,
382 | jobs = { 'leo' }
383 | }
384 |
385 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
386 | end
387 | exports('SuspiciousActivity', SuspiciousActivity)
388 |
389 | local function CarJacking(vehicle)
390 | local coords = GetEntityCoords(cache.ped)
391 | local vehicle = GetVehicleData(vehicle)
392 |
393 | local dispatchData = {
394 | message = locale('carjacking'),
395 | codeName = 'carjack',
396 | code = '10-35',
397 | icon = 'fas fa-car',
398 | priority = 2,
399 | coords = coords,
400 | street = GetStreetAndZone(coords),
401 | heading = GetPlayerHeading(),
402 | vehicle = vehicle.name,
403 | plate = vehicle.plate,
404 | color = vehicle.color,
405 | class = vehicle.class,
406 | doors = vehicle.doors,
407 | alertTime = nil,
408 | jobs = { 'leo' }
409 | }
410 |
411 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
412 | end
413 | exports('CarJacking', CarJacking)
414 |
415 | local function InjuriedPerson()
416 | local coords = GetEntityCoords(cache.ped)
417 |
418 | local dispatchData = {
419 | message = locale('persondown'),
420 | codeName = 'civdown',
421 | code = '10-69',
422 | icon = 'fas fa-face-dizzy',
423 | priority = 1,
424 | coords = coords,
425 | gender = GetPlayerGender(),
426 | street = GetStreetAndZone(coords),
427 | alertTime = 10,
428 | jobs = { 'ems' }
429 | }
430 |
431 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
432 | end
433 | exports('InjuriedPerson', InjuriedPerson)
434 |
435 | local function DeceasedPerson()
436 | local coords = GetEntityCoords(cache.ped)
437 |
438 | local dispatchData = {
439 | message = locale('civbled'),
440 | codeName = 'civdead',
441 | code = '10-69',
442 | icon = 'fas fa-skull',
443 | priority = 1,
444 | coords = coords,
445 | gender = GetPlayerGender(),
446 | street = GetStreetAndZone(coords),
447 | alertTime = 10,
448 | jobs = { 'ems' }
449 | }
450 |
451 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
452 | end
453 | exports('DeceasedPerson', DeceasedPerson)
454 |
455 | local function OfficerDown()
456 | local coords = GetEntityCoords(cache.ped)
457 |
458 | local dispatchData = {
459 | message = locale('officerdown'),
460 | codeName = 'officerdown',
461 | code = '10-99',
462 | icon = 'fas fa-skull',
463 | priority = 1,
464 | coords = coords,
465 | gender = GetPlayerGender(),
466 | street = GetStreetAndZone(coords),
467 | name = PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname,
468 | callsign = PlayerData.metadata["callsign"],
469 | alertTime = 10,
470 | jobs = { 'ems', 'leo' }
471 | }
472 |
473 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
474 | end
475 | exports('OfficerDown', OfficerDown)
476 |
477 | RegisterNetEvent("ps-dispatch:client:officerdown", function() OfficerDown() end)
478 |
479 | local function OfficerBackup()
480 | local coords = GetEntityCoords(cache.ped)
481 |
482 | local dispatchData = {
483 | message = locale('officerbackup'),
484 | codeName = 'officerbackup',
485 | code = '10-32',
486 | icon = 'fas fa-skull',
487 | priority = 1,
488 | coords = coords,
489 | gender = GetPlayerGender(),
490 | street = GetStreetAndZone(coords),
491 | name = PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname,
492 | callsign = PlayerData.metadata["callsign"],
493 | alertTime = 10,
494 | jobs = { 'ems', 'leo' }
495 | }
496 |
497 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
498 | end
499 | exports('OfficerBackup', OfficerBackup)
500 |
501 | RegisterNetEvent("ps-dispatch:client:officerbackup", function() OfficerBackup() end)
502 |
503 | local function OfficerInDistress()
504 | local coords = GetEntityCoords(cache.ped)
505 |
506 | local dispatchData = {
507 | message = locale('officerdistress'),
508 | codeName = 'officerdistress',
509 | code = '10-99',
510 | icon = 'fas fa-skull',
511 | priority = 1,
512 | coords = coords,
513 | gender = GetPlayerGender(),
514 | street = GetStreetAndZone(coords),
515 | name = PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname,
516 | callsign = PlayerData.metadata["callsign"],
517 | alertTime = 10,
518 | jobs = { 'ems', 'leo' }
519 | }
520 |
521 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
522 | end
523 | exports('OfficerInDistress', OfficerInDistress)
524 |
525 | local function EmsDown()
526 | local coords = GetEntityCoords(cache.ped)
527 |
528 | local dispatchData = {
529 | message = locale('emsdown'),
530 | codeName = 'emsdown',
531 | code = '10-99',
532 | icon = 'fas fa-skull',
533 | priority = 1,
534 | coords = coords,
535 | gender = GetPlayerGender(),
536 | street = GetStreetAndZone(coords),
537 | name = PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname,
538 | callsign = PlayerData.metadata["callsign"],
539 | alertTime = 10,
540 | jobs = { 'ems', 'leo' }
541 | }
542 |
543 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
544 | end
545 | exports('EmsDown', EmsDown)
546 |
547 | RegisterNetEvent("ps-dispatch:client:emsdown", function() EmsDown() end)
548 |
549 | local function Explosion()
550 | local coords = GetEntityCoords(cache.ped)
551 |
552 | local dispatchData = {
553 | message = locale('explosion'),
554 | codeName = 'explosion',
555 | code = '10-80',
556 | icon = 'fas fa-fire',
557 | priority = 2,
558 | coords = coords,
559 | gender = GetPlayerGender(),
560 | street = GetStreetAndZone(coords),
561 | alertTime = nil,
562 | jobs = { 'leo' }
563 | }
564 |
565 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
566 | end
567 | exports('Explosion', Explosion)
568 |
569 | local function PhoneCall(message, anonymous, job, type)
570 | local coords = GetEntityCoords(cache.ped)
571 |
572 | if IsCallAllowed(message) then
573 | PhoneAnimation()
574 |
575 | local dispatchData = {
576 | message = anonymous and locale('anon_call') or locale('call'),
577 | codeName = type == '311' and '311call' or '911call',
578 | code = type,
579 | icon = 'fas fa-phone',
580 | priority = 2,
581 | coords = coords,
582 | name = anonymous and locale('anon') or (PlayerData.charinfo.firstname .. " " .. PlayerData.charinfo.lastname),
583 | number = anonymous and locale('hidden_number') or PlayerData.charinfo.phone,
584 | information = message,
585 | street = GetStreetAndZone(coords),
586 | alertTime = nil,
587 | jobs = job
588 | }
589 |
590 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
591 | end
592 | end
593 |
594 | --- @param data string -- Message
595 | --- @param type string -- What type of emergency
596 | --- @param anonymous boolean -- Is the call anonymous
597 | local pslastaction = 0
598 | RegisterNetEvent('ps-dispatch:client:sendEmergencyMsg', function(data, type, anonymous)
599 | local year, month , day , hour, minute, second = GetUtcTime()
600 | local idtrack = tonumber(hour..minute..second)
601 | local spamdetek = idtrack - pslastaction
602 | if spamdetek < 0 then spamdetek = Config.AlertCommandCooldown end
603 | if spamdetek <= Config.AlertCommandCooldown and pslastaction > 0 then
604 | pslastaction = idtrack
605 | QBCore.Functions.Notify("Command on cooldown", "error")
606 | else
607 | pslastaction = idtrack
608 | local jobs = { ['911'] = { 'leo' }, ['311'] = { 'ems' } }
609 | PhoneCall(data, anonymous, jobs[type], type)
610 | end
611 | end)
612 |
613 |
614 | local function ArtGalleryRobbery()
615 | local coords = GetEntityCoords(cache.ped)
616 |
617 | local dispatchData = {
618 | message = locale('artgalleryrobbery'),
619 | codeName = 'artgalleryrobbery',
620 | code = '10-90',
621 | icon = 'fas fa-brush',
622 | priority = 2,
623 | coords = coords,
624 | gender = GetPlayerGender(),
625 | street = GetStreetAndZone(coords),
626 | alertTime = nil,
627 | jobs = { 'leo' }
628 | }
629 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
630 | end
631 | exports('ArtGalleryRobbery', ArtGalleryRobbery)
632 |
633 | local function HumaneRobbery()
634 | local coords = GetEntityCoords(cache.ped)
635 |
636 | local dispatchData = {
637 | message = locale('humanelabsrobbery'),
638 | codeName = 'humanelabsrobbery',
639 | code = '10-90',
640 | icon = 'fas fa-flask-vial',
641 | priority = 2,
642 | coords = coords,
643 | gender = GetPlayerGender(),
644 | street = GetStreetAndZone(coords),
645 | alertTime = nil,
646 | jobs = { 'leo' }
647 | }
648 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
649 |
650 | end
651 | exports('HumaneRobbery', HumaneRobbery)
652 |
653 | local function TrainRobbery()
654 | local coords = GetEntityCoords(cache.ped)
655 |
656 | local dispatchData = {
657 | message = locale('trainrobbery'),
658 | codeName = 'trainrobbery',
659 | code = '10-90',
660 | icon = 'fas fa-train',
661 | priority = 2,
662 | coords = coords,
663 | gender = GetPlayerGender(),
664 | street = GetStreetAndZone(coords),
665 | alertTime = nil,
666 | jobs = { 'leo' }
667 | }
668 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
669 |
670 | end
671 | exports('TrainRobbery', TrainRobbery)
672 |
673 | local function VanRobbery()
674 | local coords = GetEntityCoords(cache.ped)
675 |
676 | local dispatchData = {
677 | message = locale('vanrobbery'),
678 | codeName = 'vanrobbery',
679 | code = '10-90',
680 | icon = 'fas fa-van-shuttle',
681 | priority = 2,
682 | coords = coords,
683 | gender = GetPlayerGender(),
684 | street = GetStreetAndZone(coords),
685 | alertTime = nil,
686 | jobs = { 'leo' }
687 | }
688 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
689 |
690 | end
691 | exports('VanRobbery', VanRobbery)
692 |
693 | local function UndergroundRobbery()
694 | local coords = GetEntityCoords(cache.ped)
695 |
696 | local dispatchData = {
697 | message = locale('undergroundrobbery'),
698 | codeName = 'undergroundrobbery',
699 | code = '10-90',
700 | icon = 'fas fa-person-rays',
701 | priority = 2,
702 | coords = coords,
703 | gender = GetPlayerGender(),
704 | street = GetStreetAndZone(coords),
705 | alertTime = nil,
706 | jobs = { 'leo' }
707 | }
708 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
709 | end
710 | exports('UndergroundRobbery', UndergroundRobbery)
711 |
712 | local function DrugBoatRobbery()
713 | local coords = GetEntityCoords(cache.ped)
714 |
715 | local dispatchData = {
716 | message = locale('drugboatrobbery'),
717 | codeName = 'drugboatrobbery',
718 | code = '10-65',
719 | icon = 'fas fa-ship',
720 | priority = 2,
721 | coords = coords,
722 | gender = GetPlayerGender(),
723 | street = GetStreetAndZone(coords),
724 | alertTime = nil,
725 | jobs = { 'leo' }
726 | }
727 |
728 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
729 | end
730 | exports('DrugBoatRobbery', DrugBoatRobbery)
731 |
732 | local function UnionRobbery()
733 | local coords = GetEntityCoords(cache.ped)
734 |
735 | local dispatchData = {
736 | message = locale('unionrobbery'),
737 | codeName = 'unionrobbery',
738 | code = '10-90',
739 | icon = 'fas fa-truck-field',
740 | priority = 2,
741 | coords = coords,
742 | gender = GetPlayerGender(),
743 | street = GetStreetAndZone(coords),
744 | alertTime = nil,
745 | jobs = { 'leo' }
746 | }
747 |
748 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
749 | end
750 | exports('UnionRobbery', UnionRobbery)
751 |
752 | local function CarBoosting(vehicle)
753 | local coords = GetEntityCoords(cache.ped)
754 | local vehicle = GetVehicleData(vehicle or cache.vehicle)
755 |
756 | local dispatchData = {
757 | message = locale('carboosting'),
758 | codeName = 'carboosting',
759 | code = '10-50',
760 | icon = 'fas fa-car',
761 | priority = 2,
762 | coords = coords,
763 | street = GetStreetAndZone(coords),
764 | heading = GetPlayerHeading(),
765 | vehicle = vehicle.name,
766 | plate = vehicle.plate,
767 | color = vehicle.color,
768 | class = vehicle.class,
769 | doors = vehicle.doors,
770 | alertTime = nil,
771 | jobs = { 'leo' }
772 | }
773 |
774 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
775 | end
776 | exports('CarBoosting', CarBoosting)
777 |
778 | local function SignRobbery()
779 | local coords = GetEntityCoords(cache.ped)
780 |
781 | local dispatchData = {
782 | message = locale('signrobbery'),
783 | codeName = 'signrobbery',
784 | code = '10-10',
785 | icon = 'fab fa-artstation',
786 | priority = 2,
787 | coords = coords,
788 | gender = GetPlayerGender(),
789 | street = GetStreetAndZone(coords),
790 | alertTime = nil,
791 | jobs = { 'leo'}
792 | }
793 |
794 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
795 | end
796 | exports('SignRobbery', SignRobbery)
797 |
798 | local function BobcatSecurityHeist()
799 | local coords = GetEntityCoords(cache.ped)
800 |
801 | local dispatchData = {
802 | message = locale('bobcatsecurity'),
803 | codeName = 'bobcatsecurityheist',
804 | code = '10-90',
805 | icon = 'fa-solid fa-building-shield',
806 | priority = 2,
807 | coords = coords,
808 | gender = GetPlayerGender(),
809 | street = GetStreetAndZone(coords),
810 | alertTime = nil,
811 | jobs = { 'leo'}
812 | }
813 |
814 | TriggerServerEvent('ps-dispatch:server:notify', dispatchData)
815 | end
816 | exports('BobcatSecurityHeist', BobcatSecurityHeist)
--------------------------------------------------------------------------------
/client/eventhandlers.lua:
--------------------------------------------------------------------------------
1 | local timer = {}
2 |
3 | ---@param name string -- The name of the timer
4 | ---@param action function -- The function to execute when the timer is up
5 | ---@vararg any -- Arguments to pass to the action function
6 | local function WaitTimer(name, action, ...)
7 | if not Config.DefaultAlerts[name] then return end
8 |
9 | if not timer[name] then
10 | timer[name] = true
11 | action(...)
12 | Wait(Config.DefaultAlertsDelay * 1000)
13 | timer[name] = false
14 | end
15 | end
16 |
17 | ---@param witnesses table | Array of peds that witnessed the event
18 | ---@param ped number | Ped ID to check
19 | ---@return boolean | Returns true if the ped is in the witnesses table
20 | local function isPedAWitness(witnesses, ped)
21 | for k, v in pairs(witnesses) do
22 | if v == ped then
23 | return true
24 | end
25 | end
26 | return false
27 | end
28 |
29 | ---@param ped number | Ped ID to check
30 | ---@return boolean | Returns true if the ped is holding a whitelisted gun
31 | local function BlacklistedWeapon(ped)
32 | for i = 1, #Config.WeaponWhitelist do
33 | local weaponHash = joaat(Config.WeaponWhitelist[i])
34 | if GetSelectedPedWeapon(ped) == weaponHash then
35 | return true -- Is a whitelisted weapon
36 | end
37 | end
38 | return false -- Is not a whitelisted weapon
39 | end
40 |
41 | AddEventHandler('CEventGunShot', function(witnesses, ped)
42 | if IsPedCurrentWeaponSilenced(cache.ped) then return end
43 | if inNoDispatchZone then return end
44 | if BlacklistedWeapon(cache.ped) then return end
45 |
46 | WaitTimer('Shooting', function()
47 | if cache.ped ~= ped then return end
48 |
49 | if PlayerData.job.type == 'leo' then
50 | if not Config.Debug then
51 | return
52 | end
53 | end
54 |
55 | if inHuntingZone then
56 | exports['ps-dispatch']:Hunting()
57 | return
58 | end
59 |
60 | if witnesses and not isPedAWitness(witnesses, ped) then return end
61 |
62 | if cache.vehicle then
63 | exports['ps-dispatch']:VehicleShooting()
64 | else
65 | exports['ps-dispatch']:Shooting()
66 | end
67 | end)
68 | end)
69 |
70 | AddEventHandler('CEventShockingSeenMeleeAction', function(witnesses, ped)
71 | WaitTimer('Melee', function()
72 | if cache.ped ~= ped then return end
73 | if witnesses and not isPedAWitness(witnesses, ped) then return end
74 | if not IsPedInMeleeCombat(ped) then return end
75 |
76 | exports['ps-dispatch']:Fight()
77 | end)
78 | end)
79 |
80 | AddEventHandler('CEventPedJackingMyVehicle', function(_, ped)
81 | WaitTimer('Autotheft', function()
82 | if cache.ped ~= ped then return end
83 | local vehicle = GetVehiclePedIsUsing(ped, true)
84 | exports['ps-dispatch']:CarJacking(vehicle)
85 | end)
86 | end)
87 |
88 | AddEventHandler('CEventShockingCarAlarm', function(_, ped)
89 | WaitTimer('Autotheft', function()
90 | if cache.ped ~= ped then return end
91 | local vehicle = GetVehiclePedIsUsing(ped, true)
92 | exports['ps-dispatch']:VehicleTheft(vehicle)
93 | end)
94 | end)
95 |
96 | AddEventHandler('CEventExplosionHeard', function(witnesses, ped)
97 | if witnesses and not isPedAWitness(witnesses, ped) then return end
98 | WaitTimer('Explosion', function()
99 | exports['ps-dispatch']:Explosion()
100 | end)
101 | end)
102 |
103 | AddEventHandler('gameEventTriggered', function(name, args)
104 | if name ~= 'CEventNetworkEntityDamage' then return end
105 | local victim = args[1]
106 | local isDead = args[6] == 1
107 | WaitTimer('PlayerDowned', function()
108 | if not victim or victim ~= cache.ped then return end
109 | if not isDead then return end
110 |
111 | if PlayerData.job.type == 'leo' then
112 | exports['ps-dispatch']:OfficerDown()
113 | elseif PlayerData.job.type == 'ems' then
114 | exports['ps-dispatch']:EmsDown()
115 | else
116 | exports['ps-dispatch']:InjuriedPerson()
117 | end
118 | end)
119 | end)
120 |
121 | local SpeedingEvents = {
122 | 'CEventShockingCarChase',
123 | 'CEventShockingDrivingOnPavement',
124 | 'CEventShockingBicycleOnPavement',
125 | 'CEventShockingMadDriverBicycle',
126 | 'CEventShockingMadDriverExtreme',
127 | 'CEventShockingEngineRevved',
128 | 'CEventShockingInDangerousVehicle'
129 | }
130 |
131 | local exemptVehicleClass = {
132 | [15] = true, -- Helicopters
133 | [16] = true, -- Planes
134 | }
135 |
136 | local SpeedTrigger = 0
137 | for i = 1, #SpeedingEvents do
138 | local event = SpeedingEvents[i]
139 | AddEventHandler(event, function(_, ped)
140 | WaitTimer('Speeding', function()
141 | local currentTime = GetGameTimer()
142 | if currentTime - SpeedTrigger < 10000 then
143 | return
144 | end
145 | if cache.ped ~= ped then return end
146 |
147 | if PlayerData.job.type == 'leo' then
148 | if not Config.Debug then
149 | return
150 | end
151 | end
152 |
153 | local vehicleClass = GetVehicleClass(cache.vehicle)
154 | if exemptVehicleClass[vehicleClass] then return end
155 |
156 | if GetEntitySpeed(cache.vehicle) * 3.6 < (80 + math.random(0, 20)) then return end
157 |
158 | if cache.ped ~= GetPedInVehicleSeat(cache.vehicle, -1) then return end
159 |
160 | exports['ps-dispatch']:SpeedingVehicle()
161 | SpeedTrigger = GetGameTimer()
162 | end)
163 | end)
164 | end
165 |
--------------------------------------------------------------------------------
/client/main.lua:
--------------------------------------------------------------------------------
1 | QBCore = exports['qb-core']:GetCoreObject()
2 | PlayerData = {}
3 | inHuntingZone, inNoDispatchZone = false, false
4 | local huntingZones, nodispatchZones, huntingBlips = {} , {}, {}
5 |
6 | local blips = {}
7 | local radius2 = {}
8 | local alertsMuted = false
9 | local alertsDisabled = false
10 | local waypointCooldown = false
11 |
12 | -- Functions
13 | ---@param bool boolean Toggles visibilty of the menu
14 | local function toggleUI(bool)
15 | SetNuiFocus(bool, bool)
16 | SendNUIMessage({ action = "setVisible", data = bool })
17 | end
18 |
19 | -- Zone Functions --
20 | local function removeZones()
21 | -- Hunting Zone --
22 | for i = 1, #huntingZones do
23 | huntingZones[i]:remove()
24 | end
25 | -- No Dispatch Zone --
26 | for i = 1, #nodispatchZones do
27 | nodispatchZones[i]:remove()
28 | end
29 | -- Hunting Blips --
30 | for i = 1, #huntingBlips do
31 | RemoveBlip(huntingBlips[i])
32 | end
33 | -- Reset the stored values too
34 | huntingZones, nodispatchZones, huntingBlips = {} , {}, {}
35 | end
36 |
37 | local function createZones()
38 | -- Hunting Zone --
39 | if Config.Locations['HuntingZones'][1] then
40 | for _, hunting in pairs(Config.Locations["HuntingZones"]) do
41 | -- Creates the Blips
42 | if Config.EnableHuntingBlip then
43 | local blip = AddBlipForCoord(hunting.coords.x, hunting.coords.y, hunting.coords.z)
44 | local huntingradius = AddBlipForRadius(hunting.coords.x, hunting.coords.y, hunting.coords.z, hunting.radius)
45 | SetBlipSprite(blip, 442)
46 | SetBlipAsShortRange(blip, true)
47 | SetBlipScale(blip, 0.8)
48 | SetBlipColour(blip, 0)
49 | SetBlipColour(huntingradius, 0)
50 | SetBlipAlpha(huntingradius, 40)
51 | BeginTextCommandSetBlipName("STRING")
52 | AddTextComponentString(hunting.label)
53 | EndTextCommandSetBlipName(blip)
54 | huntingBlips[#huntingBlips+1] = blip
55 | huntingBlips[#huntingBlips+1] = huntingradius
56 | end
57 | -- Creates the Sphere --
58 | local huntingZone = lib.zones.sphere({
59 | coords = hunting.coords,
60 | radius = hunting.radius,
61 | debug = Config.Debug,
62 | onEnter = function()
63 | inHuntingZone = true
64 | end,
65 | onExit = function()
66 | inHuntingZone = false
67 | end
68 | })
69 | huntingZones[#huntingZones+1] = huntingZone
70 | end
71 | end
72 | -- No Dispatch Zone --
73 | if Config.Locations['NoDispatchZones'][1] then
74 | for _, nodispatch in pairs(Config.Locations["NoDispatchZones"]) do
75 | local nodispatchZone = lib.zones.box({
76 | coords = nodispatch.coords,
77 | size = vec3(nodispatch.length, nodispatch.width, nodispatch.maxZ - nodispatch.minZ),
78 | rotation = nodispatch.heading,
79 | debug = Config.Debug,
80 | onEnter = function()
81 | inNoDispatchZone = true
82 | end,
83 | onExit = function()
84 | inNoDispatchZone = false
85 | end
86 | })
87 | nodispatchZones[#nodispatchZones+1] = nodispatchZone
88 | end
89 | end
90 | end
91 |
92 | local function setupDispatch()
93 | local playerInfo = QBCore.Functions.GetPlayerData()
94 | local locales = lib.getLocales()
95 | PlayerData = {
96 | charinfo = {
97 | firstname = playerInfo.charinfo.firstname,
98 | lastname = playerInfo.charinfo.lastname
99 | },
100 | metadata = {
101 | callsign = playerInfo.metadata.callsign
102 | },
103 | citizenid = playerInfo.citizenid,
104 | job = {
105 | type = playerInfo.job.type,
106 | name = playerInfo.job.name,
107 | label = playerInfo.job.label
108 | },
109 | }
110 |
111 | Wait(1000)
112 |
113 | SendNUIMessage({
114 | action = "setupUI",
115 | data = {
116 | locales = locales,
117 | player = PlayerData,
118 | keybind = Config.RespondKeybind,
119 | maxCallList = Config.MaxCallList,
120 | shortCalls = Config.ShortCalls,
121 | }
122 | })
123 | end
124 |
125 | ---@param data string | table -- The player job or an array of jobs to check against
126 | ---@return boolean -- Returns true if the job is valid
127 | local function isJobValid(data)
128 | if PlayerData.job == nil then return false end
129 | local jobType = PlayerData.job.type
130 | local jobName = PlayerData.job.name
131 |
132 | if type(data) == "string" then
133 | return lib.table.contains(Config.Jobs, data) or lib.table.contains(Config.Jobs, jobName)
134 | elseif type(data) == "table" then
135 | return lib.table.contains(data, jobType) or lib.table.contains(data, jobName)
136 | end
137 |
138 | return false
139 | end
140 |
141 | local function openMenu()
142 | if not isJobValid(PlayerData.job.type) then return end
143 |
144 | local calls = lib.callback.await('ps-dispatch:callback:getCalls', false)
145 | if #calls == 0 then
146 | lib.notify({ description = locale('no_calls'), position = 'top', type = 'error' })
147 | else
148 | SendNUIMessage({ action = 'setDispatchs', data = calls, })
149 | toggleUI(true)
150 | end
151 | end
152 |
153 | local function setWaypoint()
154 | if not isJobValid(PlayerData.job.type) then return end
155 | if not IsOnDuty() then return end
156 |
157 | local data = lib.callback.await('ps-dispatch:callback:getLatestDispatch', false)
158 |
159 | if not data then return end
160 |
161 | if data.alertTime == nil then data.alertTime = Config.AlertTime end
162 | local timer = data.alertTime * 1000
163 |
164 | if not waypointCooldown and lib.table.contains(data.jobs, PlayerData.job.type) then
165 | SetNewWaypoint(data.coords.x, data.coords.y)
166 | TriggerServerEvent('ps-dispatch:server:attach', data.id, PlayerData)
167 | lib.notify({ description = locale('waypoint_set'), position = 'top', type = 'success' })
168 | waypointCooldown = true
169 | SetTimeout(timer, function()
170 | waypointCooldown = false
171 | end)
172 | end
173 | end
174 |
175 | local function randomOffset(baseX, baseY, offset)
176 | local randomX = baseX + math.random(-offset, offset)
177 | local randomY = baseY + math.random(-offset, offset)
178 |
179 | return randomX, randomY
180 | end
181 |
182 | local function createBlipData(coords, radius, sprite, color, scale, flash)
183 | local blip = AddBlipForCoord(coords.x, coords.y, coords.z)
184 | local radiusBlip = AddBlipForRadius(coords.x, coords.y, coords.z, radius)
185 |
186 | SetBlipFlashes(blip, flash)
187 | SetBlipSprite(blip, sprite or 161)
188 | SetBlipHighDetail(blip, true)
189 | SetBlipScale(blip, scale or 1.0)
190 | SetBlipColour(blip, color or 84)
191 | SetBlipAlpha(blip, 255)
192 | SetBlipAsShortRange(blip, false)
193 | SetBlipCategory(blip, 2)
194 | SetBlipColour(radiusBlip, color or 84)
195 | SetBlipAlpha(radiusBlip, 128)
196 |
197 | return blip, radiusBlip
198 | end
199 |
200 | local function createBlip(data, blipData)
201 | local blip, radius = nil, nil
202 | local sprite = blipData.sprite or blipData.alert.sprite or 161
203 | local color = blipData.color or blipData.alert.color or 84
204 | local scale = blipData.scale or blipData.alert.scale or 1.0
205 | local flash = blipData.flash or false
206 | local alpha = 255
207 | local radiusAlpha = 128
208 | local blipWaitTime = ((blipData.length or blipData.alert.length) * 60000) / radiusAlpha
209 |
210 | if blipData.offset then
211 | local offsetX, offsetY = randomOffset(data.coords.x, data.coords.y, Config.MaxOffset)
212 | blip, radius = createBlipData({ x = offsetX, y = offsetY, z = data.coords.z }, blipData.radius, sprite, color, scale, flash)
213 | blips[data.id] = blip
214 | radius2[data.id] = radius
215 | else
216 | blip, radius = createBlipData(data.coords, blipData.radius, sprite, color, scale, flash)
217 | blips[data.id] = blip
218 | radius2[data.id] = radius
219 | end
220 |
221 | BeginTextCommandSetBlipName('STRING')
222 | AddTextComponentString(data.code .. ' - ' .. data.message)
223 | EndTextCommandSetBlipName(blip)
224 |
225 | while radiusAlpha > 0 do
226 | Wait(blipWaitTime)
227 | radiusAlpha = math.max(0, radiusAlpha - 1)
228 | SetBlipAlpha(radius, radiusAlpha)
229 | end
230 |
231 | RemoveBlip(radius)
232 | RemoveBlip(blip)
233 | end
234 |
235 | local function addBlip(data, blipData)
236 | CreateThread(function()
237 | createBlip(data, blipData)
238 | end)
239 | if not alertsMuted then
240 | if blipData.sound == "Lose_1st" then
241 | PlaySound(-1, blipData.sound, blipData.sound2, 0, 0, 1)
242 | else
243 | TriggerServerEvent("InteractSound_SV:PlayOnSource", blipData.sound or blipData.alert.sound, 0.25)
244 | end
245 | end
246 | end
247 |
248 | -- Keybind
249 | local RespondToDispatch = lib.addKeybind({
250 | name = 'RespondToDispatch',
251 | description = 'Set waypoint to last call location',
252 | defaultKey = Config.RespondKeybind,
253 | onPressed = setWaypoint,
254 | })
255 |
256 | local OpenDispatchMenu = lib.addKeybind({
257 | name = 'OpenDispatchMenu',
258 | description = 'Open Dispatch Menu',
259 | defaultKey = Config.OpenDispatchMenu,
260 | onPressed = openMenu,
261 | })
262 |
263 | -- Events
264 | RegisterNetEvent('ps-dispatch:client:notify', function(data, source)
265 | if data.alertTime == nil then data.alertTime = Config.AlertTime end
266 | local timer = data.alertTime * 1000
267 |
268 | if alertsDisabled then return end
269 | if not isJobValid(data.jobs) then return end
270 | if not IsOnDuty() then return end
271 |
272 | timerCheck = true
273 |
274 | SendNUIMessage({
275 | action = 'newCall',
276 | data = {
277 | data = data,
278 | timer = timer,
279 | }
280 | })
281 |
282 | addBlip(data, Config.Blips[data.codeName] or data.alert)
283 |
284 | RespondToDispatch:disable(false)
285 | OpenDispatchMenu:disable(true)
286 |
287 | local startTime = GetGameTimer()
288 | while timerCheck do
289 | Wait(1000)
290 |
291 | local currentTime = GetGameTimer()
292 | local elapsed = currentTime - startTime
293 |
294 | if elapsed >= timer then
295 | break
296 | end
297 | end
298 |
299 | timerCheck = false
300 | OpenDispatchMenu:disable(false)
301 | RespondToDispatch:disable(true)
302 | end)
303 |
304 | RegisterNetEvent('ps-dispatch:client:openMenu', function(data)
305 | if not isJobValid(PlayerData.job.type) then return end
306 | if not IsOnDuty() then return end
307 |
308 | if #data == 0 then
309 | lib.notify({ description = locale('no_calls'), position = 'top', type = 'error' })
310 | else
311 | toggleUI(true)
312 | SendNUIMessage({ action = 'setDispatchs', data = data, })
313 | end
314 | end)
315 |
316 | -- EventHandlers
317 | RegisterNetEvent("QBCore:Client:OnJobUpdate", setupDispatch)
318 |
319 | AddEventHandler('QBCore:Client:OnPlayerLoaded', function()
320 | setupDispatch()
321 | createZones()
322 | end)
323 |
324 | AddEventHandler('QBCore:Client:OnPlayerUnload', removeZones)
325 |
326 | AddEventHandler('onResourceStart', function(resourceName)
327 | if resourceName ~= GetCurrentResourceName() then return end
328 | setupDispatch()
329 | end)
330 |
331 | AddEventHandler('onResourceStop', function(resourceName)
332 | if resourceName ~= GetCurrentResourceName() then return end
333 | removeZones()
334 | end)
335 |
336 | -- NUICallbacks
337 | RegisterNUICallback("hideUI", function(_, cb)
338 | toggleUI(false)
339 | cb("ok")
340 | end)
341 |
342 | RegisterNUICallback("attachUnit", function(data, cb)
343 | TriggerServerEvent('ps-dispatch:server:attach', data.id, PlayerData)
344 | SetNewWaypoint(data.coords.x, data.coords.y)
345 | cb("ok")
346 | end)
347 |
348 | RegisterNUICallback("detachUnit", function(data, cb)
349 | TriggerServerEvent('ps-dispatch:server:detach', data.id, PlayerData)
350 | DeleteWaypoint()
351 | cb("ok")
352 | end)
353 |
354 | RegisterNUICallback("toggleMute", function(data, cb)
355 | local muteStatus = data.boolean and locale('muted') or locale('unmuted')
356 | lib.notify({ description = locale('alerts') .. muteStatus, position = 'top', type = 'warning' })
357 | alertsMuted = data.boolean
358 | cb("ok")
359 | end)
360 |
361 | RegisterNUICallback("toggleAlerts", function(data, cb)
362 | local muteStatus = data.boolean and locale('disabled') or locale('enabled')
363 | lib.notify({ description = locale('alerts') .. muteStatus, position = 'top', type = 'warning' })
364 | alertsDisabled = data.boolean
365 | cb("ok")
366 | end)
367 |
368 | RegisterNUICallback("clearBlips", function(data, cb)
369 | lib.notify({ description = locale('blips_cleared'), position = 'top', type = 'success' })
370 | for k, v in pairs(blips) do
371 | RemoveBlip(v)
372 | end
373 | for k, v in pairs(radius2) do
374 | RemoveBlip(v)
375 | end
376 | cb("ok")
377 | end)
378 |
379 | RegisterNUICallback("refreshAlerts", function(data, cb)
380 | lib.notify({ description = locale('alerts_refreshed'), position = 'top', type = 'success' })
381 | local data = lib.callback.await('ps-dispatch:callback:getCalls', false)
382 | SendNUIMessage({ action = 'setDispatchs', data = data, })
383 | cb("ok")
384 | end)
385 |
--------------------------------------------------------------------------------
/client/utils.lua:
--------------------------------------------------------------------------------
1 | function GetPlayerHeading()
2 | local heading = GetEntityHeading(cache.ped)
3 |
4 | if heading >= 315 or heading < 45 then
5 | return locale('north')
6 | elseif heading >= 45 and heading < 135 then
7 | return locale('west')
8 | elseif heading >= 135 and heading < 225 then
9 | return locale('south')
10 | elseif heading >= 225 and heading < 315 then
11 | return locale('east')
12 | end
13 | end
14 |
15 | function GetPlayerGender()
16 | local gender = locale('male')
17 | if QBCore.Functions.GetPlayerData().charinfo.gender == 1 then
18 | gender = locale('female')
19 | end
20 | return gender
21 | end
22 |
23 | function GetIsHandcuffed()
24 | return QBCore.Functions.GetPlayerData()?.metadata?.ishandcuffed
25 | end
26 |
27 | function IsOnDuty()
28 | if Config.OnDutyOnly then
29 | if QBCore.Functions.GetPlayerData().job.onduty then
30 | return true
31 | else
32 | return false
33 | end
34 | end
35 | return true
36 | end
37 |
38 | ---@return boolean
39 | local function HasPhone()
40 | for _, item in ipairs(Config.PhoneItems) do
41 | if QBCore.Functions.HasItem(item) then
42 | return true
43 | end
44 | end
45 | return false
46 | end
47 |
48 | ---@param coords table
49 | ---@return string
50 | function GetStreetAndZone(coords)
51 | local zone = GetLabelText(GetNameOfZone(coords.x, coords.y, coords.z))
52 | local street = GetStreetNameFromHashKey(GetStreetNameAtCoord(coords.x, coords.y, coords.z))
53 | return street .. ", " .. zone
54 | end
55 |
56 | ---@param vehicle string
57 | ---@return string
58 | local function getVehicleColor(vehicle)
59 | local vehicleColor1, vehicleColor2 = GetVehicleColours(vehicle)
60 | local color1 = Config.Colors[tostring(vehicleColor1)]
61 | local color2 = Config.Colors[tostring(vehicleColor2)]
62 |
63 | if color1 and color2 then
64 | return color2 .. " on " .. color1
65 | elseif color1 then
66 | return color1
67 | elseif color2 then
68 | return color2
69 | else
70 | return "Unknown"
71 | end
72 | end
73 |
74 | ---@param vehicle string
75 | ---@return string
76 | local function getVehicleDoors(vehicle)
77 | local doorCount = 0
78 |
79 | if GetEntityBoneIndexByName(vehicle, 'door_pside_f') ~= -1 then doorCount = doorCount + 1 end
80 | if GetEntityBoneIndexByName(vehicle, 'door_pside_r') ~= -1 then doorCount = doorCount + 1 end
81 | if GetEntityBoneIndexByName(vehicle, 'door_dside_f') ~= -1 then doorCount = doorCount + 1 end
82 | if GetEntityBoneIndexByName(vehicle, 'door_dside_r') ~= -1 then doorCount = doorCount + 1 end
83 |
84 | if doorCount == 2 then
85 | doorCount = locale('two_door')
86 | elseif doorCount == 3 then
87 | doorCount = locale('three_door')
88 | elseif doorCount == 4 then
89 | doorCount = locale('four_door')
90 | else
91 | doorCount = 'unknown'
92 | end
93 |
94 | return doorCount
95 | end
96 |
97 | ---@param vehicle string
98 | ---@return table
99 | function GetVehicleData(vehicle)
100 | local data = {}
101 |
102 | local vehicleClass = {
103 | [0] = locale('compact'),
104 | [1] = locale('sedan'),
105 | [2] = locale('suv'),
106 | [3] = locale('coupe'),
107 | [4] = locale('muscle'),
108 | [5] = locale('sports_classic'),
109 | [6] = locale('sports'),
110 | [7] = locale('super'),
111 | [8] = locale('motorcycle'),
112 | [9] = locale('offroad'),
113 | [10] = locale('industrial'),
114 | [11] = locale('utility'),
115 | [12] = locale('van'),
116 | [17] = locale('service'),
117 | [19] = locale('military'),
118 | [20] = locale('truck')
119 | }
120 |
121 | data.class = vehicleClass[GetVehicleClass(vehicle)] or "Unknown"
122 | data.name = GetLabelText(GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)))
123 | data.plate = GetVehicleNumberPlateText(vehicle)
124 | data.doors = getVehicleDoors(vehicle)
125 | data.color = getVehicleColor(vehicle)
126 | data.id = NetworkGetNetworkIdFromEntity(vehicle)
127 |
128 | return data
129 | end
130 |
131 | function PhoneAnimation()
132 | lib.requestAnimDict("cellphone@in_car@ds", 500)
133 |
134 | if not IsEntityPlayingAnim(cache.ped, "cellphone@in_car@ds", "cellphone_call_listen_base", 3) then
135 | TaskPlayAnim(cache.ped, "cellphone@in_car@ds", "cellphone_call_listen_base", 3.0, 3.0, -1, 50, 0, false, false, false)
136 | end
137 |
138 | Wait(2500)
139 | StopEntityAnim(cache.ped, "cellphone_call_listen_base", "cellphone@in_car@ds", 3)
140 | end
141 |
142 | ---@param message string
143 | ---@return boolean
144 | function IsCallAllowed(message)
145 | local msgLength = string.len(message)
146 |
147 | if msgLength == 0 then return false end
148 | if GetIsHandcuffed() then return false end
149 | if Config.PhoneRequired and not HasPhone() then QBCore.Functions.Notify('You need a communications device for this.', 'error', 5000) return false end
150 |
151 | return true
152 | end
153 |
154 | local weaponTable = {
155 | [584646201] = "CLASS 2: AP-Pistol",
156 | [453432689] = "CLASS 1: Pistol",
157 | [3219281620] = "CLASS 1: Pistol MK2",
158 | [1593441988] = "CLASS 1: Combat Pistol",
159 | [-1716589765] = "CLASS 1: Heavy Pistol",
160 | [-1076751822] = "CLASS 1: SNS-Pistol",
161 | [-771403250] = "CLASS 2: Desert Eagle",
162 | [137902532] = "CLASS 2: Vintage Pistol",
163 | [-598887786] = "CLASS 2: Marksman Pistol",
164 | [-1045183535] = "CLASS 2: Revolver",
165 | [911657153] = "Taser",
166 | [324215364] = "CLASS 2: Micro-SMG",
167 | [-619010992] = "CLASS 2: Machine-Pistol",
168 | [736523883] = "CLASS 2: SMG",
169 | [2024373456] = "CLASS 2: SMG MK2",
170 | [-270015777] = "CLASS 2: Assault SMG",
171 | [171789620] = "CLASS 2: Combat PDW",
172 | [-1660422300] = "CLASS 4: Combat MG",
173 | [3686625920] = "CLASS 4: Combat MG MK2",
174 | [1627465347] = "CLASS 4: Gusenberg",
175 | [-1121678507] = "CLASS 2: Mini SMG",
176 | [-1074790547] = "CLASS 3: Assaultrifle",
177 | [961495388] = "CLASS 3: Assaultrifle MK2",
178 | [-2084633992] = "CLASS 3: Carbinerifle",
179 | [4208062921] = "CLASS 3: Carbinerifle MK2",
180 | [-1357824103] = "CLASS 3: Advancedrifle",
181 | [-1063057011] = "CLASS 3: Specialcarbine",
182 | [2132975508] = "CLASS 3: Bulluprifle",
183 | [1649403952] = "CLASS 3: Compactrifle",
184 | [100416529] = "CLASS 4: Sniperrifle",
185 | [205991906] = "CLASS 4: Heavy Sniper",
186 | [177293209] = "CLASS 4: Heavy Sniper MK2",
187 | [-952879014] = "CLASS 4: Marksmanrifle",
188 | [487013001] = "CLASS 2: Pumpshotgun",
189 | [2017895192] = "CLASS 2: Sawnoff Shotgun",
190 | [-1654528753] = "CLASS 3: Bullupshotgun",
191 | [-494615257] = "CLASS 3: Assaultshotgun",
192 | [-1466123874] = "CLASS 3: Musket",
193 | [984333226] = "CLASS 3: Heavyshotgun",
194 | [-275439685] = "CLASS 2: Doublebarrel Shotgun",
195 | [317205821] = "CLASS 2: Autoshotgun",
196 | [-1568386805] = "CLASS 5: GRENADE LAUNCHER",
197 | [-1312131151] = "CLASS 5: RPG",
198 | [125959754] = "CLASS 5: Compactlauncher"
199 | }
200 |
201 | function GetWeaponName()
202 | local currentWeapon = GetSelectedPedWeapon(cache.ped)
203 | return weaponTable[currentWeapon] or "Unknown"
204 | end
205 |
--------------------------------------------------------------------------------
/fxmanifest.lua:
--------------------------------------------------------------------------------
1 | fx_version 'cerulean'
2 |
3 | game "gta5"
4 |
5 | author "Project Sloth & OK1ez"
6 | version '2.2.1'
7 |
8 | lua54 'yes'
9 |
10 | ui_page 'html/index.html'
11 | -- ui_page 'http://localhost:5173/' --for dev
12 |
13 | client_script {
14 | '@PolyZone/client.lua',
15 | '@PolyZone/CircleZone.lua',
16 | '@PolyZone/BoxZone.lua',
17 | 'client/**',
18 | }
19 | server_script {
20 | "server/**",
21 | }
22 | shared_script {
23 | "shared/**",
24 | '@ox_lib/init.lua',
25 | }
26 |
27 | files {
28 | 'html/**',
29 | 'locales/*.json',
30 | }
31 |
32 | ox_lib 'locale' -- v3.8.0 or above
33 |
--------------------------------------------------------------------------------
/html/index.css:
--------------------------------------------------------------------------------
1 | div.svelte-11k92at{position:absolute;left:0;top:0}main.svelte-a4h32x{position:absolute;left:0;top:0;z-index:100;-webkit-user-select:none;-moz-user-select:none;user-select:none;box-sizing:border-box;padding:0;margin:0;height:100vh;width:100vw}*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.visible{visibility:visible}.absolute{position:absolute}.top-0{top:0}.z-\[1000\]{z-index:1000}.mx-\[1vh\]{margin-left:1vh;margin-right:1vh}.mx-\[2vh\]{margin-left:2vh;margin-right:2vh}.my-\[0\.5vh\]{margin-top:.5vh;margin-bottom:.5vh}.mb-\[1vh\]{margin-bottom:1vh}.ml-\[0\.5vh\]{margin-left:.5vh}.ml-\[1vh\]{margin-left:1vh}.ml-\[2vh\]{margin-left:2vh}.ml-\[3vh\]{margin-left:3vh}.ml-auto{margin-left:auto}.mr-4{margin-right:1rem}.mr-\[0\.5vh\]{margin-right:.5vh}.mr-\[1vh\]{margin-right:1vh}.mr-\[2vh\]{margin-right:2vh}.flex{display:flex}.h-\[3vh\]{height:3vh}.h-\[5vh\]{height:5vh}.h-\[85\%\]{height:85%}.h-\[97\%\]{height:97%}.h-fit{height:-moz-fit-content;height:fit-content}.h-screen{height:100vh}.w-\[25\%\]{width:25%}.w-\[3\.2vh\]{width:3.2vh}.w-\[30\%\]{width:30%}.w-\[70\%\]{width:70%}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.resize{resize:both}.flex-row{flex-direction:row}.flex-row-reverse{flex-direction:row-reverse}.flex-col{flex-direction:column}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.gap-2{gap:.5rem}.gap-\[0\.2vh\]{gap:.2vh}.gap-\[1vh\]{gap:1vh}.gap-y-\[0\.4vh\]{row-gap:.4vh}.overflow-auto{overflow:auto}.rounded-full{border-radius:9999px}.bg-\[\#004ca5\]{--tw-bg-opacity: 1;background-color:rgb(0 76 165 / var(--tw-bg-opacity))}.bg-\[\#0098A3\]{--tw-bg-opacity: 1;background-color:rgb(0 152 163 / var(--tw-bg-opacity))}.bg-\[\#232B33\]{--tw-bg-opacity: 1;background-color:rgb(35 43 51 / var(--tw-bg-opacity))}.bg-\[\#25303B\]{--tw-bg-opacity: 1;background-color:rgb(37 48 59 / var(--tw-bg-opacity))}.bg-\[\#4b4b4b\]{--tw-bg-opacity: 1;background-color:rgb(75 75 75 / var(--tw-bg-opacity))}.bg-\[\#e03535\]{--tw-bg-opacity: 1;background-color:rgb(224 53 53 / var(--tw-bg-opacity))}.bg-accent_cyan{--tw-bg-opacity: 1;background-color:rgb(0 152 163 / var(--tw-bg-opacity))}.bg-accent_dark_green{--tw-bg-opacity: 1;background-color:rgb(0 133 99 / var(--tw-bg-opacity))}.bg-accent_dark_red{--tw-bg-opacity: 1;background-color:rgb(133 0 50 / var(--tw-bg-opacity))}.bg-accent_green{--tw-bg-opacity: 1;background-color:rgb(0 163 121 / var(--tw-bg-opacity))}.bg-accent_red{--tw-bg-opacity: 1;background-color:rgb(255 0 78 / var(--tw-bg-opacity))}.bg-neutral-700{--tw-bg-opacity: 1;background-color:rgb(64 64 64 / var(--tw-bg-opacity))}.bg-primary{--tw-bg-opacity: 1;background-color:rgb(35 43 51 / var(--tw-bg-opacity))}.bg-priority_primary{--tw-bg-opacity: 1;background-color:rgb(51 35 40 / var(--tw-bg-opacity))}.bg-priority_quaternary{--tw-bg-opacity: 1;background-color:rgb(154 0 58 / var(--tw-bg-opacity))}.bg-priority_secondary{--tw-bg-opacity: 1;background-color:rgb(59 37 47 / var(--tw-bg-opacity))}.bg-priority_tertiary{--tw-bg-opacity: 1;background-color:rgb(71 47 57 / var(--tw-bg-opacity))}.bg-secondary{--tw-bg-opacity: 1;background-color:rgb(37 48 59 / var(--tw-bg-opacity))}.bg-tertiary{--tw-bg-opacity: 1;background-color:rgb(47 60 71 / var(--tw-bg-opacity))}.p-2{padding:.5rem}.p-\[1vh\]{padding:1vh}.px-\[1\.4vh\]{padding-left:1.4vh;padding-right:1.4vh}.px-\[1\.5vh\]{padding-left:1.5vh;padding-right:1.5vh}.px-\[2vh\]{padding-left:2vh;padding-right:2vh}.py-\[0\.2vh\]{padding-top:.2vh;padding-bottom:.2vh}.py-\[0\.4vh\]{padding-top:.4vh;padding-bottom:.4vh}.pr-\[0\.5vh\]{padding-right:.5vh}.text-start{text-align:start}.text-\[1\.3vh\]{font-size:1.3vh}.text-\[1\.4vh\]{font-size:1.4vh}.text-\[1\.5vh\]{font-size:1.5vh}.font-medium{font-weight:500}.uppercase{text-transform:uppercase}.text-accent_cyan{--tw-text-opacity: 1;color:rgb(0 152 163 / var(--tw-text-opacity))}.text-accent_red{--tw-text-opacity: 1;color:rgb(255 0 78 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}*{margin:0;padding:0}*:focus{outline:none}:root{font-size:62.5%;font-smooth:auto;color:#d8d8d8;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%}html,body{height:100vh;width:100vw;font-size:1.6rem;overflow:hidden}::-webkit-scrollbar{height:0px;width:4px;background-color:#7979795e;border-radius:50px}::-webkit-scrollbar-thumb{background-color:#cecece;border-radius:50px}.hover\:bg-secondary:hover{--tw-bg-opacity: 1;background-color:rgb(37 48 59 / var(--tw-bg-opacity))}
2 |
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | OK1ez
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/html/index.js:
--------------------------------------------------------------------------------
1 | (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))l(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const r of s.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&l(r)}).observe(document,{childList:!0,subtree:!0});function n(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function l(i){if(i.ep)return;i.ep=!0;const s=n(i);fetch(i.href,s)}})();function q(){}const Vt=e=>e;function Ht(e,t){for(const n in t)e[n]=t[n];return e}function Et(e){return e()}function He(){return Object.create(null)}function Z(e){e.forEach(Et)}function De(e){return typeof e=="function"}function ie(e,t){return e!=e?t==t:e!==t||e&&typeof e=="object"||typeof e=="function"}function Yt(e){return Object.keys(e).length===0}function Nt(e,...t){if(e==null)return q;const n=e.subscribe(...t);return n.unsubscribe?()=>n.unsubscribe():n}function z(e,t,n){e.$$.on_destroy.push(Nt(t,n))}function Gt(e,t,n,l){if(e){const i=Dt(e,t,n,l);return e[0](i)}}function Dt(e,t,n,l){return e[1]&&l?Ht(n.ctx.slice(),e[1](l(t))):n.ctx}function zt(e,t,n,l){if(e[2]&&l){const i=e[2](l(n));if(t.dirty===void 0)return i;if(typeof i=="object"){const s=[],r=Math.max(t.dirty.length,i.length);for(let a=0;a32){const t=[],n=e.ctx.length/32;for(let l=0;lwindow.performance.now():()=>Date.now(),Oe=St?e=>requestAnimationFrame(e):q;const ae=new Set;function At(e){ae.forEach(t=>{t.c(e)||(ae.delete(t),t.f())}),ae.size!==0&&Oe(At)}function Kt(e){let t;return ae.size===0&&Oe(At),{promise:new Promise(n=>{ae.add(t={c:e,f:n})}),abort(){ae.delete(t)}}}function d(e,t){e.appendChild(t)}function It(e){if(!e)return document;const t=e.getRootNode?e.getRootNode():e.ownerDocument;return t&&t.host?t:e.ownerDocument}function Qt(e){const t=g("style");return Xt(It(e),t),t.sheet}function Xt(e,t){return d(e.head||e,t),t.sheet}function L(e,t,n){e.insertBefore(t,n||null)}function S(e){e.parentNode&&e.parentNode.removeChild(e)}function _e(e,t){for(let n=0;ne.removeEventListener(t,n,l)}function _(e,t,n){n==null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function Zt(e){return Array.from(e.childNodes)}function B(e,t){t=""+t,e.data!==t&&(e.data=t)}function J(e,t,n){e.classList[n?"add":"remove"](t)}function xt(e,t,{bubbles:n=!1,cancelable:l=!1}={}){const i=document.createEvent("CustomEvent");return i.initCustomEvent(e,n,l,t),i}const ke=new Map;let Ee=0;function en(e){let t=5381,n=e.length;for(;n--;)t=(t<<5)-t^e.charCodeAt(n);return t>>>0}function tn(e,t){const n={stylesheet:Qt(t),rules:{}};return ke.set(e,n),n}function Ge(e,t,n,l,i,s,r,a=0){const f=16.666/l;let o=`{
2 | `;for(let $=0;$<=1;$+=f){const y=t+(n-t)*s($);o+=$*100+`%{${r(y,1-y)}}
3 | `}const u=o+`100% {${r(n,1-n)}}
4 | }`,c=`__svelte_${en(u)}_${a}`,m=It(e),{stylesheet:p,rules:b}=ke.get(m)||tn(m,e);b[c]||(b[c]=!0,p.insertRule(`@keyframes ${c} ${u}`,p.cssRules.length));const N=e.style.animation||"";return e.style.animation=`${N?`${N}, `:""}${c} ${l}ms linear ${i}ms 1 both`,Ee+=1,c}function nn(e,t){const n=(e.style.animation||"").split(", "),l=n.filter(t?s=>s.indexOf(t)<0:s=>s.indexOf("__svelte")===-1),i=n.length-l.length;i&&(e.style.animation=l.join(", "),Ee-=i,Ee||ln())}function ln(){Oe(()=>{Ee||(ke.forEach(e=>{const{ownerNode:t}=e.stylesheet;t&&S(t)}),ke.clear())})}let ge;function ve(e){ge=e}function Re(){if(!ge)throw new Error("Function called outside component initialization");return ge}function Me(e){Re().$$.on_mount.push(e)}function rn(e){Re().$$.after_update.push(e)}function Ct(e){Re().$$.on_destroy.push(e)}const oe=[],ze=[];let fe=[];const We=[],sn=Promise.resolve();let Ce=!1;function on(){Ce||(Ce=!0,sn.then(jt))}function te(e){fe.push(e)}const Ae=new Set;let re=0;function jt(){if(re!==0)return;const e=ge;do{try{for(;ree.indexOf(l)===-1?t.push(l):n.push(l)),n.forEach(l=>l()),fe=t}let me;function cn(){return me||(me=Promise.resolve(),me.then(()=>{me=null})),me}function Ie(e,t,n){e.dispatchEvent(xt(`${t?"intro":"outro"}${n}`))}const $e=new Set;let X;function ne(){X={r:0,c:[],p:X}}function le(){X.r||Z(X.c),X=X.p}function F(e,t){e&&e.i&&($e.delete(e),e.i(t))}function V(e,t,n,l){if(e&&e.o){if($e.has(e))return;$e.add(e),X.c.push(()=>{$e.delete(e),l&&(n&&e.d(1),l())}),e.o(t)}else l&&l()}const un={duration:0};function ce(e,t,n,l){const i={direction:"both"};let s=t(e,n,i),r=l?0:1,a=null,f=null,o=null;function u(){o&&nn(e,o)}function c(p,b){const N=p.b-r;return b*=Math.abs(N),{a:r,b:p.b,d:N,duration:b,start:p.start,end:p.start+b,group:p.group}}function m(p){const{delay:b=0,duration:N=300,easing:$=Vt,tick:y=q,css:T}=s||un,E={start:Jt()+b,b:p};p||(E.group=X,X.r+=1),a||f?f=E:(T&&(u(),o=Ge(e,r,p,N,b,$,T)),p&&y(0,1),a=c(E,N),te(()=>Ie(e,p,"start")),Kt(w=>{if(f&&w>f.start&&(a=c(f,N),f=null,Ie(e,a.b,"start"),T&&(u(),o=Ge(e,r,a.b,a.duration,0,$,s.css))),a){if(w>=a.end)y(r=a.b,1-r),Ie(e,a.b,"end"),f||(a.b?u():--a.group.r||Z(a.group.c)),a=null;else if(w>=a.start){const U=w-a.start;r=a.a+a.d*$(U/a.duration),y(r,1-r)}}return!!(a||f)}))}return{run(p){De(s)?cn().then(()=>{s=s(i),m(p)}):m(p)},end(){u(),a=f=null}}}function dn(e,t){V(e,1,1,()=>{t.delete(e.key)})}function _n(e,t,n,l,i,s,r,a,f,o,u,c){let m=e.length,p=s.length,b=m;const N={};for(;b--;)N[e[b].key]=b;const $=[],y=new Map,T=new Map,E=[];for(b=p;b--;){const k=c(i,s,b),h=n(k);let v=r.get(h);v?l&&E.push(()=>v.p(k,t)):(v=o(h,k),v.c()),y.set(h,$[b]=v),h in N&&T.set(h,Math.abs(b-N[h]))}const w=new Set,U=new Set;function D(k){F(k,1),k.m(a,u),r.set(k.key,k),u=k.first,p--}for(;m&&p;){const k=$[p-1],h=e[m-1],v=k.key,I=h.key;k===h?(u=k.first,m--,p--):y.has(I)?!r.has(v)||w.has(v)?D(k):U.has(I)?m--:T.get(v)>T.get(I)?(U.add(v),D(k)):(w.add(I),m--):(f(h,r),m--)}for(;m--;){const k=e[m];y.has(k.key)||f(k,r)}for(;p;)D($[p-1]);return Z(E),$}function ye(e){e&&e.c()}function ue(e,t,n,l){const{fragment:i,after_update:s}=e.$$;i&&i.m(t,n),l||te(()=>{const r=e.$$.on_mount.map(Et).filter(De);e.$$.on_destroy?e.$$.on_destroy.push(...r):Z(r),e.$$.on_mount=[]}),s.forEach(te)}function de(e,t){const n=e.$$;n.fragment!==null&&(fn(n.after_update),Z(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}function pn(e,t){e.$$.dirty[0]===-1&&(oe.push(e),on(),e.$$.dirty.fill(0)),e.$$.dirty[t/31|0]|=1<{const b=p.length?p[0]:m;return o.ctx&&i(o.ctx[c],o.ctx[c]=b)&&(!o.skip_bound&&o.bound[c]&&o.bound[c](b),u&&pn(e,c)),m}):[],o.update(),u=!0,Z(o.before_update),o.fragment=l?l(o.ctx):!1,t.target){if(t.hydrate){const c=Zt(t.target);o.fragment&&o.fragment.l(c),c.forEach(S)}else o.fragment&&o.fragment.c();t.intro&&F(e.$$.fragment),ue(e,t.target,t.anchor,t.customElement),jt()}ve(f)}class he{$destroy(){de(this,1),this.$destroy=q}$on(t,n){if(!De(n))return q;const l=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return l.push(n),()=>{const i=l.indexOf(n);i!==-1&&l.splice(i,1)}}$set(t){this.$$set&&!Yt(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}}function be(e,t){const n=l=>{const{action:i,data:s}=l.data;i===e&&t(s)};Me(()=>window.addEventListener("message",n)),Ct(()=>window.removeEventListener("message",n))}const se=[];function hn(e,t){return{subscribe:W(e,t).subscribe}}function W(e,t=q){let n;const l=new Set;function i(a){if(ie(e,a)&&(e=a,n)){const f=!se.length;for(const o of l)o[1](),se.push(o,e);if(f){for(let o=0;o{l.delete(o),l.size===0&&n&&(n(),n=null)}}return{set:i,update:s,subscribe:r}}function mn(e,t,n){const l=!Array.isArray(e),i=l?[e]:e,s=t.length<2;return hn(n,r=>{let a=!1;const f=[];let o=0,u=q;const c=()=>{if(o)return;u();const p=t(l?f[0]:f,r);s?r(p):u=De(p)?p:q},m=i.map((p,b)=>Nt(p,N=>{f[b]=N,o&=~(1<{o|=1<t.filter(n=>n.data.id!==e))}const Be=W(null),Pe=W(null),vn=mn([Be,Lt,Ue],([e,t,n])=>!e||t===null||!n?[]:e.slice(-t).filter(l=>l.message&&l.jobs.includes(n.job.type)).reverse());let Le=!1;Se.subscribe(e=>{Le=e});let Ut="";je.subscribe(e=>{Ut=e});async function K(e,t={},n){if(Le==!0&&n||Le==!0)return Promise.resolve(n||{});const l={method:"post",headers:{"Content-Type":"application/json; charset=UTF-8"},body:JSON.stringify(t)},i=window.GetParentResourceName?window.GetParentResourceName():Ut;return await(await fetch(`https://${i}/${e}`,l)).json()}function Ke(e){let t,n;const l=e[2].default,i=Gt(l,e,e[1],null);return{c(){t=g("main"),i&&i.c(),_(t,"class","svelte-a4h32x")},m(s,r){L(s,t,r),i&&i.m(t,null),n=!0},p(s,r){i&&i.p&&(!n||r&2)&&Wt(i,l,s,s[1],n?zt(l,s[1],r,null):qt(s[1]),null)},i(s){n||(F(i,s),n=!0)},o(s){V(i,s),n=!1},d(s){s&&S(t),i&&i.d(s)}}}function gn(e){let t,n,l=e[0]&&Ke(e);return{c(){l&&l.c(),t=x()},m(i,s){l&&l.m(i,s),L(i,t,s),n=!0},p(i,[s]){i[0]?l?(l.p(i,s),s&1&&F(l,1)):(l=Ke(i),l.c(),F(l,1),l.m(t.parentNode,t)):l&&(ne(),V(l,1,1,()=>{l=null}),le())},i(i){n||(F(l),n=!0)},o(i){V(l),n=!1},d(i){l&&l.d(i),i&&S(t)}}}function yn(e,t,n){let l,i;z(e,Se,f=>n(4,l=f)),z(e,ee,f=>n(0,i=f));let{$$slots:s={},$$scope:r}=t,a;return ee.subscribe(f=>{a=f}),be("setVisible",f=>{ee.set(f)}),Me(()=>{const f=o=>{a&&o.code==="Escape"&&(K("hideUI"),ee.set(!1)),!a&&o.code==="Escape"&&l&&(K("setVisible",!0),ee.set(!0))};return window.addEventListener("keydown",f),()=>window.removeEventListener("keydown",f)}),e.$$set=f=>{"$$scope"in f&&n(1,r=f.$$scope)},[i,r,s]}class wn extends he{constructor(t){super(),pe(this,t,yn,gn,ie,{})}}const $n=()=>!window.invokeNative,Te=(e,t=0)=>{if($n())for(const n of e)setTimeout(()=>{window.dispatchEvent(new MessageEvent("message",{data:{action:n.action,data:n.data}}))},t)};function Qe(e,t,n){const l=e.slice();return l[4]=t[n],l}function Xe(e,t,n){const l=e.slice();return l[7]=t[n],l}function Ze(e){let t,n=e[1],l=[];for(let i=0;i{n(0,l=!l)},a=>{if(a.custom==!0){a.customFunction();return}Te([{action:a.action,data:a.data}])}]}class Nn extends he{constructor(t){super(),pe(this,t,En,kn,ie,{})}}function Dn(e,t,n){let l;z(e,ee,s=>n(0,l=s)),Te([{action:"setVisible",data:!0}]),Te([{action:"setBrowserMode",data:!0}]);function i(s){s.key==="="&&Mt(ee,l=!0,l)}return be("setBrowserMode",s=>{Se.set(s),console.log("browser mode enabled"),s?window.addEventListener("keydown",i):window.removeEventListener("keydown",i)}),be("newCall",s=>{Fe.update(r=>(r=r||[],r.push(s),r))}),be("setDispatchs",s=>{Be.set(s)}),be("setupUI",s=>{Ue.set(s.player),Pe.set(s.locales),Tt.set(s.keybind),Lt.set(s.maxCallList),Rt.set(s.shortCalls)}),[]}class Mn extends he{constructor(t){super(),pe(this,t,Dn,null,ie,{})}}function Ft(e){const t=e-1;return t*t*t+1}function Ne(e,{delay:t=0,duration:n=400,easing:l=Ft,x:i=0,y:s=0,opacity:r=0}={}){const a=getComputedStyle(e),f=+a.opacity,o=a.transform==="none"?"":a.transform,u=f*(1-r),[c,m]=Ye(i),[p,b]=Ye(s);return{delay:t,duration:n,easing:l,css:(N,$)=>`
5 | transform: ${o} translate(${(1-N)*c}${m}, ${(1-N)*p}${b});
6 | opacity: ${f-u*$}`}}function tt(e,{delay:t=0,duration:n=400,easing:l=Ft,axis:i="y"}={}){const s=getComputedStyle(e),r=+s.opacity,a=i==="y"?"height":"width",f=parseFloat(s[a]),o=i==="y"?["top","bottom"]:["left","right"],u=o.map(y=>`${y[0].toUpperCase()}${y.slice(1)}`),c=parseFloat(s[`padding${u[0]}`]),m=parseFloat(s[`padding${u[1]}`]),p=parseFloat(s[`margin${u[0]}`]),b=parseFloat(s[`margin${u[1]}`]),N=parseFloat(s[`border${u[0]}Width`]),$=parseFloat(s[`border${u[1]}Width`]);return{delay:t,duration:n,easing:l,css:y=>`overflow: hidden;opacity: ${Math.min(y*20,1)*r};${a}: ${y*f}px;padding-${o[0]}: ${y*c}px;padding-${o[1]}: ${y*m}px;margin-${o[0]}: ${y*p}px;margin-${o[1]}: ${y*b}px;border-${o[0]}-width: ${y*N}px;border-${o[1]}-width: ${y*$}px;`}}const Sn=["January","February","March","April","May","June","July","August","September","October","November","December"];function we(e,t=!1,n=!1){const l=e.getDate(),i=Sn[e.getMonth()],s=e.getFullYear(),r=e.getHours();let a=e.getMinutes();return a<10&&(a=`0${a}`),t?`${t} at ${r}:${a}`:n?`${l}. ${i} at ${r}:${a}`:`${l}. ${i} ${s}. at ${r}:${a}`}function Bt(e){if(!e)return"Unknown";let t;try{t=typeof e=="object"?e:new Date(e)}catch{return"Invalid date"}if(isNaN(t))return"Invalid date";const n=864e5,l=new Date,i=new Date(l-n),s=Math.round((l-t)/1e3),r=Math.round(s/60),a=l.toDateString()===t.toDateString(),f=i.toDateString()===t.toDateString(),o=l.getFullYear()===t.getFullYear();return s<5?"Just Now":s<60?`${s} Seconds ago`:s<90?"A minute ago":r<60?`${r} Minutes ago`:a?we(t,"Today"):f?we(t,"Yesterday"):o?we(t,!1,!0):we(t)}function nt(e,t,n){const l=e.slice();return l[21]=t[n],l}function lt(e,t,n){const l=e.slice();return l[24]=t[n],l}function it(e,t,n){const l=e.slice();return l[27]=t[n],l}function rt(e){let t,n,l=e[6],i=[];for(let r=0;rV(i[r],1,1,()=>{i[r]=null});return{c(){for(let r=0;r0&&ft(e);function U(v,I){return I&320&&(p=null),p==null&&(p=!!Pt(v[21].units,v[8].citizenid)),p?In:An}let D=U(e,-1),k=D(e);function h(){return e[19](e[21])}return{c(){t=g("div"),w&&w.c(),n=M(),l=g("button"),i=g("p"),r=j(s),a=M(),o=j(f),c=M(),m=g("p"),k.c(),N=M(),_(i,"class",u="mx-[2vh] px-[2vh] py-[0.2vh] rounded-full "+(e[21].priority==1?" bg-accent_dark_red":" bg-accent_dark_green")),_(m,"class","ml-[3vh]"),_(l,"class",b="w-full h-[5vh] "+(e[21].priority==1?" bg-priority_quaternary":" bg-accent_green")+" flex items-center font-medium"),_(t,"class","mb-[1vh]")},m(v,I){L(v,t,I),w&&w.m(t,null),d(t,n),d(t,l),d(l,i),d(i,r),d(i,a),d(i,o),d(l,c),d(l,m),k.m(m,null),d(t,N),y=!0,T||(E=Q(l,"click",h),T=!0)},p(v,I){e=v,e[21].units.length>0?w?w.p(e,I):(w=ft(e),w.c(),w.m(t,n)):w&&(w.d(1),w=null),(!y||I&64)&&s!==(s=e[21].units.length+"")&&B(r,s),(!y||I&128)&&f!==(f=e[7].units+"")&&B(o,f),(!y||I&64&&u!==(u="mx-[2vh] px-[2vh] py-[0.2vh] rounded-full "+(e[21].priority==1?" bg-accent_dark_red":" bg-accent_dark_green")))&&_(i,"class",u),D===(D=U(e,I))&&k?k.p(e,I):(k.d(1),k=D(e),k&&(k.c(),k.m(m,null))),(!y||I&64&&b!==(b="w-full h-[5vh] "+(e[21].priority==1?" bg-priority_quaternary":" bg-accent_green")+" flex items-center font-medium"))&&_(l,"class",b)},i(v){y||(te(()=>{y&&($||($=ce(t,tt,{duration:300},!0)),$.run(1))}),y=!0)},o(v){$||($=ce(t,tt,{duration:300},!1)),$.run(0),y=!1},d(v){v&&S(t),w&&w.d(),k.d(),v&&$&&$.end(),T=!1,E()}}}function ft(e){let t,n,l=e[21].units.slice(0,e[1][e[21].id]?e[21].units.length:3),i=[];for(let r=0;r3&&ut(e);return{c(){t=g("div");for(let r=0;r3?s?s.p(r,a):(s=ut(r),s.c(),s.m(t,null)):s&&(s.d(1),s=null)},d(r){r&&S(t),_e(i,r),s&&s.d()}}}function ct(e){let t,n,l=e[24].metadata.callsign+"",i,s,r,a,f=e[24].job.name+"",o,u,c,m,p=e[24].charinfo.firstname+"",b,N,$=e[24].charinfo.lastname+"",y,T;return{c(){t=g("div"),n=g("p"),i=j(l),r=M(),a=g("p"),o=j(f),c=M(),m=g("p"),b=j(p),N=M(),y=j($),_(n,"class",s="ml-[2vh] px-[1.4vh] py-[0.2vh] rounded-full "+(e[21].priority==1?"bg-priority_secondary":"bg-secondary")),_(a,"class",u="mx-[1vh] px-[1.5vh] py-[0.2vh] rounded-full uppercase "+(e[24].job.type=="leo"?"bg-[#004ca5] ":e[24].job.type=="ems"?"bg-[#e03535]":"bg-[#4b4b4b]")),_(m,"class","ml-[0.5vh]"),_(t,"class",T="w-full h-[5vh] flex "+(e[21].priority==1?"bg-priority_tertiary":"bg-tertiary")+" flex items-center font-medium")},m(E,w){L(E,t,w),d(t,n),d(n,i),d(t,r),d(t,a),d(a,o),d(t,c),d(t,m),d(m,b),d(m,N),d(m,y)},p(E,w){w&66&&l!==(l=E[24].metadata.callsign+"")&&B(i,l),w&64&&s!==(s="ml-[2vh] px-[1.4vh] py-[0.2vh] rounded-full "+(E[21].priority==1?"bg-priority_secondary":"bg-secondary"))&&_(n,"class",s),w&66&&f!==(f=E[24].job.name+"")&&B(o,f),w&66&&u!==(u="mx-[1vh] px-[1.5vh] py-[0.2vh] rounded-full uppercase "+(E[24].job.type=="leo"?"bg-[#004ca5] ":E[24].job.type=="ems"?"bg-[#e03535]":"bg-[#4b4b4b]"))&&_(a,"class",u),w&66&&p!==(p=E[24].charinfo.firstname+"")&&B(b,p),w&66&&$!==($=E[24].charinfo.lastname+"")&&B(y,$),w&64&&T!==(T="w-full h-[5vh] flex "+(E[21].priority==1?"bg-priority_tertiary":"bg-tertiary")+" flex items-center font-medium")&&_(t,"class",T)},d(E){E&&S(t)}}}function ut(e){let t,n=!e[1][e[21].id]&&dt(e);return{c(){n&&n.c(),t=x()},m(l,i){n&&n.m(l,i),L(l,t,i)},p(l,i){l[1][l[21].id]?n&&(n.d(1),n=null):n?n.p(l,i):(n=dt(l),n.c(),n.m(t.parentNode,t))},d(l){n&&n.d(l),l&&S(t)}}}function dt(e){let t,n,l,i=pt(e[21])+"",s,r,a=e[7].additionals+"",f,o,u,c;function m(){return e[18](e[21])}return{c(){t=g("button"),n=g("p"),l=j("+"),s=j(i),r=M(),f=j(a),_(n,"class","ml-[0.5vh]"),_(t,"class",o="w-full h-[5vh] flex items-center justify-center "+(e[21].priority==1?"bg-priority_tertiary":"bg-tertiary")+" flex items-center font-medium")},m(p,b){L(p,t,b),d(t,n),d(n,l),d(n,s),d(n,r),d(n,f),u||(c=Q(t,"click",m),u=!0)},p(p,b){e=p,b&64&&i!==(i=pt(e[21])+"")&&B(s,i),b&128&&a!==(a=e[7].additionals+"")&&B(f,a),b&64&&o!==(o="w-full h-[5vh] flex items-center justify-center "+(e[21].priority==1?"bg-priority_tertiary":"bg-tertiary")+" flex items-center font-medium")&&_(t,"class",o)},d(p){p&&S(t),u=!1,c()}}}function An(e){let t=e[7].dispatch_attach+"",n;return{c(){n=j(t)},m(l,i){L(l,n,i)},p(l,i){i&128&&t!==(t=l[7].dispatch_attach+"")&&B(n,t)},d(l){l&&S(n)}}}function In(e){let t=e[7].dispatch_detach+"",n;return{c(){n=j(t)},m(l,i){L(l,n,i)},p(l,i){i&128&&t!==(t=l[7].dispatch_detach+"")&&B(n,t)},d(l){l&&S(n)}}}function _t(e){let t,n,l,i,s=e[21].id+"",r,a,f,o=e[21].code+"",u,c,m,p,b=e[21].message+"",N,$,y,T,E,w,U,D,k,h,v,I,P,G=e[14](e[21]),O=[];for(let C=0;C{A=null}),le())},i(C){v||(F(A),v=!0)},o(C){V(A),v=!1},d(C){C&&S(t),_e(O,C),C&&S(k),A&&A.d(C),C&&S(h),I=!1,P()}}}function Cn(e){let t,n,l,i,s,r,a,f,o,u,c,m,p,b,N,$,y,T,E,w,U,D,k,h,v=e[5]&&rt(e);return{c(){t=g("div"),n=g("div"),l=g("button"),l.innerHTML=' ',i=M(),s=g("button"),r=g("i"),f=M(),o=g("button"),u=g("i"),m=M(),p=g("button"),p.innerHTML=' ',b=M(),N=g("button"),$=g("i"),T=M(),E=g("div"),v&&v.c(),_(l,"class","w-full h-[3vh] flex items-center justify-center bg-primary hover:bg-secondary"),_(r,"class",a="fas fa-volume-"+(e[4]?"xmark":"high")+" text-[1.5vh]"),_(s,"class","w-full h-[3vh] flex items-center justify-center bg-primary hover:bg-secondary"),_(u,"class",c="fas fa-"+(e[3]?"bell-slash":"bell")+" text-[1.5vh]"),_(o,"class","w-full h-[3vh] flex items-center justify-center bg-primary hover:bg-secondary"),_(p,"class","w-full h-[3vh] flex items-center justify-center bg-primary hover:bg-secondary"),_($,"class",y="fas fa-"+(e[2]?"hand-point-left":"hand-point-right")+" text-[1.5vh]"),_(N,"class","w-full h-[3vh] flex items-center justify-center bg-primary hover:bg-secondary"),_(n,"class","w-[3.2vh] h-[85%] flex flex-col gap-[1vh]"),J(n,"ml-[1vh]",!e[2]),J(n,"mr-[1vh]",e[2]),_(E,"class","w-[25%] h-[97%] overflow-auto pr-[0.5vh]"),J(E,"ml-[2vh]",!e[2]),J(E,"mr-[2vh]",e[2]),_(t,"class",w="w-screen h-screen flex items-center justify-end "+(e[2]?"flex-row":"flex-row-reverse"))},m(I,P){L(I,t,P),d(t,n),d(n,l),d(n,i),d(n,s),d(s,r),d(n,f),d(n,o),d(o,u),d(n,m),d(n,p),d(n,b),d(n,N),d(N,$),d(t,T),d(t,E),v&&v.m(E,null),D=!0,k||(h=[Q(l,"click",e[15]),Q(s,"click",e[12]),Q(o,"click",e[13]),Q(p,"click",e[16]),Q(N,"click",e[11])],k=!0)},p(I,[P]){e=I,(!D||P&16&&a!==(a="fas fa-volume-"+(e[4]?"xmark":"high")+" text-[1.5vh]"))&&_(r,"class",a),(!D||P&8&&c!==(c="fas fa-"+(e[3]?"bell-slash":"bell")+" text-[1.5vh]"))&&_(u,"class",c),(!D||P&4&&y!==(y="fas fa-"+(e[2]?"hand-point-left":"hand-point-right")+" text-[1.5vh]"))&&_($,"class",y),(!D||P&4)&&J(n,"ml-[1vh]",!e[2]),(!D||P&4)&&J(n,"mr-[1vh]",e[2]),e[5]?v?(v.p(e,P),P&32&&F(v,1)):(v=rt(e),v.c(),F(v,1),v.m(E,null)):v&&(ne(),V(v,1,1,()=>{v=null}),le()),(!D||P&4)&&J(E,"ml-[2vh]",!e[2]),(!D||P&4)&&J(E,"mr-[2vh]",e[2]),(!D||P&4&&w!==(w="w-screen h-screen flex items-center justify-end "+(e[2]?"flex-row":"flex-row-reverse")))&&_(t,"class",w)},i(I){D||(F(v),te(()=>{D&&(U||(U=ce(t,Ne,{x:e[2]?400:-400},!0)),U.run(1))}),D=!0)},o(I){V(v),U||(U=ce(t,Ne,{x:e[2]?400:-400},!1)),U.run(0),D=!1},d(I){I&&S(t),v&&v.d(),I&&U&&U.end(),k=!1,Z(h)}}}function Pt(e,t){for(let n=0;nn(3,i=h)),z(e,qe,h=>n(4,s=h)),z(e,Be,h=>n(5,r=h)),z(e,vn,h=>n(6,a=h)),z(e,Pe,h=>n(7,f=h)),z(e,Ue,h=>n(8,o=h));let u=null,c={},m;Me(()=>{m=Ot.subscribe(h=>{n(2,l=h)})}),Ct(()=>{m()});function p(h){u===h?n(0,u=null):n(0,u=h)}function b(h){n(1,c[h]=!c[h],c)}function N(){n(2,l=!l)}function $(){qe.update(h=>!h),K("toggleMute",{boolean:s})}function y(){Je.update(h=>!h),K("toggleAlerts",{boolean:i})}function T(h){return[{icon:"fas fa-clock",label:"Time",value:Bt(h.time)},{icon:"fas fa-user",label:"Name",value:h.name},{icon:"fas fa-phone",label:"Number",value:h.number},{icon:"fas fa-comment",label:"Information",value:h.information},{icon:"fas fa-map-location-dot",label:"Street",value:h.street},{icon:"fas fa-user",label:"Gender",value:h.gender},{icon:"fas fa-gun",label:"Automatic Gun Fire",value:h.automaticGunFire},{icon:"fas fa-gun",label:"Weapon",value:h.weapon},{icon:"fas fa-car",label:"Vehicle",value:h.vehicle},{icon:"fas fa-rectangle-list",label:"Plate",value:h.plate},{icon:"fas fa-droplet",label:"Color",value:h.color},{icon:"fas fa-car",label:"Class",value:h.class},{icon:"fas fa-door-open",label:"Doors",value:h.doors},{icon:"fas fa-compass",label:"Heading",value:h.heading},{icon:"fas fa-user-group",label:"Units",value:h.units.length}]}const E=()=>{K("refreshAlerts")},w=()=>{K("clearBlips")},U=h=>p(h.id),D=h=>b(h.id),k=h=>{Pt(h.units,o.citizenid)?(K("detachUnit",h),K("refreshAlerts")):(K("attachUnit",h),K("refreshAlerts"))};return n(2,l=!1),[u,c,l,i,s,r,a,f,o,p,b,N,$,y,T,E,w,U,D,k]}class Ln extends he{constructor(t){super(),pe(this,t,jn,Cn,ie,{})}}function ht(e,t,n){const l=e.slice();return l[6]=t[n],l[8]=n,l}function mt(e,t,n){const l=e.slice();return l[9]=t[n],l}function bt(e){let t,n=e[3](e[6]),l=[];for(let i=0;i{O&&(G||(G=ce(n,Ne,{x:t[1]?400:-400},!0)),G.run(1))}),O=!0)},o(C){G||(G=ce(n,Ne,{x:t[1]?400:-400},!1)),G.run(0),O=!1},d(C){C&&S(n),Y&&Y.d(),A&&A.d(),C&&G&&G.end()}}}function Tn(e){let t,n,l=[],i=new Map,s,r,a=e[0].slice().reverse();const f=o=>o[6].data.id;for(let o=0;on(4,l=o)),z(e,Ot,o=>n(1,i=o)),z(e,Tt,o=>n(2,s=o));let r=[];Fe.subscribe(o=>{n(0,r=o||[])});function a(o){bn(o)}Me(()=>{r.forEach(o=>{const{data:u,timer:c}=o;setTimeout(()=>{a(u.id)},c)})}),rn(()=>{r.forEach(o=>{const{data:u,timer:c}=o;setTimeout(()=>{a(u.id)},c)})});function f(o){return l?[{label:"Call",value:o.data.message},{icon:"fas fa-comment",label:"Information",value:o.data.information}]:[{icon:"fas fa-clock",label:"Time",value:Bt(o.data.time)},{icon:"fas fa-user",label:"Name",value:o.data.name},{icon:"fas fa-phone",label:"Number",value:o.data.number},{icon:"fas fa-comment",label:"Information",value:o.data.information},{icon:"fas fa-map-location-dot",label:"Street",value:o.data.street},{icon:"fas fa-user",label:"Gender",value:o.data.gender},{icon:"fas fa-gun",label:"Automatic Gun Fire",value:o.data.automaticGunFire},{icon:"fas fa-gun",label:"Weapon",value:o.data.weapon},{icon:"fas fa-car",label:"Vehicle",value:o.data.vehicle},{icon:"fas fa-rectangle-list",label:"Plate",value:o.data.plate},{icon:"fas fa-droplet",label:"Color",value:o.data.color},{icon:"fas fa-car",label:"Class",value:o.data.class},{icon:"fas fa-door-open",label:"Doors",value:o.data.doors},{icon:"fas fa-compass",label:"Heading",value:o.data.heading}]}return[r,i,s,f]}class Rn extends he{constructor(t){super(),pe(this,t,On,Tn,ie,{})}}function $t(e){let t,n,l,i;return t=new wn({props:{$$slots:{default:[Un]},$$scope:{ctx:e}}}),l=new Rn({}),{c(){ye(t.$$.fragment),n=M(),ye(l.$$.fragment)},m(s,r){ue(t,s,r),L(s,n,r),ue(l,s,r),i=!0},i(s){i||(F(t.$$.fragment,s),F(l.$$.fragment,s),i=!0)},o(s){V(t.$$.fragment,s),V(l.$$.fragment,s),i=!1},d(s){de(t,s),s&&S(n),de(l,s)}}}function Un(e){let t,n;return t=new Ln({}),{c(){ye(t.$$.fragment)},m(l,i){ue(t,l,i),n=!0},i(l){n||(F(t.$$.fragment,l),n=!0)},o(l){V(t.$$.fragment,l),n=!1},d(l){de(t,l)}}}function kt(e){let t,n,l,i;return t=new Nn({}),{c(){ye(t.$$.fragment),n=M(),l=g("body"),_(l,"class","bg-neutral-700")},m(s,r){ue(t,s,r),L(s,n,r),L(s,l,r),i=!0},i(s){i||(F(t.$$.fragment,s),i=!0)},o(s){V(t.$$.fragment,s),i=!1},d(s){de(t,s),s&&S(n),s&&S(l)}}}function Fn(e){let t,n,l,i,s,r=e[0]&&$t(e);n=new Mn({});let a=e[1]&&kt();return{c(){r&&r.c(),t=M(),ye(n.$$.fragment),l=M(),a&&a.c(),i=x()},m(f,o){r&&r.m(f,o),L(f,t,o),ue(n,f,o),L(f,l,o),a&&a.m(f,o),L(f,i,o),s=!0},p(f,[o]){f[0]?r?o&1&&F(r,1):(r=$t(f),r.c(),F(r,1),r.m(t.parentNode,t)):r&&(ne(),V(r,1,1,()=>{r=null}),le()),f[1]?a?o&2&&F(a,1):(a=kt(),a.c(),F(a,1),a.m(i.parentNode,i)):a&&(ne(),V(a,1,1,()=>{a=null}),le())},i(f){s||(F(r),F(n.$$.fragment,f),F(a),s=!0)},o(f){V(r),V(n.$$.fragment,f),V(a),s=!1},d(f){r&&r.d(f),f&&S(t),de(n,f),f&&S(l),a&&a.d(f),f&&S(i)}}}function Bn(e,t,n){let l,i,s;return z(e,je,r=>n(2,l=r)),z(e,Pe,r=>n(0,i=r)),z(e,Se,r=>n(1,s=r)),Mt(je,l="ps-dispatch",l),[i,s]}class Pn extends he{constructor(t){super(),pe(this,t,Bn,Fn,ie,{})}}new Pn({target:document.getElementById("app")});
7 |
--------------------------------------------------------------------------------
/locales/cs.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Odpojení od dispečerského volání",
3 | "dispatch_attach": "Připojení k dispečerskému volání",
4 | "unit": "Jednotka",
5 | "units": "Jednotky",
6 | "additionals": "Dodatečné jednotky",
7 |
8 | "open_dispatch": "Otevřít Dispatch",
9 | "911_help": "Pošle zprávu policistům.",
10 | "911a_help": "Pošle anonymní zprávu policistům.",
11 | "311_help": "Pošle zprávu doktorům.",
12 | "311a_help": "Pošle anonymní zprávu doktorům.",
13 |
14 | "no_calls": "Nebyly nalezeny žádné zprávy",
15 | "alerts": "Alerty ",
16 | "blips_cleared": "Blipy Vyčištěny",
17 | "alerts_refreshed": "Obnovená upozornění",
18 | "enabled": "Zapnuto",
19 | "disabled": "Vypnuto",
20 | "muted": "Ztlumení",
21 | "unmuted": "Zrušit ztlumení",
22 | "waypoint_set": "Nastavit Wayipont.",
23 |
24 | "caller_local": "Místní",
25 | "call_from": "Zpráva od ",
26 | "two_door": "Dvoudveřové ",
27 | "three_door": "Trojdveřové ",
28 | "four_door": "Čtyřdveřové ",
29 | "compact": "Compact",
30 | "sedan": "Sedan",
31 | "suv": "SUV",
32 | "coupe": "Coupe",
33 | "muscle": "Muscle car",
34 | "sports_classic": "Sports classic",
35 | "sports": "Sports car",
36 | "super": "Super car",
37 | "motorcycle": "Motorka",
38 | "offroad": "Off-road vozidlo",
39 | "industrial": "Industrial vozidlo",
40 | "utility": "Utility vozidlo",
41 | "van": "Van",
42 | "service": "Service vozidlo",
43 | "military": "Vojenské vozidlo",
44 | "truck": "Kamion",
45 | "north": "North Bound",
46 | "east": "East Bound",
47 | "south": "South Bound",
48 | "west": "West Bound",
49 | "male": "Male",
50 | "female": "Female",
51 |
52 | "anon_call": "Příchozí Anonnymní Zpráva",
53 | "anon": "Anononym",
54 | "hidden_number": "Skryté Číslo",
55 | "call": "Příchozí Zpráva",
56 | "vehicleshots": "Střelba z Vozidla",
57 | "shooting": "Střelba ze zbraně",
58 | "melee": "Bitka",
59 | "driveby": "Střelba z auta",
60 | "speeding": "Bezohledná jízda",
61 | "autotheft": "Krádež vozidla",
62 | "persondown": "Zraněná osoba",
63 | "civbled": "Civilian Bled Out",
64 | "storerobbery": "Vykrádání Obchodu",
65 | "fleecabank": "Přepadení banky",
66 | "paletobank": "Přepadení banky",
67 | "pacificbank": "Přepadení banky",
68 | "bobcatsecurity": "Bobcat Bezpečnostní loupež",
69 | "prisonbreak": "Probíhá útěk z vězení",
70 | "vangelico": "Loupežné přepadení klenotnictvý",
71 | "houserobbery": "Vykrádání Domu",
72 | "drugsell": "Podezřelé předání",
73 | "carjacking": "Vykrádání vozidla",
74 | "vehicletheft": "Krádež vozidla",
75 | "officerdown": "Policista postřelen",
76 | "officerdistress": "Policista v nouzi",
77 | "officerbackup": "Policista potřebuje zálohu",
78 | "emsdown": "EMS Down",
79 | "artgalleryrobbery": "Loupež v umělecké galerie",
80 | "humanelabsrobbery": "Loupež v laboratořích Humane Labs",
81 | "trainrobbery": "Přepadení vlaku",
82 | "vanrobbery": "Přepadení bezpečnostní dodávky",
83 | "underground": "Přepadení bunkru",
84 | "drugboatrobbery": "podezřelá loď",
85 | "unionrobbery": "Loupež v Union Depository",
86 | "carboosting": "Car Boosting Probíhá",
87 | "yachtheist": "Probíhá loupež jachty",
88 | "susactivity": "Podezřelá aktivita",
89 | "hunting": "Možné porušení lovu",
90 | "explosion": "Hlášena exploze",
91 |
92 | "justnow": "práve teď",
93 | "minute": "před minutamy",
94 | "hour": "hodiny zpátky",
95 | "day": "dny zpátky"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/de.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Vom Einsatz ablösen",
3 | "dispatch_attach": "Zu Einsatz zuordnen",
4 | "unit": "Einheit",
5 | "units": "Einheiten",
6 | "additionals": "Weitere Einheiten",
7 |
8 | "open_dispatch": "Dispatch-Menü öffnen",
9 | "911_help": "Eine Nachricht an die Polizei senden.",
10 | "911a_help": "Sende eine anonyme Nachricht an die Polizei.",
11 | "311_help": "Eine Nachricht an den Rettungsdienst senden.",
12 | "311a_help": "Sende eine anonyme Nachricht an den Rettungsdienst.",
13 |
14 | "no_calls": "Keine Einsätze gefunden.",
15 | "alerts": "Meldungen ",
16 | "blips_cleared": "Blips entfernt",
17 | "alerts_refreshed": "Meldungen aktualisiert",
18 | "enabled": "Aktiviert",
19 | "disabled": "Deaktiviert",
20 | "muted": "Stummgeschaltet",
21 | "unmuted": "Entstummt",
22 | "waypoint_set": "Wegpunkt gesetzt.",
23 |
24 | "caller_local": "Einheimische/r",
25 | "call_from": "Anruf von ",
26 | "two_door": "Zweitürig",
27 | "three_door": "Dreitürig",
28 | "four_door": "Viertürig",
29 | "compact": "Kompaktwagen",
30 | "sedan": "Limousine",
31 | "suv": "SUV",
32 | "coupe": "Coupé",
33 | "muscle": "Muscle Car",
34 | "sports_classic": "Sportklassiker",
35 | "sports": "Sportwagen",
36 | "super": "Supersportwagen",
37 | "motorcycle": "Motorrad",
38 | "offroad": "Geländefahrzeug",
39 | "industrial": "Industriefahrzeug",
40 | "utility": "Nutzfahrzeug",
41 | "van": "Lieferwagen",
42 | "service": "Servicefahrzeug",
43 | "military": "Militärfahrzeug",
44 | "truck": "LKW",
45 | "north": "Nördlich",
46 | "east": "Östlich",
47 | "south": "Südlich",
48 | "west": "Westlich",
49 | "male": "Mann",
50 | "female": "Frau",
51 |
52 | "anon_call": "Anonymer eingehender Anruf",
53 | "anon": "Anonym",
54 | "hidden_number": "Versteckte Nummer",
55 | "call": "Eingehender Anruf",
56 | "vehicleshots": "Schüsse aus einem Fahrzeug",
57 | "shooting": "Schießerei",
58 | "melee": "Schlägerei im Gange",
59 | "driveby": "Drive-by im Gange",
60 | "speeding": "Rücksichtsloses Fahren",
61 | "autotheft": "Diebstahl eines Kraftfahrzeugs",
62 | "persondown": "Person verletzt",
63 | "civbled": "Zivilist verblutet",
64 | "storerobbery": "Ladenraub",
65 | "fleecabank": "Fleeca-Bankraub",
66 | "paletobank": "Paleto-Bankraub",
67 | "pacificbank": "Pacific-Bankraub",
68 | "bobcatsecurity": "Bobcat Security Raubüberfall",
69 | "prisonbreak": "Gefängnisausbruch im Gange",
70 | "vangelico": "Vangelico-Raubüberfall",
71 | "houserobbery": "Hauseinbruch",
72 | "drugsell": "Verdächtige Übergabe",
73 | "carjacking": "Autodiebstahl",
74 | "vehicletheft": "Fahrzeugdiebstahl",
75 | "officerdown": "Officer verletzt",
76 | "officerdistress": "Officer in Not",
77 | "officerbackup": "Officer braucht Verstärkung",
78 | "emsdown": "Rettungsdienst verletzt",
79 | "artgalleryrobbery": "Kunstgalerie-Raubüberfall",
80 | "humanelabsrobbery": "Humane Labs-Raubüberfall",
81 | "trainrobbery": "Zugraubüberfall",
82 | "vanrobbery": "Sicherheitswagen-Raubüberfall",
83 | "underground": "Bunker-Raubüberfall",
84 | "drugboatrobbery": "Verdächtiges Boot",
85 | "unionrobbery": "Union Depository-Raubüberfall",
86 | "carboosting": "Auto-Boosting im Gange",
87 | "yachtheist": "Yacht-Raubüberfall im Gange",
88 | "susactivity": "Verdächtige Aktivität",
89 | "hunting": "Mögliche Jagdverletzung",
90 | "explosion": "Explosion gemeldet",
91 |
92 | "justnow": "gerade eben",
93 | "minute": "vor Minuten",
94 | "hour": "vor Stunden",
95 | "day": "vor Tagen"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Detach from Dispatch Call",
3 | "dispatch_attach": "Attach to Dispatch Call",
4 | "unit": "Unit",
5 | "units": "Units",
6 | "additionals": "Additional Units",
7 |
8 | "open_dispatch": "Open Dispatch Menu",
9 | "911_help": "Send a message to the police.",
10 | "911a_help": "Send an anonymous message to the police.",
11 | "311_help": "Send a message to the EMS.",
12 | "311a_help": "Send an anonymous message to the EMS.",
13 |
14 | "no_calls": "No Calls Found.",
15 | "alerts": "Alerts ",
16 | "blips_cleared": "Blips Cleared",
17 | "alerts_refreshed": "Alerts Refreshed",
18 | "enabled": "Enabled",
19 | "disabled": "Disabled",
20 | "muted": "Muted",
21 | "unmuted": "Unmuted",
22 | "waypoint_set": "Waypoint set.",
23 |
24 | "caller_local": "Local",
25 | "call_from": "Call from ",
26 | "two_door": "Two-door ",
27 | "three_door": "Three-door ",
28 | "four_door": "Four-door ",
29 | "compact": "Compact",
30 | "sedan": "Sedan",
31 | "suv": "SUV",
32 | "coupe": "Coupe",
33 | "muscle": "Muscle car",
34 | "sports_classic": "Sports classic",
35 | "sports": "Sports car",
36 | "super": "Super car",
37 | "motorcycle": "Motorcycle",
38 | "offroad": "Off-road vehicle",
39 | "industrial": "Industrial vehicle",
40 | "utility": "Utility vehicle",
41 | "van": "Van",
42 | "service": "Service vehicle",
43 | "military": "Military vehicle",
44 | "truck": "Truck",
45 | "north": "North Bound",
46 | "east": "East Bound",
47 | "south": "South Bound",
48 | "west": "West Bound",
49 | "male": "Male",
50 | "female": "Female",
51 |
52 | "anon_call": "Incoming Anonymous Call",
53 | "anon": "Anonymous",
54 | "hidden_number": "Hidden Number",
55 | "call": "Incoming Call",
56 | "vehicleshots": "Shots Fired from Vehicle",
57 | "shooting": "Discharge of a firearm",
58 | "melee": "Fight in progress",
59 | "driveby": "Drive-by shooting",
60 | "speeding": "Reckless driving",
61 | "autotheft": "Theft of a motor vehicle",
62 | "persondown": "Person is injured",
63 | "civbled": "Civilian Bled Out",
64 | "storerobbery": "Store Robbery",
65 | "fleecabank": "Fleeca Bank Robbery",
66 | "paletobank": "Paleto Bank Robbery",
67 | "pacificbank": "Pacific Bank Robbery",
68 | "bobcatsecurity": "Bobcat Security Heist",
69 | "prisonbreak": "Prison Break In Progress",
70 | "vangelico": "Vangelico Robbery",
71 | "houserobbery": "House Robbery",
72 | "drugsell": "Suspicious Handoff",
73 | "carjacking": "Car Jacking",
74 | "vehicletheft": "Vehicle Theft",
75 | "officerdown": "Officer is down",
76 | "officerdistress": "Officer in distress",
77 | "officerbackup": "Officer needs backup",
78 | "emsdown": "EMS Down",
79 | "artgalleryrobbery": "Art Gallery Robbery",
80 | "humanelabsrobbery": "Humane Labs Robbery",
81 | "trainrobbery": "Train Robbery",
82 | "vanrobbery": "Security Van Robbery",
83 | "underground": "Bunker Robbery",
84 | "drugboatrobbery": "Suspicious Boat",
85 | "unionrobbery": "Union Depository Robbery",
86 | "carboosting": "Car Boosting In Progress",
87 | "yachtheist": "Yacht Heist In Progress",
88 | "susactivity": "Suspicious Activity",
89 | "hunting": "Possible Hunting Violation",
90 | "explosion": "Explosion Reported",
91 |
92 | "justnow": "just now",
93 | "minute": "minutes ago",
94 | "hour": "hours ago",
95 | "day": "days ago"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/es.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Desasignar de la Llamada Dispatch",
3 | "dispatch_attach": "Asignar a la Llamada Dispatch",
4 | "unit": "Unidad",
5 | "units": "Unidades",
6 | "additionals": "Unidades Adicionales",
7 |
8 | "open_dispatch": "Abrir Menú Dispatch",
9 | "911_help": "Enviar un mensaje a la policía.",
10 | "911a_help": "Enviar un mensaje anónimo a la policía.",
11 | "311_help": "Enviar un mensaje al servicio de emergencias.",
12 | "311a_help": "Enviar un mensaje anónimo al servicio de emergencias.",
13 |
14 | "no_calls": "No se encontraron llamadas.",
15 | "alerts": "Alertas ",
16 | "blips_cleared": "Marcadores Eliminados",
17 | "alerts_refreshed": "Alertas Actualizadas",
18 | "enabled": "Habilitado",
19 | "disabled": "Deshabilitado",
20 | "muted": "Silenciado",
21 | "unmuted": "No silenciado",
22 | "waypoint_set": "Establecido punto de ruta.",
23 |
24 | "caller_local": "Local",
25 | "call_from": "Llamada de ",
26 | "two_door": "Vehículo de dos puertas ",
27 | "three_door": "Vehículo de tres puertas ",
28 | "four_door": "Vehículo de cuatro puertas ",
29 | "compact": "Compacto",
30 | "sedan": "Sedán",
31 | "suv": "SUV",
32 | "coupe": "Coupé",
33 | "muscle": "Muscle",
34 | "sports_classic": "Deportivo Clásico",
35 | "sports": "Deportivo",
36 | "super": "Superdeportivo",
37 | "motorcycle": "Motocicleta",
38 | "offroad": "Todoterreno",
39 | "industrial": "Vehículo industrial",
40 | "utility": "Vehículo utilitario",
41 | "van": "Furgoneta",
42 | "service": "Vehículo de servicio",
43 | "military": "Vehículo militar",
44 | "truck": "Camión",
45 | "north": "Dirección norte",
46 | "east": "Dirección este",
47 | "south": "Dirección sur",
48 | "west": "Dirección oeste",
49 | "male": "Masculina",
50 | "female": "Femenina",
51 |
52 | "anon_call": "Llamada Anónima Entrante",
53 | "anon": "Anónimo",
54 | "hidden_number": "Número Oculto",
55 | "call": "Llamada Entrante",
56 | "vehicleshots": "Disparos desde un vehículo",
57 | "shooting": "Disparo de arma de fuego",
58 | "melee": "Pelea informada",
59 | "driveby": "Tiroteo desde un vehículo en movimiento",
60 | "speeding": "Conducción temeraria",
61 | "autotheft": "Robo de un vehículo automotor",
62 | "persondown": "Persona herida",
63 | "civbled": "Civil herido",
64 | "storerobbery": "Robo en una tienda",
65 | "fleecabank": "Robo a Banco Fleeca",
66 | "paletobank": "Robo a Banco de Paleto",
67 | "pacificbank": "Robo a Banco Pacific",
68 | "bobcatsecurity": "Robo en Bobcat Security",
69 | "prisonbreak": "Intento de fuga en prisión",
70 | "vangelico": "Robo a Vangelico",
71 | "houserobbery": "Robo en una casa",
72 | "drugsell": "Entrega sospechosa",
73 | "carjacking": "Robo de vehículo",
74 | "vehicletheft": "Robo de vehículo",
75 | "officerdown": "Oficial caído",
76 | "officerdistress": "Oficial en peligro",
77 | "officerbackup": "oficial necesita refuerzos",
78 | "emsdown": "Servicios Médicos de Emergencia caídos",
79 | "artgalleryrobbery": "Robo en una galería de arte",
80 | "humanelabsrobbery": "Robo en Laboratorios Humane",
81 | "trainrobbery": "Robo de tren",
82 | "vanrobbery": "Robo de furgoneta blindada",
83 | "underground": "Robo en un búnker",
84 | "drugboatrobbery": "Embarcación sospechosa",
85 | "unionrobbery": "Robo en la Depositaría Union",
86 | "carboosting": "Robo de vehículo",
87 | "yachtheist": "Robo en yate",
88 | "susactivity": "Actividad sospechosa",
89 | "hunting": "Posible violación de caza",
90 | "explosion": "Explosión informada",
91 |
92 | "justnow": "justo ahora",
93 | "minute": "minutos atrás",
94 | "hour": "horas atrás",
95 | "day": "días atrás"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Détacher de l'appel du dispatch",
3 | "dispatch_attach": "Attacher à l'appel du dispatch",
4 | "unit": "Unité",
5 | "units": "Unités",
6 | "additionals": "Unités additionnelles",
7 |
8 | "open_dispatch": "Ouvrir le menu du dispatch",
9 | "911_help": "Envoyer un message à la police.",
10 | "911a_help": "Envoyer un message anonyme à la police.",
11 | "311_help": "Envoyer un message au service médical d'urgence (EMS).",
12 | "311a_help": "Envoyer un message anonyme au service médical d'urgence (EMS).",
13 |
14 | "no_calls": "Aucun appel trouvé.",
15 | "alerts": "Alertes",
16 | "blips_cleared": "Marqueurs effacés",
17 | "alerts_refreshed": "Alertes rafraîchies",
18 | "enabled": "Activé",
19 | "disabled": "Désactivé",
20 | "muted": "Muet",
21 | "unmuted": "Non muet",
22 | "waypoint_set": "Waypoint défini.",
23 |
24 | "caller_local": "Local",
25 | "call_from": "Appel de",
26 | "two_door": "Véhicule 2 portes",
27 | "three_door": "Véhicule 3 portes",
28 | "four_door": "Véhicule 4 portes",
29 | "compact": "Véhicule compact",
30 | "sedan": "Berline",
31 | "suv": "SUV",
32 | "coupe": "Coupé",
33 | "muscle": "Grosse cylindrée",
34 | "sports_classic": "Voiture de sport classique",
35 | "sports": "Voiture de sport",
36 | "super": "Supercar",
37 | "motorcycle": "Motos",
38 | "offroad": "Véhicule tout-terrain",
39 | "industrial": "Véhicule industriel",
40 | "utility": "Véhicule utilitaire",
41 | "van": "Van",
42 | "service": "Véhicule de service",
43 | "military": "Véhicule militaire",
44 | "truck": "Camion",
45 | "north": "Direction Nord",
46 | "east": "Direction Est",
47 | "south": "Direction Sud",
48 | "west": "Direction Ouest",
49 | "male": "Homme",
50 | "female": "Femme",
51 |
52 | "anon_call": "Appel anonyme entrant",
53 | "anon": "Anonyme",
54 | "hidden_number": "Numéro masqué",
55 | "call": "Appel entrant",
56 | "vehicleshots": "Coups de feu depuis un véhicule",
57 | "shooting": "Fusillade",
58 | "melee": "Bagarre en cours",
59 | "driveby": "Tir depuis un véhicule en mouvement",
60 | "speeding": "Conduite imprudente",
61 | "autotheft": "Vol de véhicule",
62 | "persondown": "Personne blessée",
63 | "civbled": "Civil blessé",
64 | "storerobbery": "Braquage de magasin",
65 | "fleecabank": "Braquage de la banque Fleeca",
66 | "paletobank": "Braquage de la banque Paleto",
67 | "pacificbank": "Braquage de la banque Pacific",
68 | "bobcatsecurity": "Vol de Bobcat Security",
69 | "prisonbreak": "Évasion de prison en cours",
70 | "vangelico": "Braquage de Vangelico",
71 | "houserobbery": "Cambriolage de maison",
72 | "drugsell": "Transaction suspecte",
73 | "carjacking": "Carjacking",
74 | "vehicletheft": "Vol de véhicule",
75 | "officerdown": "Officier blessé",
76 | "officerdistress": "Officier en détresse",
77 | "officerbackup": "Officier a besoin de renforts",
78 | "emsdown": "EMS blessé",
79 | "artgalleryrobbery": "Braquage de galerie d'art",
80 | "humanelabsrobbery": "Braquage des laboratoires Humane",
81 | "trainrobbery": "Braquage de train",
82 | "vanrobbery": "Braquage de fourgon blindé",
83 | "underground": "Braquage de bunker",
84 | "drugboatrobbery": "Bateau suspect",
85 | "unionrobbery": "Braquage de la banque Union Depository",
86 | "carboosting": "Vol de voiture en cours",
87 | "yachtheist": "Cambriolage de yacht en cours",
88 | "susactivity": "Activité suspecte",
89 | "hunting": "Violation possible de la chasse",
90 | "explosion": "Explosion signalée",
91 |
92 | "justnow": "à l'instant",
93 | "minute": "minute(s) auparavant",
94 | "hour": "heure(s) auparavant",
95 | "day": "jour(s) auparavant"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/nl.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Loskoppelen van Dispatch Oproep",
3 | "dispatch_attach": "Verbinden met Dispatch Oproep",
4 | "unit": "Eenheid",
5 | "units": "Eenheden",
6 | "additionals": "Extra Eenheden",
7 |
8 | "open_dispatch": "Open Dispatch Menu",
9 | "911_help": "Stuur een bericht naar de politie.",
10 | "911a_help": "Stuur een anoniem bericht naar de politie.",
11 | "311_help": "Stuur een bericht naar de ambulance.",
12 | "311a_help": "Stuur een anoniem bericht naar de ambulance.",
13 |
14 | "no_calls": "Geen Oproepen Gevonden.",
15 | "alerts": "Meldingen",
16 | "blips_cleared": "Blips Gewist",
17 | "alerts_refreshed": "Meldingen herladen",
18 | "enabled": "Ingeschakeld",
19 | "disabled": "Uitgeschakeld",
20 | "muted": "Gedempt",
21 | "unmuted": "Niet Gedempt",
22 | "waypoint_set": "Gps ingesteld",
23 |
24 | "caller_local": "Burger",
25 | "call_from": "Oproep van ",
26 | "two_door": "Tweedeurs ",
27 | "three_door": "Driedeurs ",
28 | "four_door": "Vierdeurs ",
29 | "compact": "Compact",
30 | "sedan": "Sedan",
31 | "suv": "SUV",
32 | "coupe": "Coupé",
33 | "muscle": "Muscle car",
34 | "sports_classic": "Klassieke Sportauto",
35 | "sports": "Sportauto",
36 | "super": "Supercar",
37 | "motorcycle": "Motor",
38 | "offroad": "Off-road voertuig",
39 | "industrial": "Industrieel voertuig",
40 | "utility": "Dienstvoertuig",
41 | "van": "Bestelwagen",
42 | "service": "Servicevoertuig",
43 | "military": "Militair voertuig",
44 | "truck": "Vrachtwagen",
45 | "north": "Noordwaarts",
46 | "east": "Oostwaarts",
47 | "south": "Zuidwaarts",
48 | "west": "Westwaarts",
49 | "male": "Mannelijk",
50 | "female": "Vrouwelijk",
51 |
52 | "anon_call": "Inkomende Anonieme Oproep",
53 | "anon": "Anoniem",
54 | "hidden_number": "Verborgen Nummer",
55 | "call": "Inkomende Oproep",
56 | "vehicleshots": "Schoten Afgevuurd vanuit Voertuig",
57 | "shooting": "Schietpartij",
58 | "melee": "Vechtpartij gaande",
59 | "driveby": "Drive-by schietpartij",
60 | "speeding": "Roekeloos rijgedrag",
61 | "autotheft": "Voertuig diefstal",
62 | "persondown": "Persoon is gewond",
63 | "civbled": "Bloedverlies",
64 | "storerobbery": "Winkeloverval",
65 | "fleecabank": "Fleeca Bankoverval",
66 | "paletobank": "Paleto Bankoverval",
67 | "pacificbank": "Pacific Bankoverval",
68 | "prisonbreak": "Gevangenisuitbraak gaande",
69 | "bobcatsecurity": "Bobcat overval word gepleegd",
70 | "vangelico": "Vangelico Overval",
71 | "houserobbery": "Huisoverval",
72 | "drugsell": "Verdachte Overdracht",
73 | "carjacking": "Auto-Ontvoering",
74 | "vehicletheft": "Voertuigdiefstal",
75 | "officerdown": "Agent neer",
76 | "officerdistress": "Agent in nood",
77 | "officerbackup": "Agent heeft hulp nodig",
78 | "emsdown": "Ambulancepersoneel neer",
79 | "artgalleryrobbery": "Kunstgalerijoverval",
80 | "humanelabsrobbery": "Humane Labs Overval",
81 | "trainrobbery": "Treinoverval",
82 | "vanrobbery": "Beveiligingswagenoverval",
83 | "underground": "Bunkeroverval",
84 | "drugboatrobbery": "Verdacht Bootgedrag",
85 | "unionrobbery": "Union Depository Overval",
86 | "carboosting": "Auto Diefstal Gaande",
87 | "yachtheist": "Jachtheist Gaande",
88 | "susactivity": "Verdachte Activiteit",
89 | "hunting": "Mogelijke Jacht Overtreding",
90 | "explosion": "Explosie Gemeld",
91 |
92 | "justnow": "zojuist",
93 | "minute": "minuten geleden",
94 | "hour": "uren geleden",
95 | "day": "dagen geleden"
96 | }
97 |
--------------------------------------------------------------------------------
/locales/pt-br.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Desanexar do Chamdo do Dispatch",
3 | "dispatch_attach": "Anexar a Chamado do Dispatch",
4 | "unit": "Unidade",
5 | "units": "Unidades",
6 | "additionals": "Unidades adicionais",
7 |
8 | "open_dispatch": "Abra o menu de Dispatch",
9 | "911_help": "Envie uma mensagem para a polícia.",
10 | "911a_help": "Envie uma mensagem anônima para a polícia.",
11 | "311_help": "Envie uma mensagem para o EMS.",
12 | "311a_help": "Envie uma mensagem anônima para o EMS.",
13 |
14 | "no_calls": "Nenhuma chamada encontrada.",
15 | "alerts": "Alertas ",
16 | "blips_cleared": "Blips Limpos",
17 | "enabled": "Habilitada",
18 | "disabled": "Desabilitada",
19 | "muted": "Silenciada",
20 | "unmuted": "Desmutado",
21 |
22 | "caller_local": "Local",
23 | "call_from": "Chamado de ",
24 | "two_door": "Duas Portas ",
25 | "three_door": "Três Portas ",
26 | "four_door": "Quatro Portas ",
27 | "compact": "Compacto",
28 | "sedan": "Sedãn",
29 | "suv": "SUV",
30 | "coupe": "Coupê",
31 | "muscle": "Muscle",
32 | "sports_classic": "Esportivo Clássico",
33 | "sports": "Esportivo",
34 | "super": "Super",
35 | "motorcycle": "Motocicleta",
36 | "offroad": "Veículo Off-Road",
37 | "industrial": "Veículo Industrial",
38 | "utility": "Veículo Utilitário",
39 | "van": "Van",
40 | "service": "Veículo de Serviço",
41 | "military": "Veículo Militar",
42 | "truck": "Caminhão",
43 | "north": "Sentido Norte",
44 | "east": "Sentido Leste",
45 | "south": "Sentido Sul",
46 | "west": "Sentido Oeste",
47 | "male": "Macho",
48 | "female": "Fêmea",
49 |
50 | "anon_call": "Chamada Anônima Recebida",
51 | "anon": "Anônima",
52 | "hidden_number": "Número oculto",
53 | "call": "Chamada recebida",
54 | "vehicleshots": "Tiros disparados do veículo",
55 | "shooting": "Descarga de uma arma de fogo",
56 | "melee": "Lutar em andamento",
57 | "driveby": "Tiroteio",
58 | "speeding": "Condução imprudente",
59 | "autotheft": "Roubo de um veículo a motor",
60 | "persondown": "Pessoa está ferida",
61 | "civbled": "Civil sangrando",
62 | "storerobbery": "Roubo a Loja",
63 | "fleecabank": "Roubo ao Fleeca Bank",
64 | "paletobank": "Roubo ao Paleto Bank",
65 | "pacificbank": "Roubo ao Pacific Bank",
66 | "bobcatsecurity": "Roubo ao Bobcat Security",
67 | "prisonbreak": "Fuga da Prisão",
68 | "vangelico": "Roubo a Joalheria",
69 | "houserobbery": "Assalto a casa",
70 | "drugsell": "Venda de Drogas",
71 | "carjacking": "Arrombamento de Veículo",
72 | "vehicletheft": "Roubo de veículo",
73 | "officerdown": "Oficial Caído",
74 | "officerdistress": "Oficial em angústia",
75 | "officerbackup": "Oficial precisa de reforços",
76 | "emsdown": "EMS Caído",
77 | "artgalleryrobbery": "Roubo da galeria de arte",
78 | "humanelabsrobbery": "Roubo de laboratórios Humane",
79 | "trainrobbery": "Assalto a trem",
80 | "vanrobbery": "Roubo a Van",
81 | "underground": "Roubo de Bunker",
82 | "drugboatrobbery": "Barco suspeito",
83 | "unionrobbery": "Roubo de depósito da União",
84 | "carboosting": "Boosting em andamento",
85 | "yachtheist": "Assalto de iate em andamento",
86 | "susactivity": "Atividade suspeita",
87 | "hunting": "Possível violação de caça",
88 | "explosion": "Explosão relatada",
89 | "carheist": "Roubo a Aeronave",
90 |
91 | "justnow": "agora mesmo",
92 | "minute": "minutos atrás",
93 | "hour": "horas atrás",
94 | "day": "dias atrás"
95 | }
96 |
--------------------------------------------------------------------------------
/locales/tr.json:
--------------------------------------------------------------------------------
1 | {
2 | "dispatch_detach": "Çağrıdan Ayrıl",
3 | "dispatch_attach": "Çağrıya Katıl",
4 | "unit": "Birim",
5 | "units": "Birimler",
6 | "additionals": "Ek Birimler",
7 |
8 | "open_dispatch": "Çağrı Menüsünü Aç",
9 | "911_help": "Polise mesaj gönder.",
10 | "911a_help": "Polise anonim mesaj gönder.",
11 | "311_help": "EMS'e mesaj gönder.",
12 | "311a_help": "EMS'e anonim mesaj gönder.",
13 |
14 | "no_calls": "Çağrı Bulunamadı.",
15 | "alerts": "Uyarılar ",
16 | "blips_cleared": "İşaretler Temizlendi",
17 | "alerts_refreshed": "Uyarılar Yenilendi",
18 | "enabled": "Açıldı",
19 | "disabled": "Kapatıldı",
20 | "muted": "Susturuldu",
21 | "unmuted": "Susturma Açıldı",
22 | "waypoint_set": "İşaret ayarlandı.",
23 |
24 | "caller_local": "Yerel",
25 | "call_from": " tarafından çağrı",
26 | "two_door": "İki kapı ",
27 | "three_door": "Üç kapı ",
28 | "four_door": "Dört kapı ",
29 | "compact": "Kompakt",
30 | "sedan": "Sedan",
31 | "suv": "SUV",
32 | "coupe": "Coupe",
33 | "muscle": "Muscle Araç",
34 | "sports_classic": "Spor Klasik",
35 | "sports": "Spor Araç",
36 | "super": "Süper Araç",
37 | "motorcycle": "Motosiklet",
38 | "offroad": "Off-Road Araç",
39 | "industrial": "Endüstriyel Araç",
40 | "utility": "Kamu Aracı",
41 | "van": "Van",
42 | "service": "Kamu Aracı",
43 | "military": "Askeri Araç",
44 | "truck": "Tır",
45 | "north": "Kuzey Sınırı",
46 | "east": "Doğu Sınırı",
47 | "south": "Güney Sınırı",
48 | "west": "Batı Sınırı",
49 | "male": "Erkek",
50 | "female": "Kadın",
51 |
52 | "anon_call": "Gelen Anonim Çağrı",
53 | "anon": "Anonim",
54 | "hidden_number": "Gizli Numara",
55 | "call": "Gelen İhbar",
56 | "vehicleshots": "Araçtan Silah Sıkıldı",
57 | "shooting": "Ateşli Silah Sıkıldı",
58 | "melee": "Kavga",
59 | "driveby": "Drive-by Ateşli Silah Sıkıldı",
60 | "speeding": "Tehlikeli Sürüş",
61 | "autotheft": "Motor Hırsızlığı",
62 | "persondown": "Sivil Yaralandı",
63 | "civbled": "Sivil Kan Kaybediyor",
64 | "storerobbery": "Mağaza Soygunu",
65 | "fleecabank": "Fleeca Bankası Soygunu",
66 | "paletobank": "Paleto Bankası Soygunu",
67 | "pacificbank": "Pacific Merkez Bankası Soygunu",
68 | "bobcatsecurity": "Bobcat Soygunu",
69 | "prisonbreak": "Hapishaneden Firar Ediliyor",
70 | "vangelico": "Kuyumcu Soygunu",
71 | "houserobbery": "Ev Soygunu",
72 | "drugsell": "Şüpheli Satış",
73 | "carjacking": "Araç Soygunu",
74 | "vehicletheft": "Araç Hırsızlığı",
75 | "officerdown": "Memur Yaralandı",
76 | "officerdistress": "Memur Zor Durumda",
77 | "officerbackup": "Memur Yardım Talep Ediyor",
78 | "emsdown": "EMS Yaralandı",
79 | "artgalleryrobbery": "Sanat Eseri Soygunu",
80 | "humanelabsrobbery": "Humane Laboratuvarı Soygunu",
81 | "trainrobbery": "Tren Soygunu",
82 | "vanrobbery": "Güvenlik Aracı Soygunu",
83 | "underground": "Yeraltı Sığınağı Soygunu",
84 | "drugboatrobbery": "Şüpheli Bot",
85 | "unionrobbery": "Union Rezerv Bankası Soygunu",
86 | "carboosting": "Araç Yükseltmesi Yapıldı",
87 | "yachtheist": "Yat Soygunu",
88 | "susactivity": "Şüpheli Aktivite",
89 | "hunting": "Olası Avlanma İhlali",
90 | "explosion": "Patlama Rapor Edildi",
91 |
92 | "justnow": "şuan",
93 | "minute": "dakika önce",
94 | "hour": "saat önce",
95 | "day": "gün önce"
96 | }
--------------------------------------------------------------------------------
/server/main.lua:
--------------------------------------------------------------------------------
1 | local calls = {}
2 | local callCount = 0
3 |
4 | -- Functions
5 | exports('GetDispatchCalls', function()
6 | return calls
7 | end)
8 |
9 | -- Events
10 | RegisterServerEvent('ps-dispatch:server:notify', function(data)
11 | callCount = callCount + 1
12 | data.id = callCount
13 | data.time = os.time() * 1000
14 | data.units = {}
15 | data.responses = {}
16 |
17 | if #calls > 0 then
18 | if calls[#calls] == data then
19 | return
20 | end
21 | end
22 |
23 | if #calls >= Config.MaxCallList then
24 | table.remove(calls, 1)
25 | end
26 |
27 | calls[#calls + 1] = data
28 |
29 | TriggerClientEvent('ps-dispatch:client:notify', -1, data)
30 | end)
31 |
32 | RegisterServerEvent('ps-dispatch:server:attach', function(id, player)
33 | for i=1, #calls do
34 | if calls[i]['id'] == id then
35 | for j = 1, #calls[i]['units'] do
36 | if calls[i]['units'][j]['citizenid'] == player.citizenid then
37 | return
38 | end
39 | end
40 | calls[i]['units'][#calls[i]['units'] + 1] = player
41 | return
42 | end
43 | end
44 | end)
45 |
46 | RegisterServerEvent('ps-dispatch:server:detach', function(id, player)
47 | for i = #calls, 1, -1 do
48 | if calls[i]['id'] == id then
49 | if calls[i]['units'] and (#calls[i]['units'] or 0) > 0 then
50 | for j = #calls[i]['units'], 1, -1 do
51 | if calls[i]['units'][j]['citizenid'] == player.citizenid then
52 | table.remove(calls[i]['units'], j)
53 | end
54 | end
55 | end
56 | return
57 | end
58 | end
59 | end)
60 |
61 | -- Callbacks
62 | lib.callback.register('ps-dispatch:callback:getLatestDispatch', function(source)
63 | return calls[#calls]
64 | end)
65 |
66 | lib.callback.register('ps-dispatch:callback:getCalls', function(source)
67 | return calls
68 | end)
69 |
70 | -- Commands
71 | lib.addCommand('dispatch', {
72 | help = locale('open_dispatch')
73 | }, function(source, raw)
74 | TriggerClientEvent("ps-dispatch:client:openMenu", source, calls)
75 | end)
76 |
77 | lib.addCommand('911', {
78 | help = 'Send a message to 911',
79 | params = { { name = 'message', type = 'string', help = '911 Message' }},
80 | }, function(source, args, raw)
81 | local fullMessage = raw:sub(5)
82 | TriggerClientEvent('ps-dispatch:client:sendEmergencyMsg', source, fullMessage, "911", false)
83 | end)
84 | lib.addCommand('911a', {
85 | help = 'Send an anonymous message to 911',
86 | params = { { name = 'message', type = 'string', help = '911 Message' }},
87 | }, function(source, args, raw)
88 | local fullMessage = raw:sub(5)
89 | TriggerClientEvent('ps-dispatch:client:sendEmergencyMsg', source, fullMessage, "911", true)
90 | end)
91 |
92 | lib.addCommand('311', {
93 | help = 'Send a message to 311',
94 | params = { { name = 'message', type = 'string', help = '311 Message' }},
95 | }, function(source, args, raw)
96 | local fullMessage = raw:sub(5)
97 | TriggerClientEvent('ps-dispatch:client:sendEmergencyMsg', source, fullMessage, "311", false)
98 | end)
99 |
100 | lib.addCommand('311a', {
101 | help = 'Send an anonymous message to 311',
102 | params = { { name = 'message', type = 'string', help = '311 Message' }},
103 | }, function(source, args, raw)
104 | local fullMessage = raw:sub(5)
105 | TriggerClientEvent('ps-dispatch:client:sendEmergencyMsg', source, fullMessage, "311", true)
106 | end)
107 |
108 |
--------------------------------------------------------------------------------
/shared/config.lua:
--------------------------------------------------------------------------------
1 | Config = Config or {}
2 |
3 | Config.ShortCalls = false -- Dispatch notifications are sent containing only the alert name, omitting additional details. For more information, the dispatch menu can be accessed.
4 | Config.Debug = false -- Enables debug and send alerts when leo break the law.
5 |
6 | Config.RespondKeybind = 'E'
7 | Config.OpenDispatchMenu = 'O'
8 | Config.AlertTime = 5 -- Specify the duration for the alert to appear on the screen. The default time is 5 seconds for all alerts. To set a different duration for specific alerts, change the value in `alertTime = nil` found in the alerts.lua file.
9 |
10 | Config.MaxCallList = 25 -- maximum dispatch calls in dispatch list
11 | Config.OnDutyOnly = true -- Set true if only on duty players can see the alert
12 | Config.Jobs = { -- Job Types or names that can access the dispatch menu. If you want to allow more jobs to see certain dispatch alerts. Go to alerts.lua and add the job name to the alert.
13 | "leo",
14 | "ems"
15 | }
16 |
17 | Config.AlertCommandCooldown = 60 -- this would make the command work every 60 seconds to avoid spamming
18 |
19 | Config.DefaultAlertsDelay = 5 -- Delay between each default alert, prevent spamming
20 | Config.DefaultAlerts = {
21 | Speeding = true,
22 | Shooting = true,
23 | Autotheft = true,
24 | Melee = true,
25 | PlayerDowned = true,
26 | Explosion = true
27 | }
28 |
29 | Config.MinOffset = 1
30 | Config.MaxOffset = 120
31 |
32 | Config.PhoneRequired = true -- Set true if only can use 911/311 command when got a phone on inventory.
33 | Config.PhoneItems = { -- Add the entire list of your phone items.
34 | "phone",
35 | }
36 |
37 | -- Locations for the Hunting Zones and No Dispatch Zones( Label: Name of Blip // Radius: Radius of the Alert and Blip)
38 | Config.EnableHuntingBlip = true
39 |
40 | Config.Locations = {
41 | ["HuntingZones"] = {
42 | [1] = {label = "Hunting Zone", radius = 650.0, coords = vector3(-938.61, 4823.99, 313.92)},
43 | },
44 | ["NoDispatchZones"] = {
45 | [1] = {label = "Ammunation 1", coords = vector3(13.53, -1097.92, 29.8), length = 14.0, width = 5.0, heading = 70, minZ = 28.8, maxZ = 32.8},
46 | [2] = {label = "Ammunation 2", coords = vector3(821.96, -2163.09, 29.62), length = 14.0, width = 5.0, heading = 270, minZ = 28.62, maxZ = 32.62},
47 | },
48 | }
49 |
50 | -- Whitelist Guns that do not send shooting alerts
51 | Config.WeaponWhitelist = {
52 | 'WEAPON_GRENADE',
53 | 'WEAPON_BZGAS',
54 | 'WEAPON_MOLOTOV',
55 | 'WEAPON_STICKYBOMB',
56 | 'WEAPON_PROXMINE',
57 | 'WEAPON_SNOWBALL',
58 | 'WEAPON_PIPEBOMB',
59 | 'WEAPON_BALL',
60 | 'WEAPON_SMOKEGRENADE',
61 | 'WEAPON_FLARE',
62 | 'WEAPON_PETROLCAN',
63 | 'WEAPON_FIREEXTINGUISHER',
64 | 'WEAPON_HAZARDCAN',
65 | 'WEAPON_RAYCARBINE',
66 | 'WEAPON_STUNGUN'
67 | }
68 |
69 | Config.Blips = {
70 | ['vehicleshots'] = { -- Need to match the codeName in alerts.lua
71 | radius = 0,
72 | sprite = 119,
73 | color = 1,
74 | scale = 1.5,
75 | length = 2,
76 | sound = 'Lose_1st',
77 | sound2 = 'GTAO_FM_Events_Soundset',
78 | offset = false,
79 | flash = false
80 | },
81 | ['shooting'] = {
82 | radius = 0,
83 | sprite = 110,
84 | color = 1,
85 | scale = 1.5,
86 | length = 2,
87 | sound = 'Lose_1st',
88 | sound2 = 'GTAO_FM_Events_Soundset',
89 | offset = false,
90 | flash = false
91 | },
92 | ['speeding'] = {
93 | radius = 0,
94 | sprite = 326,
95 | color = 84,
96 | scale = 1.5,
97 | length = 2,
98 | sound = 'Lose_1st',
99 | sound2 = 'GTAO_FM_Events_Soundset',
100 | offset = false,
101 | flash = false
102 | },
103 | ['fight'] = {
104 | radius = 0,
105 | sprite = 685,
106 | color = 69,
107 | scale = 1.5,
108 | length = 2,
109 | sound = 'Lose_1st',
110 | sound2 = 'GTAO_FM_Events_Soundset',
111 | offset = false,
112 | flash = false
113 | },
114 | ['civdown'] = {
115 | radius = 0,
116 | sprite = 126,
117 | color = 3,
118 | scale = 1.5,
119 | length = 2,
120 | sound = 'dispatch',
121 | offset = false,
122 | flash = false
123 | },
124 | ['civdead'] = {
125 | radius = 0,
126 | sprite = 126,
127 | color = 3,
128 | scale = 1.5,
129 | length = 2,
130 | sound = 'dispatch',
131 | offset = false,
132 | flash = false
133 | },
134 | ['911call'] = {
135 | radius = 0,
136 | sprite = 480,
137 | color = 1,
138 | scale = 1.5,
139 | length = 2,
140 | sound = 'Lose_1st',
141 | sound2 = 'GTAO_FM_Events_Soundset',
142 | offset = false,
143 | flash = false
144 | },
145 | ['311call'] = {
146 | radius = 0,
147 | sprite = 480,
148 | color = 3,
149 | scale = 1.5,
150 | length = 2,
151 | sound = 'Lose_1st',
152 | sound2 = 'GTAO_FM_Events_Soundset',
153 | offset = false,
154 | flash = false
155 | },
156 | ['officerdown'] = {
157 | radius = 15.0,
158 | sprite = 526,
159 | color = 1,
160 | scale = 1.5,
161 | length = 2,
162 | sound = 'panicbutton',
163 | offset = false,
164 | flash = true
165 | },
166 | ['officerbackup'] = {
167 | radius = 15.0,
168 | sprite = 526,
169 | color = 1,
170 | scale = 1.5,
171 | length = 2,
172 | sound = 'panicbutton',
173 | offset = false,
174 | flash = true
175 | },
176 | ['officerdistress'] = {
177 | radius = 15.0,
178 | sprite = 526,
179 | color = 1,
180 | scale = 1.5,
181 | length = 2,
182 | sound = 'panicbutton',
183 | offset = false,
184 | flash = true
185 | },
186 | ['emsdown'] = {
187 | radius = 15.0,
188 | sprite = 526,
189 | color = 3,
190 | scale = 1.5,
191 | length = 2,
192 | sound = 'panicbutton',
193 | offset = false,
194 | flash = false
195 | },
196 | ['hunting'] = {
197 | radius = 0,
198 | sprite = 141,
199 | color = 2,
200 | scale = 1.5,
201 | length = 2,
202 | sound = 'Lose_1st',
203 | sound2 = 'GTAO_FM_Events_Soundset',
204 | offset = false,
205 | flash = false
206 | },
207 | ['storerobbery'] = {
208 | radius = 0,
209 | sprite = 52,
210 | color = 1,
211 | scale = 1.5,
212 | length = 2,
213 | sound = 'Lose_1st',
214 | sound2 = 'GTAO_FM_Events_Soundset',
215 | offset = false,
216 | flash = false
217 | },
218 | ['bankrobbery'] = {
219 | radius = 0,
220 | sprite = 500,
221 | color = 2,
222 | scale = 1.5,
223 | length = 2,
224 | sound = 'robberysound',
225 | offset = false,
226 | flash = false
227 | },
228 | ['paletobankrobbery'] = {
229 | radius = 0,
230 | sprite = 500,
231 | color = 12,
232 | scale = 1.5,
233 | length = 2,
234 | sound = 'robberysound',
235 | offset = false,
236 | flash = false
237 | },
238 | ['pacificbankrobbery'] = {
239 | radius = 0,
240 | sprite = 500,
241 | color = 5,
242 | scale = 1.5,
243 | length = 2,
244 | sound = 'robberysound',
245 | offset = false,
246 | flash = false
247 | },
248 | ['bobcatsecurityheist'] = {
249 | radius = 0,
250 | sprite = 500,
251 | color = 5,
252 | scale = 1.5,
253 | length = 2,
254 | sound = 'robberysound',
255 | offset = false,
256 | flash = false
257 | },
258 | ['prisonbreak'] = {
259 | radius = 0,
260 | sprite = 189,
261 | color = 59,
262 | scale = 1.5,
263 | length = 2,
264 | sound = 'robberysound',
265 | offset = false,
266 | flash = false
267 | },
268 | ['vangelicorobbery'] = {
269 | radius = 0,
270 | sprite = 434,
271 | color = 5,
272 | scale = 1.5,
273 | length = 2,
274 | sound = 'robberysound',
275 | offset = false,
276 | flash = false
277 | },
278 | ['houserobbery'] = {
279 | radius = 0,
280 | sprite = 40,
281 | color = 5,
282 | scale = 1.5,
283 | length = 2,
284 | sound = 'Lose_1st',
285 | sound2 = 'GTAO_FM_Events_Soundset',
286 | offset = false,
287 | flash = false
288 | },
289 | ['suspicioushandoff'] = {
290 | radius = 120.0,
291 | sprite = 469,
292 | color = 52,
293 | scale = 0,
294 | length = 2,
295 | sound = 'Lose_1st',
296 | sound2 = 'GTAO_FM_Events_Soundset',
297 | offset = true,
298 | flash = false
299 | },
300 | ['yachtheist'] = {
301 | radius = 0,
302 | sprite = 455,
303 | color = 60,
304 | scale = 1.5,
305 | length = 2,
306 | sound = 'robberysound',
307 | offset = false,
308 | flash = false
309 | },
310 | ['vehicletheft'] = {
311 | radius = 0,
312 | sprite = 595,
313 | color = 60,
314 | scale = 1.5,
315 | length = 2,
316 | sound = 'Lose_1st',
317 | sound2 = 'GTAO_FM_Events_Soundset',
318 | offset = false,
319 | flash = false
320 | },
321 | ['signrobbery'] = {
322 | radius = 0,
323 | sprite = 358,
324 | color = 60,
325 | scale = 1.5,
326 | length = 2,
327 | sound = 'Lose_1st',
328 | sound2 = 'GTAO_FM_Events_Soundset',
329 | offset = false,
330 | flash = false
331 | },
332 | ['susactivity'] = {
333 | radius = 0,
334 | sprite = 66,
335 | color = 37,
336 | scale = 0.5,
337 | length = 2,
338 | sound = 'Lose_1st',
339 | sound2 = 'GTAO_FM_Events_Soundset',
340 | offset = false,
341 | flash = false
342 | },
343 | -- Rainmad Scripts
344 | ['artgalleryrobbery'] = {
345 | radius = 0,
346 | sprite = 269,
347 | color = 59,
348 | scale = 1.5,
349 | length = 2,
350 | sound = 'robberysound',
351 | offset = false,
352 | flash = false
353 | },
354 | ['humanelabsrobbery'] = {
355 | radius = 0,
356 | sprite = 499,
357 | color = 1,
358 | scale = 1.5,
359 | length = 2,
360 | sound = 'robberysound',
361 | offset = false,
362 | flash = false
363 | },
364 | ['trainrobbery'] = {
365 | radius = 0,
366 | sprite = 667,
367 | color = 78,
368 | scale = 1.5,
369 | length = 2,
370 | sound = 'robberysound',
371 | offset = false,
372 | flash = false
373 | },
374 | ['vanrobbery'] = {
375 | radius = 0,
376 | sprite = 67,
377 | color = 59,
378 | scale = 1.5,
379 | length = 2,
380 | sound = 'robberysound',
381 | offset = false,
382 | flash = false
383 | },
384 | ['undergroundrobbery'] = {
385 | radius = 0,
386 | sprite = 486,
387 | color = 59,
388 | scale = 1.5,
389 | length = 2,
390 | sound = 'robberysound',
391 | offset = false,
392 | flash = false
393 | },
394 | ['drugboatrobbery'] = {
395 | radius = 0,
396 | sprite = 427,
397 | color = 26,
398 | scale = 1.5,
399 | length = 2,
400 | sound = 'robberysound',
401 | offset = false,
402 | flash = false
403 | },
404 | ['unionrobbery'] = {
405 | radius = 0,
406 | sprite = 500,
407 | color = 60,
408 | scale = 1.5,
409 | length = 2,
410 | sound = 'robberysound',
411 | offset = false,
412 | flash = false
413 | },
414 | ['carboosting'] = {
415 | radius = 0,
416 | sprite = 595,
417 | color = 60,
418 | scale = 1.5,
419 | length = 2,
420 | sound = 'Lose_1st',
421 | sound2 = 'GTAO_FM_Events_Soundset',
422 | offset = false,
423 | flash = false
424 | },
425 | ['carjack'] = {
426 | radius = 0,
427 | sprite = 595,
428 | color = 60,
429 | scale = 1.5,
430 | length = 2,
431 | sound = 'Lose_1st',
432 | sound2 = 'GTAO_FM_Events_Soundset',
433 | offset = false,
434 | flash = false
435 | },
436 | ['explosion'] = {
437 | radius = 75.0,
438 | sprite = 436,
439 | color = 1,
440 | scale = 1.5,
441 | length = 2,
442 | sound = 'Lose_1st',
443 | sound2 = 'GTAO_FM_Events_Soundset',
444 | offset = true,
445 | flash = false
446 | }
447 | }
448 |
449 | Config.Colors = {
450 | ['0'] = "Metallic Black",
451 | ['1'] = "Metallic Graphite Black",
452 | ['2'] = "Metallic Black Steel",
453 | ['3'] = "Metallic Dark Silver",
454 | ['4'] = "Metallic Silver",
455 | ['5'] = "Metallic Blue Silver",
456 | ['6'] = "Metallic Steel Gray",
457 | ['7'] = "Metallic Shadow Silver",
458 | ['8'] = "Metallic Stone Silver",
459 | ['9'] = "Metallic Midnight Silver",
460 | ['10'] = "Metallic Gun Metal",
461 | ['11'] = "Metallic Anthracite Grey",
462 | ['12'] = "Matte Black",
463 | ['13'] = "Matte Gray",
464 | ['14'] = "Matte Light Grey",
465 | ['15'] = "Util Black",
466 | ['16'] = "Util Black Poly",
467 | ['17'] = "Util Dark silver",
468 | ['18'] = "Util Silver",
469 | ['19'] = "Util Gun Metal",
470 | ['20'] = "Util Shadow Silver",
471 | ['21'] = "Worn Black",
472 | ['22'] = "Worn Graphite",
473 | ['23'] = "Worn Silver Grey",
474 | ['24'] = "Worn Silver",
475 | ['25'] = "Worn Blue Silver",
476 | ['26'] = "Worn Shadow Silver",
477 | ['27'] = "Metallic Red",
478 | ['28'] = "Metallic Torino Red",
479 | ['29'] = "Metallic Formula Red",
480 | ['30'] = "Metallic Blaze Red",
481 | ['31'] = "Metallic Graceful Red",
482 | ['32'] = "Metallic Garnet Red",
483 | ['33'] = "Metallic Desert Red",
484 | ['34'] = "Metallic Cabernet Red",
485 | ['35'] = "Metallic Candy Red",
486 | ['36'] = "Metallic Sunrise Orange",
487 | ['37'] = "Metallic Classic Gold",
488 | ['38'] = "Metallic Orange",
489 | ['39'] = "Matte Red",
490 | ['40'] = "Matte Dark Red",
491 | ['41'] = "Matte Orange",
492 | ['42'] = "Matte Yellow",
493 | ['43'] = "Util Red",
494 | ['44'] = "Util Bright Red",
495 | ['45'] = "Util Garnet Red",
496 | ['46'] = "Worn Red",
497 | ['47'] = "Worn Golden Red",
498 | ['48'] = "Worn Dark Red",
499 | ['49'] = "Metallic Dark Green",
500 | ['50'] = "Metallic Racing Green",
501 | ['51'] = "Metallic Sea Green",
502 | ['52'] = "Metallic Olive Green",
503 | ['53'] = "Metallic Green",
504 | ['54'] = "Metallic Gasoline Blue Green",
505 | ['55'] = "Matte Lime Green",
506 | ['56'] = "Util Dark Green",
507 | ['57'] = "Util Green",
508 | ['58'] = "Worn Dark Green",
509 | ['59'] = "Worn Green",
510 | ['60'] = "Worn Sea Wash",
511 | ['61'] = "Metallic Midnight Blue",
512 | ['62'] = "Metallic Dark Blue",
513 | ['63'] = "Metallic Saxony Blue",
514 | ['64'] = "Metallic Blue",
515 | ['65'] = "Metallic Mariner Blue",
516 | ['66'] = "Metallic Harbor Blue",
517 | ['67'] = "Metallic Diamond Blue",
518 | ['68'] = "Metallic Surf Blue",
519 | ['69'] = "Metallic Nautical Blue",
520 | ['70'] = "Metallic Bright Blue",
521 | ['71'] = "Metallic Purple Blue",
522 | ['72'] = "Metallic Spinnaker Blue",
523 | ['73'] = "Metallic Ultra Blue",
524 | ['74'] = "Metallic Bright Blue",
525 | ['75'] = "Util Dark Blue",
526 | ['76'] = "Util Midnight Blue",
527 | ['77'] = "Util Blue",
528 | ['78'] = "Util Sea Foam Blue",
529 | ['79'] = "Uil Lightning blue",
530 | ['80'] = "Util Maui Blue Poly",
531 | ['81'] = "Util Bright Blue",
532 | ['82'] = "Matte Dark Blue",
533 | ['83'] = "Matte Blue",
534 | ['84'] = "Matte Midnight Blue",
535 | ['85'] = "Worn Dark blue",
536 | ['86'] = "Worn Blue",
537 | ['87'] = "Worn Light blue",
538 | ['88'] = "Metallic Taxi Yellow",
539 | ['89'] = "Metallic Race Yellow",
540 | ['90'] = "Metallic Bronze",
541 | ['91'] = "Metallic Yellow Bird",
542 | ['92'] = "Metallic Lime",
543 | ['93'] = "Metallic Champagne",
544 | ['94'] = "Metallic Pueblo Beige",
545 | ['95'] = "Metallic Dark Ivory",
546 | ['96'] = "Metallic Choco Brown",
547 | ['97'] = "Metallic Golden Brown",
548 | ['98'] = "Metallic Light Brown",
549 | ['99'] = "Metallic Straw Beige",
550 | ['100'] = "Metallic Moss Brown",
551 | ['101'] = "Metallic Biston Brown",
552 | ['102'] = "Metallic Beechwood",
553 | ['103'] = "Metallic Dark Beechwood",
554 | ['104'] = "Metallic Choco Orange",
555 | ['105'] = "Metallic Beach Sand",
556 | ['106'] = "Metallic Sun Bleeched Sand",
557 | ['107'] = "Metallic Cream",
558 | ['108'] = "Util Brown",
559 | ['109'] = "Util Medium Brown",
560 | ['110'] = "Util Light Brown",
561 | ['111'] = "Metallic White",
562 | ['112'] = "Metallic Frost White",
563 | ['113'] = "Worn Honey Beige",
564 | ['114'] = "Worn Brown",
565 | ['115'] = "Worn Dark Brown",
566 | ['116'] = "Worn straw beige",
567 | ['117'] = "Brushed Steel",
568 | ['118'] = "Brushed Black Steel",
569 | ['119'] = "Brushed Aluminium",
570 | ['120'] = "Chrome",
571 | ['121'] = "Worn Off White",
572 | ['122'] = "Util Off White",
573 | ['123'] = "Worn Orange",
574 | ['124'] = "Worn Light Orange",
575 | ['125'] = "Metallic Securicor Green",
576 | ['126'] = "Worn Taxi Yellow",
577 | ['127'] = "Police Car Blue",
578 | ['128'] = "Matte Green",
579 | ['129'] = "Matte Brown",
580 | ['130'] = "Worn Orange",
581 | ['131'] = "Matte White",
582 | ['132'] = "Worn White",
583 | ['133'] = "Worn Olive Army Green",
584 | ['134'] = "Pure White",
585 | ['135'] = "Hot Pink",
586 | ['136'] = "Salmon pink",
587 | ['137'] = "Metallic Vermillion Pink",
588 | ['138'] = "Orange",
589 | ['139'] = "Green",
590 | ['140'] = "Blue",
591 | ['141'] = "Mettalic Black Blue",
592 | ['142'] = "Metallic Black Purple",
593 | ['143'] = "Metallic Black Red",
594 | ['144'] = "hunter green",
595 | ['145'] = "Metallic Purple",
596 | ['146'] = "Metallic Dark Blue",
597 | ['147'] = "Black",
598 | ['148'] = "Matte Purple",
599 | ['149'] = "Matte Dark Purple",
600 | ['150'] = "Metallic Lava Red",
601 | ['151'] = "Matte Forest Green",
602 | ['152'] = "Matte Olive Drab",
603 | ['153'] = "Matte Desert Brown",
604 | ['154'] = "Matte Desert Tan",
605 | ['155'] = "Matte Foilage Green",
606 | ['156'] = "Default Alloy Color",
607 | ['157'] = "Epsilon Blue",
608 | ['158'] = "Pure Gold",
609 | ['159'] = "Brushed Gold",
610 | ['160'] = "MP100"
611 | }
612 |
--------------------------------------------------------------------------------
/sounds/dispatch.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Project-Sloth/ps-dispatch/e42760ba65c1d840a01ba8519d82e1af96f0e14a/sounds/dispatch.ogg
--------------------------------------------------------------------------------
/sounds/panicbutton.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Project-Sloth/ps-dispatch/e42760ba65c1d840a01ba8519d82e1af96f0e14a/sounds/panicbutton.ogg
--------------------------------------------------------------------------------
/sounds/robberysound.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Project-Sloth/ps-dispatch/e42760ba65c1d840a01ba8519d82e1af96f0e14a/sounds/robberysound.ogg
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 |
3 |
4 | .DS_Store
5 |
--------------------------------------------------------------------------------
/ui/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 4,
3 | "useTabs": true,
4 | "semi": false,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # Svelte + TS + Vite
2 |
3 | This template should help get you started developing with Svelte and TypeScript in Vite.
4 |
5 | ## Recommended IDE Setup
6 |
7 | [VSCode](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8 |
9 | ## Need an official Svelte framework?
10 |
11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12 |
13 | ## Technical considerations
14 |
15 | **Why use this over SvelteKit?**
16 |
17 | - It brings its own routing solution which might not be preferable for some users.
18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19 | `vite dev` and `vite build` wouldn't work in a SvelteKit environment, for example.
20 |
21 | This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
22 |
23 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
24 |
25 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
26 |
27 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information.
28 |
29 | **Why enable `allowJs` in the TS template?**
30 |
31 | While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant.
32 |
33 | **Why is HMR not preserving my local component state?**
34 |
35 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr).
36 |
37 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR.
38 |
39 | ```ts
40 | // store.ts
41 | // An extremely simple external store
42 | import { writable } from 'svelte/store'
43 | export default writable(0)
44 | ```
45 |
--------------------------------------------------------------------------------
/ui/app.js:
--------------------------------------------------------------------------------
1 | $(document).ready(() => {
2 | window.addEventListener('message', function (event) {
3 | let data = event.data;
4 | if (data.update == 'newCall') {
5 | addNewCall(data.callID, data.timer, data.data, data.isPolice);
6 | }
7 | });
8 | });
9 |
10 | const MONTH_NAMES = [
11 | 'January',
12 | 'February',
13 | 'March',
14 | 'April',
15 | 'May',
16 | 'June',
17 | 'July',
18 | 'August',
19 | 'September',
20 | 'October',
21 | 'November',
22 | 'December',
23 | ];
24 |
25 | function getFormattedDate(date, prefomattedDate = false, hideYear = false) {
26 | const day = date.getDate();
27 | const month = MONTH_NAMES[date.getMonth()];
28 | const year = date.getFullYear();
29 | const hours = date.getHours();
30 | let minutes = date.getMinutes();
31 |
32 | if (minutes < 10) {
33 | minutes = `0${minutes}`;
34 | }
35 |
36 | if (prefomattedDate) {
37 | return `${prefomattedDate} at ${hours}:${minutes}`;
38 | }
39 |
40 | if (hideYear) {
41 | return `${day}. ${month} at ${hours}:${minutes}`;
42 | }
43 |
44 | return `${day}. ${month} ${year}. at ${hours}:${minutes}`;
45 | }
46 |
47 | function timeAgo(dateParam) {
48 | if (!dateParam) {
49 | return null;
50 | }
51 |
52 | const date =
53 | typeof dateParam === 'object' ? dateParam : new Date(dateParam);
54 | const DAY_IN_MS = 86400000;
55 | const today = new Date();
56 | const yesterday = new Date(today - DAY_IN_MS);
57 | const seconds = Math.round((today - date) / 1000);
58 | const minutes = Math.round(seconds / 60);
59 | const isToday = today.toDateString() === date.toDateString();
60 | const isYesterday = yesterday.toDateString() === date.toDateString();
61 | const isThisYear = today.getFullYear() === date.getFullYear();
62 |
63 | if (seconds < 5) {
64 | return 'Just Now';
65 | } else if (seconds < 60) {
66 | return `${seconds} Seconds ago`;
67 | } else if (seconds < 90) {
68 | return 'About a minute ago';
69 | } else if (minutes < 60) {
70 | return `${minutes} Minutes ago`;
71 | } else if (isToday) {
72 | return getFormattedDate(date, 'Today');
73 | } else if (isYesterday) {
74 | return getFormattedDate(date, 'Yesterday');
75 | } else if (isThisYear) {
76 | return getFormattedDate(date, false, true);
77 | }
78 |
79 | return getFormattedDate(date);
80 | }
81 |
82 | function addNewCall(callID, timer, info, isPolice) {
83 | const prio = info['priority'];
84 | let DispatchItem;
85 | if (info['isDead']) {
86 | DispatchItem = `#${callID}
${info.dispatchCode}
${info.dispatchMessage}
`;
87 | } else {
88 | DispatchItem = `
#${callID}
${info.dispatchCode}
${info.dispatchMessage}
`;
89 | }
90 |
91 | // Above we are defining a default dispatch item and then we will append the data we have been sent.
92 |
93 | if (info['time']) {
94 | DispatchItem += `
${timeAgo(
95 | info['time']
96 | )}
`;
97 | }
98 |
99 | if (info['firstStreet']) {
100 | DispatchItem += `
${info['firstStreet']}
`;
101 | }
102 |
103 | if (info['heading']) {
104 | DispatchItem += `
${info['heading']}
`;
105 | }
106 |
107 | if (info['callsign']) {
108 | DispatchItem += `
${info['callsign']}
`;
109 | }
110 |
111 | if (info['doorCount']) {
112 | DispatchItem += `
${info['doorCount']}
`;
113 | }
114 |
115 | if (info['weapon']) {
116 | DispatchItem += `
${info['weapon']}
`;
117 | }
118 |
119 | if (info['camId']) {
120 | DispatchItem += `
${info['camId']}
`;
121 | }
122 |
123 | if (info['gender']) {
124 | DispatchItem += `
${info['gender']}
`;
125 | }
126 |
127 | if (info['model'] && info['plate']) {
128 | DispatchItem += `
${info['model']} ${info['plate']}
`;
129 | } else if (info['plate']) {
130 | DispatchItem += `
${info['plate']}
`;
131 | } else if (info['model']) {
132 | DispatchItem += `
${info['model']}
`;
133 | }
134 |
135 | if (info['firstColor']) {
136 | DispatchItem += `
${info['firstColor']}
`;
137 | }
138 | if (info['automaticGunfire'] == true) {
139 | DispatchItem += `
Automatic Gunfire
`;
140 | }
141 |
142 | if (info['name'] && info['number']) {
143 | DispatchItem += `
${info['name']} ${info['number']}
`;
144 | } else if (info['number']) {
145 | DispatchItem += `
${info['number']}
`;
146 | } else if (info['name']) {
147 | DispatchItem += `
${info['name']}
`;
148 | }
149 |
150 | if (info['information']) {
151 | DispatchItem += `
${info['information']}
`;
152 | }
153 |
154 | DispatchItem += `
`;
155 |
156 | $('.dispatch-holder').prepend(DispatchItem);
157 |
158 | var timer = 4000;
159 |
160 | if (prio == 1) {
161 | timer = 12000;
162 | } else if (prio == 2) {
163 | timer = 9000;
164 | }
165 |
166 | $(`.${callID}`).addClass('animate__backInRight');
167 | setTimeout(() => {
168 | $(`.${callID}`).addClass('animate__backOutRight');
169 | setTimeout(() => {
170 | $(`.${callID}`).remove();
171 | }, 1000);
172 | }, timer || 4500);
173 | }
174 |
--------------------------------------------------------------------------------
/ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
OK1ez
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "would_you_rather",
3 | "version": "0.0.0",
4 | "type": "module",
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "check": "svelte-check --tsconfig ./tsconfig.json"
10 | },
11 | "devDependencies": {
12 | "@sveltejs/vite-plugin-svelte": "^2.4.2",
13 | "@tsconfig/svelte": "^3.0.0",
14 | "@types/leaflet": "^1.9.8",
15 | "autoprefixer": "^10.4.16",
16 | "daisyui": "^3.1.2",
17 | "postcss": "^8.4.31",
18 | "svelte": "^3.59.2",
19 | "svelte-check": "^2.7.2",
20 | "svelte-preprocess": "^5.1.3",
21 | "tailwindcss": "^3.4.1",
22 | "tslib": "^2.6.2",
23 | "typescript": "^4.7.3",
24 | "vite": "4.5.3"
25 | },
26 | "dependencies": {
27 | "leaflet": "^1.9.4",
28 | "lucide": "^0.307.0",
29 | "lucide-svelte": "^0.307.0",
30 | "svelte-dnd-action": "^0.9.38"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/ui/postcss.config.js:
--------------------------------------------------------------------------------
1 | import tailwind from 'tailwindcss';
2 | import autoprefixer from 'autoprefixer';
3 | import tailwindConfig from './tailwind.config.cjs';
4 |
5 | export default {
6 | plugins: [tailwind(tailwindConfig), autoprefixer],
7 | }
8 |
--------------------------------------------------------------------------------
/ui/src/App.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {#if $Locale}
14 |
15 |
16 |
17 |
18 |
19 | {/if}
20 |
21 |
22 |
23 | {#if $BROWSER_MODE}
24 |
25 |
26 | {/if}
27 |
--------------------------------------------------------------------------------
/ui/src/Tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | * {
6 | margin: 0;
7 | padding: 0;
8 | }
9 |
10 | *:focus {
11 | outline: none;
12 | }
13 |
14 | :root {
15 | font-size: 62.5%;
16 | font-smooth: auto;
17 | color: #d8d8d8;
18 | font-synthesis: none;
19 | text-rendering: optimizeLegibility;
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | -webkit-text-size-adjust: 100%;
23 | }
24 |
25 | html, body {
26 | height: 100vh;
27 | width: 100vw;
28 | font-size: 1.6rem;
29 | overflow: hidden;
30 | }
31 |
32 | ::-webkit-scrollbar {
33 | height: 0px;
34 | width: 4px;
35 | background-color: #7979795e;
36 | border-radius: 50px;
37 | }
38 |
39 | ::-webkit-scrollbar-thumb {
40 | background-color: #cecece;
41 | border-radius: 50px;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/ui/src/components/Main.svelte:
--------------------------------------------------------------------------------
1 |
61 |
62 |
63 |
64 |
68 | {#each notifications.slice().reverse() as dispatch, index (dispatch.data.id)}
69 |
70 |
71 |
72 | #{dispatch.data.id}
73 |
74 |
75 | {dispatch.data.code}
76 |
77 |
78 | {dispatch.data.message}
79 |
80 |
81 |
82 |
83 |
84 | {#if dispatch.data}
85 | {#each getDispatchData(dispatch) as field}
86 | {#if field.value}
87 |
88 |
89 | {field.label}: {field.value}
90 |
91 | {/if}
92 | {/each}
93 | {/if}
94 |
95 |
96 | {#if index === 0}
97 |
98 | [{$RESPOND_KEYBIND}] Respond
99 |
100 | {/if}
101 |
102 |
103 |
104 | {/each}
105 |
106 |
107 |
--------------------------------------------------------------------------------
/ui/src/components/Menu.svelte:
--------------------------------------------------------------------------------
1 |
87 |
88 |
204 |
205 |
--------------------------------------------------------------------------------
/ui/src/main.ts:
--------------------------------------------------------------------------------
1 | import App from './App.svelte'
2 | import './Tailwind.css'
3 |
4 | const app = new App({
5 | target: document.getElementById('app')
6 | })
7 |
8 | export default app
--------------------------------------------------------------------------------
/ui/src/providers/AlwaysListener.svelte:
--------------------------------------------------------------------------------
1 |
57 |
--------------------------------------------------------------------------------
/ui/src/providers/BackdropFix.svelte:
--------------------------------------------------------------------------------
1 |
177 |
178 |
179 |
184 |
185 |
186 |
--------------------------------------------------------------------------------
/ui/src/providers/DebugBrowser.svelte:
--------------------------------------------------------------------------------
1 |
118 |
119 |
120 |
121 |
{
123 | show = !show;
124 | }}
125 | >
126 | Show
127 |
128 | {#if show}
129 |
130 | {#each options as option}
131 |
132 |
{option.component}
133 | {#each option.actions as action}
134 |
{
136 |
137 | if (action.custom == true) {
138 | action.customFunction();
139 | return
140 | }
141 | debugData([
142 | {
143 | action: action.action,
144 | data: action.data,
145 | },
146 | ])
147 | }}
148 | >
149 | {action.name}
150 |
151 | {/each}
152 |
153 | {/each}
154 |
155 | {/if}
156 |
157 |
158 |
--------------------------------------------------------------------------------
/ui/src/providers/VisibilityProvider.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 | {#if $VISIBILITY}
37 |
38 |
39 |
40 |
41 | {/if}
42 |
43 |
57 |
--------------------------------------------------------------------------------
/ui/src/store/stores.ts:
--------------------------------------------------------------------------------
1 | import { writable, derived } from "svelte/store";
2 |
3 | export const VISIBILITY = writable
(false);
4 | export const BROWSER_MODE = writable(false);
5 | export const RESOURCE_NAME = writable("");
6 |
7 | export const PLAYER = writable(null);
8 | export const MAX_CALL_LIST = writable(null);
9 | export const RESPOND_KEYBIND = writable("");
10 |
11 | export const DISPATCH_MUTED = writable(false);
12 | export const DISPATCH_DISABLED = writable(false);
13 |
14 | export const DISPATCH = writable(null);
15 |
16 | export const IS_RIGHT_MARGIN = writable(true);
17 |
18 | export const shortCalls = writable(true);
19 |
20 | export function removeDispatch(callID) {
21 | DISPATCH.update(dispatches => {
22 | return dispatches.filter(dispatch => dispatch.data.id !== callID);
23 | });
24 | }
25 |
26 | interface DISPATCHMENU_DATA {
27 | id: number,
28 | message: string,
29 | code: string,
30 | icon: string,
31 | time: number,
32 | priority: number,
33 | street: string,
34 | coords: any[],
35 | gender: string,
36 | automaticGunFire: boolean,
37 | weapon: string,
38 | units: any[],
39 | name: string,
40 | number: string,
41 | information: string,
42 | vehicle: string,
43 | color: string,
44 | plate: string,
45 | class: string,
46 | doors: string,
47 | heading: string,
48 | jobs: any[],
49 | }
50 |
51 | export const DISPATCH_MENU = writable(null);
52 | export const DISPATCH_MENUS = writable(null);
53 |
54 |
55 | interface LOCALE_DATA {
56 | dispatch_detach: string,
57 | dispatch_attach: string,
58 | unit: string,
59 | units: string,
60 | additionals: string,
61 | }
62 |
63 | export const Locale = writable(null);
64 |
65 | export const processedDispatchMenu = derived(
66 | [DISPATCH_MENU, MAX_CALL_LIST, PLAYER],
67 | ([$DISPATCH_MENU, $MAX_CALL_LIST, $PLAYER]) => {
68 | if (!$DISPATCH_MENU || $MAX_CALL_LIST === null || !$PLAYER) {
69 | // Handling null or undefined values
70 | return [];
71 | }
72 |
73 | return $DISPATCH_MENU
74 | .slice(-$MAX_CALL_LIST)
75 | .filter(dispatch =>
76 | dispatch.message && dispatch.jobs.includes($PLAYER.job.type)
77 | )
78 | .reverse();
79 | }
80 | );
--------------------------------------------------------------------------------
/ui/src/typings/type.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Project-Sloth/ps-dispatch/e42760ba65c1d840a01ba8519d82e1af96f0e14a/ui/src/typings/type.ts
--------------------------------------------------------------------------------
/ui/src/utils/ReceiveNUI.ts:
--------------------------------------------------------------------------------
1 | import { onMount, onDestroy } from "svelte";
2 |
3 | interface NuiMessage {
4 | action: string;
5 | data: T;
6 | }
7 |
8 | /**
9 | * A function that manage events listeners for receiving data from the client scripts
10 | * @param action The specific `action` that should be listened for.
11 | * @param handler The callback function that will handle data relayed by this function
12 | *
13 | * @example
14 | * useNuiEvent<{VISIBILITY: true, wasVisible: 'something'}>('setVisible', (data) => {
15 | * // whatever logic you want
16 | * })
17 | *
18 | **/
19 |
20 | export function ReceiveNUI(
21 | action: string,
22 | handler: (data: T) => void
23 | ) {
24 | const eventListener = (event: MessageEvent>) => {
25 | const { action: eventAction, data } = event.data;
26 |
27 | eventAction === action && handler(data);
28 | };
29 | onMount(() => window.addEventListener("message", eventListener));
30 | onDestroy(() => window.removeEventListener("message", eventListener));
31 | }
32 |
--------------------------------------------------------------------------------
/ui/src/utils/SendNUI.ts:
--------------------------------------------------------------------------------
1 | import { BROWSER_MODE, RESOURCE_NAME } from '@store/stores'
2 |
3 | let isBrowserMode: boolean = false;
4 | BROWSER_MODE.subscribe((value: boolean) => {
5 | isBrowserMode = value;
6 | });
7 |
8 | let debugResName: string = "";
9 | RESOURCE_NAME.subscribe((value: string) => {
10 | debugResName = value;
11 | });
12 |
13 | /**
14 | * @param eventName - The endpoint eventname to target
15 | * @param data - Data you wish to send in the NUI Callback
16 | *
17 | * @return returnData - A promise for the data sent back by the NuiCallbacks CB argument
18 | */
19 |
20 | export async function SendNUI(
21 | eventName: string,
22 | data: unknown = {},
23 | debugReturn?: T
24 | ): Promise {
25 | if ((isBrowserMode == true && debugReturn) || (isBrowserMode == true)) {
26 | return Promise.resolve(debugReturn || {} as T)
27 | }
28 | const options = {
29 | method: "post",
30 | headers: {
31 | "Content-Type": "application/json; charset=UTF-8",
32 | },
33 | body: JSON.stringify(data),
34 | };
35 |
36 | const resourceName = (window as any).GetParentResourceName
37 | ? (window as any).GetParentResourceName()
38 | : debugResName;
39 |
40 |
41 | const resp: Response = await fetch(`https://${resourceName}/${eventName}`, options);
42 | return await resp.json()
43 | }
--------------------------------------------------------------------------------
/ui/src/utils/debugData.ts:
--------------------------------------------------------------------------------
1 | import {isEnvBrowser} from "./misc";
2 |
3 | interface DebugEvent {
4 | action: string;
5 | data: T;
6 | }
7 |
8 | /*
9 | * Emulates dispatching an event using SendNuiMessage in the lua scripts.
10 | * This is used when developing in browser
11 | *
12 | * @param events - The event you want to cover
13 | * @param timer - How long until it should trigger (ms)
14 | */
15 | export const debugData = (events: DebugEvent
[], timer = 0): void => {
16 | if (isEnvBrowser()) {
17 | for (const event of events) {
18 | setTimeout(() => {
19 | window.dispatchEvent(
20 | new MessageEvent("message", {
21 | data: {
22 | action: event.action,
23 | data: event.data,
24 | },
25 | })
26 | );
27 | }, timer);
28 | }
29 | }
30 | };
--------------------------------------------------------------------------------
/ui/src/utils/misc.ts:
--------------------------------------------------------------------------------
1 | export const isEnvBrowser = (): boolean => !(window as any).invokeNative;
--------------------------------------------------------------------------------
/ui/src/utils/timeAgo.ts:
--------------------------------------------------------------------------------
1 | const MONTH_NAMES = [
2 | 'January',
3 | 'February',
4 | 'March',
5 | 'April',
6 | 'May',
7 | 'June',
8 | 'July',
9 | 'August',
10 | 'September',
11 | 'October',
12 | 'November',
13 | 'December',
14 | ];
15 |
16 | function getFormattedDate(date, prefomattedDate = false, hideYear = false) {
17 | const day = date.getDate();
18 | const month = MONTH_NAMES[date.getMonth()];
19 | const year = date.getFullYear();
20 | const hours = date.getHours();
21 | let minutes = date.getMinutes();
22 |
23 | if (minutes < 10) {
24 | minutes = `0${minutes}`;
25 | }
26 |
27 | if (prefomattedDate) {
28 | return `${prefomattedDate} at ${hours}:${minutes}`;
29 | }
30 |
31 | if (hideYear) {
32 | return `${day}. ${month} at ${hours}:${minutes}`;
33 | }
34 |
35 | return `${day}. ${month} ${year}. at ${hours}:${minutes}`;
36 | }
37 |
38 |
39 | export function timeAgo(dateParam) {
40 | if (!dateParam) {
41 | return 'Unknown';
42 | }
43 |
44 | let date;
45 | try {
46 | date = typeof dateParam === 'object' ? dateParam : new Date(dateParam);
47 | } catch (e) {
48 | return 'Invalid date';
49 | }
50 |
51 | if (isNaN(date)) {
52 | return 'Invalid date';
53 | }
54 | const DAY_IN_MS = 86400000;
55 | const today = new Date();
56 | const yesterday = new Date(today - DAY_IN_MS);
57 | const seconds = Math.round((today - date) / 1000);
58 | const minutes = Math.round(seconds / 60);
59 | const isToday = today.toDateString() === date.toDateString();
60 | const isYesterday = yesterday.toDateString() === date.toDateString();
61 | const isThisYear = today.getFullYear() === date.getFullYear();
62 |
63 | if (seconds < 5) {
64 | return 'Just Now';
65 | } else if (seconds < 60) {
66 | return `${seconds} Seconds ago`;
67 | } else if (seconds < 90) {
68 | return 'A minute ago';
69 | } else if (minutes < 60) {
70 | return `${minutes} Minutes ago`;
71 | } else if (isToday) {
72 | return getFormattedDate(date, 'Today');
73 | } else if (isYesterday) {
74 | return getFormattedDate(date, 'Yesterday');
75 | } else if (isThisYear) {
76 | return getFormattedDate(date, false, true);
77 | }
78 |
79 | return getFormattedDate(date);
80 | }
--------------------------------------------------------------------------------
/ui/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/ui/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Montserrat');
2 |
3 | body {
4 | font-family: 'Montserrat', sans-serif;
5 | overflow: hidden;
6 | /* display: none; */
7 | }
8 |
9 | ::-webkit-scrollbar {
10 | width: 0px;
11 | }
12 |
13 | .dispatch-container {
14 | width: 100%;
15 | height: 100%;
16 | position: absolute;
17 | display: flex;
18 | }
19 |
20 | .dispatch-holder {
21 | width: 50vh;
22 | height: 87.5vh;
23 | margin: auto;
24 | margin-top: 9.75vh;
25 | margin-right: 0vh;
26 | display: flex;
27 | flex-direction: column;
28 | overflow-y: scroll;
29 | }
30 |
31 | .dispatch-item {
32 | margin: auto;
33 | margin-top: 0vh;
34 | margin-right: 1vh;
35 | margin-bottom: .5vh;
36 | background-color: rgba(21, 38, 56, 0.80);
37 | border-right: .75vh solid rgba(57, 59, 57, 0.80);
38 | width: 37.5vh;
39 | height: fit-content;
40 | border-bottom-left-radius: 1vh;
41 | border-top-left-radius: 1vh;
42 | display: flex;
43 | flex-direction: column;
44 | }
45 |
46 | .dispatch-item-true {
47 | background-color: rgba(21, 38, 56, 0.80);
48 | }
49 |
50 | .dispatch-item-false {
51 | background-color: rgba(56, 21, 21, 0.8);
52 | }
53 |
54 | .dispatch-item-officer {
55 | background-color: rgb(97, 3, 11);
56 | }
57 |
58 | .top-info-holder {
59 | width: 95%;
60 | height: fit-content;
61 | margin: auto;
62 | margin-top: .75vh;
63 | margin-left: .75vh;
64 | align-items: center;
65 | display: flex;
66 | flex-direction: row;
67 | color: white;
68 | font-size: 1.25vh;
69 | font-weight: bold;
70 | white-space: nowrap;
71 | }
72 |
73 | .call-id {
74 | background-color: #950909;
75 | padding: .35vh;
76 | padding-bottom: .4vh;
77 | padding-left: 1.75vh;
78 | padding-right: 1.75vh;
79 | margin-top: auto;
80 | margin-bottom: auto;
81 | margin-left: 0vh;
82 | margin-right: .4vh;
83 | border-radius: 1vh;
84 | text-align: center;
85 | }
86 |
87 | .call-code {
88 | background-color: #097C95;
89 | padding: .35vh;
90 | padding-bottom: .4vh;
91 | padding-left: 1.75vh;
92 | padding-right: 1.75vh;
93 | margin-top: auto;
94 | margin-bottom: auto;
95 | margin-left: 0vh;
96 | margin-right: .5vh;
97 | border-radius: 1vh;
98 | text-align: center;
99 | }
100 |
101 | .call-name {
102 | margin-top: auto;
103 | margin-bottom: auto;
104 | margin-left: 0vh;
105 | margin-right: auto;
106 | border-radius: 1vh;
107 | text-align: left;
108 | font-size: 1.7vh;
109 | overflow: hidden;
110 | text-overflow: ellipsis;
111 | }
112 |
113 | .bottom-info-holder {
114 | width: 95%;
115 | height: fit-content;
116 | margin: auto;
117 | margin-top: .75vh;
118 | margin-bottom: .6vh;
119 | align-items: center;
120 | display: flex;
121 | flex-direction: column;
122 | color: white;
123 | font-size: 1.2vh;
124 | font-weight: bold;
125 | white-space: nowrap;
126 | text-align: left;
127 | }
128 |
129 | .call-bottom-info {
130 | margin: auto;
131 | margin-left: 0vh;
132 | margin-bottom: .5vh;
133 | text-align: left;
134 | overflow:wrap;
135 | white-space:normal;
136 | }
137 |
138 | .line {
139 | background-color: rgba(128, 128, 128, 0.1);
140 | height: 0.05vh;
141 | width: 100%;
142 | margin-top: 0.1vh;
143 | margin-bottom: 0.1vh
144 | }
145 |
146 | .call-bottom-information {
147 | margin-top: 0.5vh;
148 | }
149 |
150 | .fas {
151 | margin-right: .5vh;
152 | }
153 |
154 | .fab {
155 | margin-right: .5vh;
156 | }
157 |
158 | .far {
159 | margin-right: .5vh;
160 | }
161 |
162 | .fab {
163 | margin-right: .5vh;
164 | }
165 |
166 | .priority-1 {
167 | background: linear-gradient(270deg, #b70000, #0a24b0);
168 | background-size: 400% 400%;
169 | animation: gradient 3s ease infinite;
170 | background-color: #097b9500;
171 | }
172 |
173 | .priority-2 {
174 | background-color: #ce7808;
175 | }
176 |
177 | @keyframes gradient {
178 | 0%{background-position:0% 50%}
179 | 50%{background-position:100% 50%}
180 | 100%{background-position:0% 50%}
181 | }
--------------------------------------------------------------------------------
/ui/svelte.config.js:
--------------------------------------------------------------------------------
1 | import sveltePreprocess from 'svelte-preprocess'
2 |
3 | export default {
4 | // Consult https://github.com/sveltejs/svelte-preprocess
5 | // for more information about preprocessors
6 | preprocess: sveltePreprocess()
7 | }
8 |
--------------------------------------------------------------------------------
/ui/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | darkmode: true,
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{svelte,js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | extend: {
9 | colors: {
10 | primary: '#232B33',
11 | secondary: '#25303B',
12 | tertiary: '#2F3C47',
13 | priority_primary: '#332328',
14 | priority_secondary: '#3B252F',
15 | priority_tertiary: '#472F39',
16 | priority_quaternary: '#9A003A',
17 | accent: '#2284d9',
18 | accent_green: '#00A379',
19 | accent_dark_green: '#008563',
20 | accent_cyan: '#0098A3',
21 | accent_red: '#FF004E',
22 | accent_dark_red: '#850032',
23 | border_primary: '#373a40',
24 | hover_secondary: '#2c2e33',
25 | }
26 | },
27 | },
28 | plugins: [],
29 | }
30 |
--------------------------------------------------------------------------------
/ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/svelte/tsconfig.json",
3 | "compilerOptions": {
4 | "target": "esnext",
5 | "useDefineForClassFields": true,
6 | "module": "esnext",
7 | "resolveJsonModule": true,
8 | "baseUrl": ".",
9 | "paths": {
10 | "@assets/*": ["src/assets/*"],
11 | "@components/*": ["src/components/*"],
12 | "@providers/*": ["src/providers/*"],
13 | "@store/*": ["src/store/*"],
14 | "@utils/*": ["src/utils/*"],
15 | "@typings/*": ["src/typings/*"],
16 | "@layout/*": ["src/layout/*"],
17 | "@pages/*": ["src/pages/*"],
18 | },
19 | /**
20 | * Typecheck JS in `.svelte` and `.js` files by default.
21 | * Disable checkJs if you'd like to use dynamic types in JS.
22 | * Note that setting allowJs false does not prevent the use
23 | * of JS in `.svelte` files.
24 | */
25 | "allowJs": true,
26 | "checkJs": true
27 | },
28 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"]
29 | }
30 |
--------------------------------------------------------------------------------
/ui/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import { svelte } from '@sveltejs/vite-plugin-svelte'
3 | import postcss from './postcss.config.js';
4 | import { resolve } from "path";
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | css: {
9 | postcss,
10 | },
11 | plugins: [svelte({
12 | /* plugin options */
13 | })],
14 | base: './', // fivem nui needs to have local dir reference
15 | resolve: {
16 | alias: {
17 | "@assets": resolve("./src/assets"),
18 | "@components": resolve("./src/components"),
19 | "@providers": resolve("./src/providers"),
20 | "@store": resolve("./src/store"),
21 | "@utils": resolve("./src/utils"),
22 | "@typings": resolve("./src/typings"),
23 | "@layout": resolve("./src/layout"),
24 | "@pages": resolve("./src/pages"),
25 | },
26 | },
27 | build: {
28 | emptyOutDir: true,
29 | outDir: '../html',
30 | assetsDir: './',
31 | rollupOptions: {
32 | output: {
33 | // By not having hashes in the name, you don't have to update the manifest, yay!
34 | entryFileNames: `[name].js`,
35 | chunkFileNames: `[name].js`,
36 | assetFileNames: `[name].[ext]`
37 | }
38 | }
39 | }
40 |
41 | })
42 |
--------------------------------------------------------------------------------