diff --git a/ND_ShotSpotter/config_client.lua b/ND_ShotSpotter/config_client.lua new file mode 100644 index 0000000..37a0cf0 --- /dev/null +++ b/ND_ShotSpotter/config_client.lua @@ -0,0 +1,61 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +config = { + shotSpotterDelay = 10, -- delay (in seconds) when cops will receive a notification after there has been a shooting. + shotSpotterTimer = 120, -- How long should the shot spotter stay on the map. + shotSpotterCooldown = 30, -- Cooldown for the next time a player can trigger it again. + + shotSpotterUsePostal = true, -- if you're using the nearest postal script turn this to true. + postalResourceName = "nearest-postal", -- the resource name of the nearest postal script, this is used for the export. + + -- This is the departments that will receive the shot spotter alerts. + receiveAlerts = { + "SAHP", + "LSPD", + "BCSO" + }, + + -- Weapon that won't be triggered by the shot spotter. + weaponBlackList = { + "weapon_flaregun", + "weapon_stungun_mp", + "weapon_grenade", + "weapon_bzgas", + "weapon_molotov", + "weapon_stickybomb", + "weapon_proxmine", + "weapon_snowball", + "weapon_pipebomb", + "weapon_ball", + "weapon_smokegrenade", + "weapon_flare", + "weapon_petrolcan", + "weapon_fireextinguisher", + "weapon_hazardcan", + "weapon_fertilizercan" + }, + + useRealisticShotSpotter = false, -- this is if you want to enable the shot spotter only when the player is inside one of the zones below. The zones are in the city and a little around it. + realisticShotSpotterLocations = { + {x = 653.4214, y = -648.7440, z = 57.1897}, + {x = 1015.9837, y = -255.2573, z = 85.5857}, + {x = 329.9973, y = 288.9604, z = 120.1029}, + {x = -202.7689, y = -327.3490, z = 66.0497}, + {x = 31.3205, y = -875.2959, z = 31.4629}, + {x = 70.1372, y = -1718.3291, z = 34.2056}, + {x = 1196.9178, y = -1624.6641, z = 50.3403}, + {x = -852.9095, y = -1215.8782, z = 9.2463}, + {x = -932.7648, y = -448.8844, z = 42.9436}, + {x = -1713.6848, y = 478.4267, z = 130.3795}, + {x = -596.5602, y = 515.0753, z = 109.675}, + {x = 716.6274, y = -1958.7434, z = 44.7564} + }, + + testing = false -- if you're adding zones above and want to enable the blips on the map to see where the zone is then turn this on, otherwise turn it off. +} + +function notify(message) + BeginTextCommandThefeedPost("STRING") + AddTextComponentSubstringPlayerName(message) + EndTextCommandThefeedPostTicker(0,1) +end \ No newline at end of file diff --git a/ND_ShotSpotter/config_server.lua b/ND_ShotSpotter/config_server.lua new file mode 100644 index 0000000..9e83a01 --- /dev/null +++ b/ND_ShotSpotter/config_server.lua @@ -0,0 +1,4 @@ +server_config = { + useDiscordLogging = false, + discordWebhook = "", +} \ No newline at end of file diff --git a/ND_ShotSpotter/source/client.lua b/ND_ShotSpotter/source/client.lua new file mode 100644 index 0000000..a84cc0f --- /dev/null +++ b/ND_ShotSpotter/source/client.lua @@ -0,0 +1,157 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +local alreadyShot = false +local setRoute = false +local route = false + +-- Suppressors hash, if a weapon has these then it won't trigger the shot spotter. +local suppresors = { + "0x65EA7EBB", -- Pistol. + "0x837445AA", -- Carbine Rifle, Advanced Rifle, Bullpup Rifle, Assault Shotgun, Marksman Rifle. + "0xA73D4664", -- .50 Pistol, Micro SMG, Assault SMG, Assault Rifle, Special Carbine, Bullpup Shotgun, Heavy Shotgun, Sniper Rifle. + "0xC304849A", -- Combat Pistol, AP Pistol, Heavy Pistol, Vintage Pistol, SMG. + "0xE608B35E" -- Pump Shotgun. +} + +-- check if the players location is inside the shot spotter locations, this will only be used in the code when realistic shot spotter is turned on. +function isInShotSpotterLocation(pedCoords) + for _, location in pairs(config.realisticShotSpotterLocations) do + if #(pedCoords - vector3(location.x, location.y, location.z)) < 450.0 then + return true + end + end + return false +end + +-- check if the players department can receive shot spotter alerts. +function isCop() + local selectedDepartment = exports["ND_Core"]:getCharacterInfo().department + for _, department in pairs(config.receiveAlerts) do + if department == selectedDepartment then + return true + end + end + return false +end + +function triggerShotSpotter(ped) + local pedCoords = GetEntityCoords(ped) + if config.shotSpotterUsePostal then + postal = exports[config.postalResourceName]:getPostal() + else + postal = false + end + + -- if the player isn't in the realistic shot spotter locations then the shot spotter won't trigger. + if config.useRealisticShotSpotter and not isInShotSpotterLocation(pedCoords) then + return + end + + -- if the player has a blacklisted weapon then the shot spotter won't trigger. + local selectedWeapon = GetSelectedPedWeapon(ped) + for _, weapon in pairs(config.weaponBlackList) do + if GetHashKey(weapon) == selectedWeapon then + return + end + end + + -- if the player has a suppresor attached to their weapon then the shot spotter won't trigger. + for _, suppresor in pairs(suppresors) do + if HasPedGotWeaponComponent(ped, selectedWeapon, tonumber(suppresor)) then + return + end + end + + -- if the player is a cop then they won't trigger the shotspotter. + if isCop() then + return + end + + -- the alreadyShot variable is used for checking if the player has already shot and to add a cooldown until it turns to false. + if alreadyShot then return end + alreadyShot = true + Citizen.Wait(config.shotSpotterDelay * 1000) + local zoneName = GetLabelText(GetNameOfZone(pedCoords.x, pedCoords.y, pedCoords.z)) + local street = GetStreetNameFromHashKey(GetStreetNameAtCoord(pedCoords.x, pedCoords.y, pedCoords.z)) + TriggerServerEvent("ND_ShotSpotter:Trigger", street, pedCoords, postal, zoneName) + Citizen.Wait(config.shotSpotterCooldown * 1000) + alreadyShot = false +end + +-- Check if the player is shooting. +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + local ped = PlayerPedId() + if IsPedShooting(ped) then + triggerShotSpotter(ped) + end + end +end) + +RegisterNetEvent("ND_ShotSpotter:Report") +AddEventHandler("ND_ShotSpotter:Report", function(street, pedCoords, postal) + -- if the player isn't a cop then they won't receive the alert. + if not isCop() then + return + end + + -- if the player is close to the shots then they won't receive a shot spotter alert. + if #(GetEntityCoords(PlayerPedId()) - pedCoords) < 50.0 then + return + end + + blip = AddBlipForCoord(pedCoords.x, pedCoords.y, pedCoords.z) + setRoute = true + route = false + TriggerEvent("ND_shotSpotter:setRoute") + notify("~w~Press ~g~G ~w~to respond to the latest shot spotter.") + SetBlipSprite(blip, 161) + SetBlipAsShortRange(blip, true) + BeginTextCommandSetBlipName("STRING") + SetBlipColour(blip, 1) + if not postal then + msg = "Shotspotter detected in " .. street .. "." + AddTextComponentString("Shot Spotter: " .. street) + else + msg = "Shotspotter detected in " .. street .. ", postal: " .. postal .. "." + AddTextComponentString("Shot Spotter: " .. street .. ", postal: " .. postal) + end + TriggerEvent("chat:addMessage", { + color = {255, 0, 0}, + args = {"^*Dispatch ", msg} + }) + EndTextCommandSetBlipName(blip) + Citizen.Wait(config.shotSpotterTimer * 1000) + RemoveBlip(blip) + setRoute = false + route = false +end) + +-- set route to latest shot spotter location. +RegisterNetEvent("ND_shotSpotter:setRoute") +AddEventHandler("ND_shotSpotter:setRoute", function() + while setRoute do + Citizen.Wait(0) + if IsControlJustPressed(0, 113) then + if route then + route = false + SetBlipRoute(blip, route) + else + route = true + SetBlipRoute(blip, route) + SetBlipRouteColour(blip, 1) + end + end + end +end) + +if config.testing then + Citizen.CreateThread(function() + Citizen.Wait(0) + for k, v in pairs(config.realisticShotSpotterLocations) do + k = AddBlipForRadius(v.x, v.y, v.z, 450.0) + SetBlipAlpha(k, 100) + end + end) +end \ No newline at end of file diff --git a/ND_ShotSpotter/source/server.lua b/ND_ShotSpotter/source/server.lua new file mode 100644 index 0000000..a4a2186 --- /dev/null +++ b/ND_ShotSpotter/source/server.lua @@ -0,0 +1,36 @@ +-- For support join my discord: https://discord.gg/Z9Mxu72zZ6 + +RegisterNetEvent("ND_ShotSpotter:Trigger") +AddEventHandler("ND_ShotSpotter:Trigger", function(street, pedCoords, postal, zoneName) + if server_config.useDiscordLogging then + local embed = { + { + title = "ShotSpotter Alert", + description = "Gunshots detected on " .. zoneName .. ", " .. street .. "." , + fields = { + { + name = "Location:", + value = zoneName .. ", **" .. street .. "**" + } + }, + footer = { + icon_url = "https://i.imgur.com/notBtrZ.png", + text = "Created by Andyyy#7666" + }, + thumbnail = { + url = "https://i.imgur.com/BTbxJZu.png" + }, + color = 16722976 + } + } + if postal then + embed[1].description = "Gunshots detected on " .. zoneName .. ", " .. street .. " (" .. postal .. ")." + embed[1].fields[2] = { + name = "Postal:", + value = postal + } + end + PerformHttpRequest(server_config.discordWebhook, function(err, text, headers) end, 'POST', json.encode({username = "ND Shotspotter", embeds = embed}), {["Content-Type"] = "application/json"}) + end + TriggerClientEvent("ND_ShotSpotter:Report", -1, street, pedCoords, postal) +end) \ No newline at end of file