+
+ Dashboard
+ Profiles
+ Incidents
+ Reports
+ BOLOs
+ Roster
+ DMV
+ Staff Logs
+ From 2d99ad8af65bfea500441a2696bbfc1e97ef9eba Mon Sep 17 00:00:00 2001 From: MonkeyWhisper <82112471+MonkeyWhisper@users.noreply.github.com> Date: Fri, 1 Apr 2022 15:42:45 -0700 Subject: [PATCH] Added original files Added files from the original fork from Flaws, doing this because we could no start issues because it was a fork. --- client/cl_impound.lua | 75 + client/main.lua | 853 ++++++ fxmanifest.lua | 43 + mdt.sql | 111 + readme.md | 10 + server/dbm.lua | 153 + server/main.lua | 1265 ++++++++ server/utils.lua | 51 + shared/config.lua | 637 ++++ ui/app.js | 5048 +++++++++++++++++++++++++++++++ ui/dashboard.html | 755 +++++ ui/img/court.png | Bin 0 -> 55525 bytes ui/img/ems_badge.png | Bin 0 -> 15935 bytes ui/img/ems_badge_zonah.png | Bin 0 -> 21158 bytes ui/img/female.png | Bin 0 -> 50333 bytes ui/img/male.png | Bin 0 -> 73641 bytes ui/img/not-found.jpg | Bin 0 -> 10656 bytes ui/img/profile_pic.png | Bin 0 -> 54820 bytes ui/img/sasp_badge.png | Bin 0 -> 36832 bytes ui/img/warrant_pfp.png | Bin 0 -> 19434 bytes ui/style.css | 5854 ++++++++++++++++++++++++++++++++++++ 21 files changed, 14855 insertions(+) create mode 100644 client/cl_impound.lua create mode 100644 client/main.lua create mode 100644 fxmanifest.lua create mode 100644 mdt.sql create mode 100644 readme.md create mode 100644 server/dbm.lua create mode 100644 server/main.lua create mode 100644 server/utils.lua create mode 100644 shared/config.lua create mode 100644 ui/app.js create mode 100644 ui/dashboard.html create mode 100644 ui/img/court.png create mode 100644 ui/img/ems_badge.png create mode 100644 ui/img/ems_badge_zonah.png create mode 100644 ui/img/female.png create mode 100644 ui/img/male.png create mode 100644 ui/img/not-found.jpg create mode 100644 ui/img/profile_pic.png create mode 100644 ui/img/sasp_badge.png create mode 100644 ui/img/warrant_pfp.png create mode 100644 ui/style.css diff --git a/client/cl_impound.lua b/client/cl_impound.lua new file mode 100644 index 00000000..288b236c --- /dev/null +++ b/client/cl_impound.lua @@ -0,0 +1,75 @@ +local currentGarage = 1 + +local function doCarDamage(currentVehicle, veh) + local smash = false + local damageOutside = false + local damageOutside2 = false + local engine = veh.engine + 0.0 + local body = veh.body + 0.0 + + if engine < 200.0 then engine = 200.0 end + if engine > 1000.0 then engine = 950.0 end + if body < 150.0 then body = 150.0 end + if body < 950.0 then smash = true end + if body < 920.0 then damageOutside = true end + if body < 920.0 then damageOutside2 = true end + + Citizen.Wait(100) + SetVehicleEngineHealth(currentVehicle, engine) + + if smash then + SmashVehicleWindow(currentVehicle, 0) + SmashVehicleWindow(currentVehicle, 1) + SmashVehicleWindow(currentVehicle, 2) + SmashVehicleWindow(currentVehicle, 3) + SmashVehicleWindow(currentVehicle, 4) + end + + if damageOutside then + SetVehicleDoorBroken(currentVehicle, 1, true) + SetVehicleDoorBroken(currentVehicle, 6, true) + SetVehicleDoorBroken(currentVehicle, 4, true) + end + + if damageOutside2 then + SetVehicleTyreBurst(currentVehicle, 1, false, 990.0) + SetVehicleTyreBurst(currentVehicle, 2, false, 990.0) + SetVehicleTyreBurst(currentVehicle, 3, false, 990.0) + SetVehicleTyreBurst(currentVehicle, 4, false, 990.0) + end + + if body < 1000 then + SetVehicleBodyHealth(currentVehicle, 985.1) + end +end + +local function TakeOutImpound(vehicle) + local coords = Config.ImpoundLocations[currentGarage] + if coords then + QBCore.Functions.SpawnVehicle(vehicle.vehicle, function(veh) + QBCore.Functions.TriggerCallback('qb-garage:server:GetVehicleProperties', function(properties) + QBCore.Functions.SetVehicleProperties(veh, properties) + SetVehicleNumberPlateText(veh, vehicle.plate) + SetEntityHeading(veh, coords.w) + exports[Config.Fuel]:SetFuel(veh, vehicle.fuel) + doCarDamage(veh, vehicle) + TriggerServerEvent('police:server:TakeOutImpound',vehicle.plate) + TriggerEvent("vehiclekeys:client:SetOwner", QBCore.Functions.GetPlate(veh)) + SetVehicleEngineOn(veh, true, true) + end, vehicle.plate) + end, coords, true) + end +end + +RegisterNetEvent('qb-mdt:client:TakeOutImpound', function(data) + local pos = GetEntityCoords(PlayerPedId()) + currentGarage = data.currentSelection + local takeDist = Config.ImpoundLocations[data.currentSelection] + takeDist = vector3(takeDist.x, takeDist.y, takeDist.z) + if #(pos - takeDist) <= 15.0 then + local vehicle = data.vehicle + TakeOutImpound(data) + else + QBCore.Functions.Notify("You are too far away from the impound location!") + end +end) \ No newline at end of file diff --git a/client/main.lua b/client/main.lua new file mode 100644 index 00000000..a7fd2b20 --- /dev/null +++ b/client/main.lua @@ -0,0 +1,853 @@ +QBCore = exports['qb-core']:GetCoreObject() +local PlayerData = {} +local CurrentCops = 0 +local isOpen = false +local callSign = "" + +local tablet = 0 +local tabletDict = "amb@code_human_in_bus_passenger_idles@female@tablet@base" +local tabletAnim = "base" +local tabletProp = `prop_cs_tablet` +local tabletBone = 60309 +local tabletOffset = vector3(0.03, 0.002, -0.0) +local tabletRot = vector3(10.0, 160.0, 0.0) + +-- Events from qbcore +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + PlayerData = QBCore.Functions.GetPlayerData() + callSign = PlayerData.metadata.callsign +end) + +RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) + PlayerData.job = JobInfo +end) + +RegisterNetEvent('QBCore:Client:OnGangUpdate', function(GangInfo) + PlayerData.gang = GangInfo +end) + +RegisterNetEvent('police:SetCopCount', function(amount) + CurrentCops = amount +end) + +RegisterNetEvent('QBCore:Player:SetPlayerData', function(val) + PlayerData = val +end) + +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + Wait(150) + PlayerData = QBCore.Functions.GetPlayerData() + callSign = PlayerData.metadata.callsign +end) + +--==================================================================================== +------------------------------------------ +-- Functions -- +------------------------------------------ +--====================================================================================\ + +RegisterKeyMapping('mdt', 'Open Police MDT', 'keyboard', 'k') + +RegisterCommand('mdt', function() + if GetJobType(PlayerData.job.name) ~= nil then + TriggerServerEvent('mdt:server:openMDT') + end +end, false) + +Citizen.CreateThread(function() + TriggerEvent('chat:addSuggestion', '/mdt', 'Open the emergency services MDT', {}) +end) + +local function doAnimation() + if not isOpen then return end + -- Animation + RequestAnimDict(tabletDict) + while not HasAnimDictLoaded(tabletDict) do Citizen.Wait(100) end + -- Model + RequestModel(tabletProp) + while not HasModelLoaded(tabletProp) do Citizen.Wait(100) end + + local plyPed = PlayerPedId() + local tabletObj = CreateObject(tabletProp, 0.0, 0.0, 0.0, true, true, false) + local tabletBoneIndex = GetPedBoneIndex(plyPed, tabletBone) + + AttachEntityToEntity(tabletObj, plyPed, tabletBoneIndex, tabletOffset.x, tabletOffset.y, tabletOffset.z, tabletRot.x, tabletRot.y, tabletRot.z, true, false, false, false, 2, true) + SetModelAsNoLongerNeeded(tabletProp) + + CreateThread(function() + while isOpen do + Wait(0) + if not IsEntityPlayingAnim(plyPed, tabletDict, tabletAnim, 3) then + TaskPlayAnim(plyPed, tabletDict, tabletAnim, 3.0, 3.0, -1, 49, 0, 0, 0, 0) + end + end + + + ClearPedSecondaryTask(plyPed) + Citizen.Wait(250) + DetachEntity(tabletObj, true, false) + DeleteEntity(tabletObj) + end) +end + + +local function CurrentDuty(duty) + if duty == 1 then + return "10-41" + end + return "10-42" +end + +local function EnableGUI(enable) + SetNuiFocus(enable, enable) + SendNUIMessage({ type = "show", enable = enable, job = PlayerData.job.name, rosterLink = Config.RosterLink[PlayerData.job.name] }) + isOpen = enable + doAnimation() +end + +local function RefreshGUI() + SetNuiFocus(false, false) + SendNUIMessage({ type = "show", enable = false, job = PlayerData.job.name, rosterLink = Config.RosterLink[PlayerData.job.name] }) + isOpen = false +end + + +--==================================================================================== +------------------------------------------ +-- MAIN PAGE -- +------------------------------------------ +--==================================================================================== + + +RegisterCommand("restartmdt", function(source, args, rawCommand) + RefreshGUI() +end, false) + +RegisterNUICallback("deleteBulletin", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:deleteBulletin', id, data.title) + cb(true) +end) + +RegisterNUICallback("newBulletin", function(data, cb) + local title = data.title + local info = data.info + local time = data.time + TriggerServerEvent('mdt:server:NewBulletin', title, info, time) + cb(true) +end) + +RegisterNUICallback('escape', function(data, cb) + EnableGUI(false) + cb(true) +end) + +RegisterNetEvent('mdt:client:dashboardbulletin', function(sentData) + SendNUIMessage({ type = "bulletin", data = sentData }) +end) + +RegisterNetEvent('mdt:client:dashboardWarrants', function() + QBCore.Functions.TriggerCallback("mdt:server:getWarrants", function(data) + if data then + SendNUIMessage({ type = "warrants", data = data }) + end + end) + -- SendNUIMessage({ type = "warrants",}) +end) + +RegisterNUICallback("getAllDashboardData", function(data, cb) + TriggerEvent("mdt:client:dashboardWarrants") + cb(true) +end) + + +RegisterNetEvent('mdt:client:dashboardReports', function(sentData) + SendNUIMessage({ type = "reports", data = sentData }) +end) + +RegisterNetEvent('mdt:client:dashboardCalls', function(sentData) + SendNUIMessage({ type = "calls", data = sentData }) +end) + +RegisterNetEvent('mdt:client:newBulletin', function(ignoreId, sentData, job) + if ignoreId == GetPlayerServerId(PlayerId()) then return end; + if PlayerData.job.name == 'police' then + SendNUIMessage({ type = "newBulletin", data = sentData }) + elseif PlayerData.job.name == 'ambulance' then + SendNUIMessage({ type = "newBulletin", data = sentData }) + end +end) + +RegisterNetEvent('mdt:client:deleteBulletin', function(ignoreId, sentData, job) + if ignoreId == GetPlayerServerId(PlayerId()) then return end; + if PlayerData.job.name == 'police' or PlayerData.job.name == 'ambulance'then + SendNUIMessage({ type = "deleteBulletin", data = sentData }) + end +end) + +RegisterNetEvent('mdt:client:open', function(bulletin, activeUnits) + EnableGUI(true) + local x, y, z = table.unpack(GetEntityCoords(PlayerPedId())) + + local currentStreetHash, intersectStreetHash = GetStreetNameAtCoord(x, y, z) + local currentStreetName = GetStreetNameFromHashKey(currentStreetHash) + local intersectStreetName = GetStreetNameFromHashKey(intersectStreetHash) + local zone = tostring(GetNameOfZone(x, y, z)) + local area = GetLabelText(zone) + local playerStreetsLocation = area + + if not zone then zone = "UNKNOWN" end; + + if intersectStreetName ~= nil and intersectStreetName ~= "" then playerStreetsLocation = currentStreetName .. ", " .. intersectStreetName .. ", " .. area + elseif currentStreetName ~= nil and currentStreetName ~= "" then playerStreetsLocation = currentStreetName .. ", " .. area + else playerStreetsLocation = area end + + -- local grade = PlayerData.job.grade.name + + SendNUIMessage({ type = "data", activeUnits = activeUnits, name = "Welcome, " ..PlayerData.job.grade.name..' '..PlayerData.charinfo.lastname:sub(1,1):upper()..PlayerData.charinfo.lastname:sub(2), location = playerStreetsLocation, fullname = PlayerData.charinfo.firstname..' '..PlayerData.charinfo.lastname, bulletin = bulletin }) + TriggerEvent("mdt:client:dashboardWarrants") +end) + +RegisterNetEvent('mdt:client:exitMDT', function() + EnableGUI(false) +end) + +--==================================================================================== +------------------------------------------ +-- PROFILE PAGE -- +------------------------------------------ +--==================================================================================== + +RegisterNUICallback("searchProfiles", function(data, cb) + local p = promise.new() + + QBCore.Functions.TriggerCallback('mdt:server:SearchProfile', function(result) + p:resolve(result) + end, data.name) + + local data = Citizen.Await(p) + + cb(data) +end) + + +RegisterNetEvent('mdt:client:searchProfile', function(sentData, isLimited) + SendNUIMessage({ type = "profiles", data = sentData, isLimited = isLimited }) +end) + +RegisterNUICallback("saveProfile", function(data, cb) + local profilepic = data.pfp + local information = data.description + local cid = data.id + local fName = data.fName + local sName = data.sName + local tags = data.tags + local gallery = data.gallery + local fingerprint = data.fingerprint + local licenses = data.licenses + + TriggerServerEvent("mdt:server:saveProfile", profilepic, information, cid, fName, sName, tags, gallery, fingerprint, licenses) + cb(true) +end) + +RegisterNUICallback("getProfileData", function(data, cb) + local id = data.id + local p = nil + local getProfileDataPromise = function(data) + if p then return end + p = promise.new() + QBCore.Functions.TriggerCallback('mdt:server:GetProfileData', function(result) + p:resolve(result) + end, data) + return Citizen.Await(p) + end + local pP = nil + local result = getProfileDataPromise(id) + + --[[ local getProfileProperties = function(data) + if pP then return end + pP = promise.new() + QBCore.Functions.TriggerCallback('qb-phone:server:MeosGetPlayerHouses', function(result) + pP:resolve(result) + end, data) + return Citizen.Await(pP) + end + local propertiesResult = getProfileProperties(id) + result.properties = propertiesResult + ]] + local vehicles=result.vehicles + for i=1,#vehicles do + local vehicle=result.vehicles[i] + result.vehicles[i]['model'] = GetLabelText(GetDisplayNameFromVehicleModel(vehicle['vehicle'])) + end + p = nil + return cb(result) +end) + +RegisterNUICallback("newTag", function(data, cb) + if data.id ~= "" and data.tag ~= "" then + TriggerServerEvent('mdt:server:newTag', data.id, data.tag) + end + cb(true) +end) + +RegisterNUICallback("removeProfileTag", function(data, cb) + local cid = data.cid + local tagtext = data.text + TriggerServerEvent('mdt:server:removeProfileTag', cid, tagtext) + cb(removeProfileTag) +end) + +RegisterNUICallback("updateLicence", function(data, cb) + local type = data.type + local status = data.status + local cid = data.cid + TriggerServerEvent('mdt:server:updateLicense', cid, type, status) + cb(true) +end) + +RegisterNUICallback("searchIncidents", function(data, cb) + local incident = data.incident + TriggerServerEvent('mdt:server:searchIncidents', incident) + cb(true) +end) + +RegisterNUICallback("getIncidentData", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:getIncidentData', id) + cb(true) +end) + +RegisterNUICallback("incidentSearchPerson", function(data, cb) + local name = data.name + TriggerServerEvent('mdt:server:incidentSearchPerson', name ) + cb(true) +end) + +RegisterNetEvent('mdt:client:getProfileData', function(sentData, isLimited) + if not isLimited then + local vehicles = sentData['vehicles'] + for i=1, #vehicles do + sentData['vehicles'][i]['plate'] = string.upper(sentData['vehicles'][i]['plate']) + local tempModel = vehicles[i]['model'] + if tempModel and tempModel ~= "Unknown" then + local DisplayNameModel = GetDisplayNameFromVehicleModel(tempModel) + local LabelText = GetLabelText(DisplayNameModel) + if LabelText == "NULL" then LabelText = DisplayNameModel end + sentData['vehicles'][i]['model'] = LabelText + end + end + end + SendNUIMessage({ type = "profileData", data = sentData, isLimited = isLimited }) +end) + +RegisterNetEvent('mdt:client:getIncidents', function(sentData) + SendNUIMessage({ type = "incidents", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getIncidentData', function(sentData, sentConvictions) + SendNUIMessage({ type = "incidentData", data = sentData, convictions = sentConvictions }) +end) + +RegisterNetEvent('mdt:client:incidentSearchPerson', function(sentData) + SendNUIMessage({ type = "incidentSearchPerson", data = sentData }) +end) + + +RegisterNUICallback('SetHouseLocation', function(data, cb) + local coords = {} + for word in data.coord[1]:gmatch('[^,%s]+') do + coords[#coords+1] = tonumber(word) + end + SetNewWaypoint(coords[1], coords[2]) + QBCore.Functions.Notify('GPS has been set!', 'success') +end) + +--==================================================================================== +------------------------------------------ +-- BOLO PAGE -- +------------------------------------------ +--==================================================================================== + +RegisterNUICallback("searchBolos", function(data, cb) + local searchVal = data.searchVal + TriggerServerEvent('mdt:server:searchBolos', searchVal) + cb(true) +end) + +RegisterNUICallback("getAllBolos", function(data, cb) + TriggerServerEvent('mdt:server:getAllBolos') + cb(true) +end) + +RegisterNUICallback("getAllIncidents", function(data, cb) + TriggerServerEvent('mdt:server:getAllIncidents') + cb(true) +end) + +RegisterNUICallback("getBoloData", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:getBoloData', id) + cb(true) +end) + +RegisterNUICallback("newBolo", function(data, cb) + local existing = data.existing + local id = data.id + local title = data.title + local plate = data.plate + local owner = data.owner + local individual = data.individual + local detail = data.detail + local tags = data.tags + local gallery = data.gallery + local officers = data.officers + local time = data.time + TriggerServerEvent('mdt:server:newBolo', existing, id, title, plate, owner, individual, detail, tags, gallery, officers, time) + cb(true) +end) + +RegisterNUICallback("deleteBolo", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:deleteBolo', id) + cb(true) +end) + +RegisterNUICallback("deleteICU", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:deleteICU', id) + cb(true) +end) + +RegisterNetEvent('mdt:client:getBolos', function(sentData) + SendNUIMessage({ type = "bolos", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getAllIncidents', function(sentData) + SendNUIMessage({ type = "incidents", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getAllBolos', function(sentData) + SendNUIMessage({ type = "bolos", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getBoloData', function(sentData) + SendNUIMessage({ type = "boloData", data = sentData }) +end) + +RegisterNetEvent('mdt:client:boloComplete', function(sentData) + SendNUIMessage({ type = "boloComplete", data = sentData }) +end) + +--==================================================================================== +------------------------------------------ +-- REPORTS PAGE -- +------------------------------------------ +--==================================================================================== + +RegisterNUICallback("getAllReports", function(data, cb) + TriggerServerEvent('mdt:server:getAllReports') + cb(true) +end) + +RegisterNUICallback("getReportData", function(data, cb) + local id = data.id + TriggerServerEvent('mdt:server:getReportData', id) + cb(true) +end) + +RegisterNUICallback("searchReports", function(data, cb) + local name = data.name + TriggerServerEvent('mdt:server:searchReports', name) + cb(true) +end) + +RegisterNUICallback("newReport", function(data, cb) + local existing = data.existing + local id = data.id + local title = data.title + local reporttype = data.type + local details = data.details + local tags = data.tags + local gallery = data.gallery + local officers = data.officers + local civilians = data.civilians + local time = data.time + + TriggerServerEvent('mdt:server:newReport', existing, id, title, reporttype, details, tags, gallery, officers, civilians, time) + cb(true) +end) + +RegisterNetEvent('mdt:client:getAllReports', function(sentData) + SendNUIMessage({ type = "reports", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getReportData', function(sentData) + SendNUIMessage({ type = "reportData", data = sentData }) +end) + +RegisterNetEvent('mdt:client:reportComplete', function(sentData) + SendNUIMessage({ type = "reportComplete", data = sentData }) +end) + +--==================================================================================== +------------------------------------------ +-- DMV PAGE -- +------------------------------------------ +--==================================================================================== +RegisterNUICallback("searchVehicles", function(data, cb) + + local p = promise.new() + + QBCore.Functions.TriggerCallback('mdt:server:SearchVehicles', function(result) + p:resolve(result) + end, data.name) + + local result = Citizen.Await(p) + for i=1, #result do + local vehicle = result[i] + local mods = json.decode(result[i].mods) + result[i]['plate'] = string.upper(result[i]['plate']) + result[i]['color'] = Config.ColorInformation[mods['color1']] + result[i]['colorName'] = Config.ColorNames[mods['color1']] + result[i]['model'] = GetLabelText(GetDisplayNameFromVehicleModel(vehicle['vehicle'])) + end + cb(result) + +end) + +RegisterNUICallback("getVehicleData", function(data, cb) + local plate = data.plate + TriggerServerEvent('mdt:server:getVehicleData', plate) + cb(true) +end) + +RegisterNUICallback("saveVehicleInfo", function(data, cb) + local dbid = data.dbid + local plate = data.plate + local imageurl = data.imageurl + local notes = data.notes + local stolen = data.stolen + local code5 = data.code5 + local impound = data.impound + local JobType = GetJobType(PlayerData.job.name) + if JobType == 'police' and impound.impoundChanged == true then + if impound.inpoundActive then + local found = 0 + local plate = string.upper(string.gsub(data['plate'], "^%s*(.-)%s*$", "%1")) + local vehicles = GetGamePool('CVehicle') + + for k,v in pairs(vehicles) do + local plt = string.upper(string.gsub(GetVehicleNumberPlateText(v), "^%s*(.-)%s*$", "%1")) + if plt == plate then + local dist = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(v)) + if dist < 5.0 then + found = VehToNet(v) + SendNUIMessage({ type = "greenImpound" }) + end + break + end + end + + if found == 0 then + QBCore.Functions.Notify('Vehicle not found!', 'error') + SendNUIMessage({ type = "redImpound" }) + end + --TriggerServerEvent('mdt:server:impoundVehicle', data, found) + --cb('ok') + else + local ped = PlayerPedId() + local playerPos = GetEntityCoords(ped) + for k, v in pairs(Config.ImpoundLocations) do + if (#(playerPos - vector3(v.x, v.y, v.z)) < 20.0) then + --TriggerServerEvent('mdt:server:removeImpound', data['plate'], k) + impound.CurrentSelection = k + break + end + end + end + end + print(impound.CurrentSelection) + TriggerServerEvent('mdt:server:saveVehicleInfo', dbid, plate, imageurl, notes, stolen, code5, impound) + cb(true) +end) + +RegisterNUICallback("getAllLogs", function(data, cb) + TriggerServerEvent('mdt:server:getAllLogs') + cb(true) +end) + +RegisterNUICallback("getPenalCode", function(data, cb) + TriggerServerEvent('mdt:server:getPenalCode') + cb(true) +end) + +RegisterNUICallback("toggleDuty", function(data, cb) + -- TriggerServerEvent('QBCore:ToggleDuty') + TriggerEvent("qb-policejob:ToggleDuty") + cb(true) +end) + +RegisterNUICallback("setCallsign", function(data, cb) + TriggerServerEvent('mdt:server:setCallsign', data.cid, data.newcallsign) + cb(true) +end) + +RegisterNUICallback("setRadio", function(data, cb) + TriggerServerEvent('mdt:server:setRadio', data.cid, data.newradio) + cb(true) +end) + +RegisterNUICallback("saveIncident", function(data, cb) + TriggerServerEvent('mdt:server:saveIncident', data.ID, data.title, data.information, data.tags, data.officers, data.civilians, data.evidence, data.associated, data.time) + cb(true) +end) + +RegisterNUICallback("removeIncidentCriminal", function(data, cb) + TriggerServerEvent('mdt:server:removeIncidentCriminal', data.cid, data.incidentId) + cb(true) +end) + +RegisterNetEvent('mdt:client:getVehicleData', function(sentData) + if sentData and sentData[1] then + local vehicle = sentData[1] + local vehData = json.decode(vehicle['vehicle']) + vehicle['color'] = Config.ColorInformation[vehicle['color1']] + vehicle['colorName'] = Config.ColorNames[vehicle['color1']] + vehicle['model'] = GetLabelText(GetDisplayNameFromVehicleModel(vehicle['vehicle'])) + vehicle['class'] = Config.ClassList[GetVehicleClassFromName(vehicle['vehicle'])] + vehicle['vehicle'] = nil + SendNUIMessage({ type = "getVehicleData", data = vehicle }) + end +end) + +RegisterNetEvent('mdt:client:updateVehicleDbId', function(sentData) + SendNUIMessage({ type = "updateVehicleDbId", data = tonumber(sentData) }) +end) + +RegisterNetEvent('mdt:client:getAllLogs', function(sentData) + SendNUIMessage({ type = "getAllLogs", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getPenalCode', function(titles, penalcode) + SendNUIMessage({ type = "getPenalCode", titles = titles, penalcode = penalcode }) +end) + +RegisterNetEvent('mdt:client:setRadio', function(radio) + if type(tonumber(radio)) == "number" then + exports["pma-voice"]:setVoiceProperty("radioEnabled", true) + exports["pma-voice"]:setRadioChannel(tonumber(radio)) + QBCore.Functions.Notify("You have set your radio frequency to "..radio..".", "success") + else + QBCore.Functions.Notify("Invalid Station(Please enter a number)", "error") + end +end) + +RegisterNetEvent('mdt:client:sig100', function(radio, type) + local job = PlayerData.job.name + local duty = PlayerData.job.onduty + if (job == "police" or job == "ambulance") and duty == 1 then + if type == true then + exports['erp_notifications']:PersistentAlert("START", "signall100-"..radio, "inform", "Radio "..radio.." is currently signal 100!") + end + end + if not type then + exports['erp_notifications']:PersistentAlert("END", "signall100-"..radio) + end +end) + +RegisterNetEvent('mdt:client:updateCallsign', function(callsign) + callSign = tostring(callsign) +end) + +RegisterNetEvent('mdt:client:updateIncidentDbId', function(sentData) + SendNUIMessage({ type = "updateIncidentDbId", data = tonumber(sentData) }) +end) + + +--==================================================================================== +------------------------------------------ +-- DISPATCH PAGE -- +------------------------------------------ +--==================================================================================== + +RegisterNetEvent('dispatch:clNotify', function(sNotificationData, sNotificationId) + SendNUIMessage({ type = "call", data = sNotificationData }) +end) + +RegisterNUICallback("setWaypoint", function(data, cb) + TriggerServerEvent('mdt:server:setWaypoint', data.callid) + cb(true) +end) + +RegisterNUICallback("callDetach", function(data, cb) + TriggerServerEvent('mdt:server:callDetach', data.callid) + cb(true) +end) + +RegisterNUICallback("callAttach", function(data, cb) + TriggerServerEvent('mdt:server:callAttach', data.callid) + cb(true) +end) + +RegisterNUICallback("attachedUnits", function(data, cb) + TriggerServerEvent('mdt:server:attachedUnits', data.callid) + cb(true) +end) + +RegisterNUICallback("callDispatchDetach", function(data, cb) + TriggerServerEvent('mdt:server:callDispatchDetach', data.callid, data.cid) + cb(true) +end) + +RegisterNUICallback("setDispatchWaypoint", function(data, cb) + TriggerServerEvent('mdt:server:setDispatchWaypoint', data.callid, data.cid) + cb(true) +end) + +RegisterNUICallback("callDragAttach", function(data, cb) + TriggerServerEvent('mdt:server:callDragAttach', data.callid, data.cid) + cb(true) +end) + +RegisterNUICallback("setWaypointU", function(data, cb) + TriggerServerEvent('mdt:server:setWaypoint:unit', data.cid) + cb(true) +end) + +RegisterNUICallback("dispatchMessage", function(data, cb) + TriggerServerEvent('mdt:server:sendMessage', data.message, data.time) + cb(true) +end) + +RegisterNUICallback("refreshDispatchMsgs", function(data, cb) + TriggerServerEvent('mdt:server:refreshDispatchMsgs') + cb(true) +end) + +RegisterNUICallback("dispatchNotif", function(data, cb) + local info = data['data'] + local mentioned = false + if callSign ~= "" then if string.find(string.lower(info['message']),string.lower(string.gsub(callSign,'-','%%-'))) then mentioned = true end end + if mentioned then + + -- Send notification to phone?? + TriggerEvent('erp_phone:sendNotification', {img = info['profilepic'], title = "Dispatch (Mention)", content = info['message'], time = 7500, customPic = true }) + + PlaySoundFrontend(-1, "SELECT", "HUD_FRONTEND_DEFAULT_SOUNDSET", false) + PlaySoundFrontend(-1, "Event_Start_Text", "GTAO_FM_Events_Soundset", 0) + else + TriggerEvent('erp_phone:sendNotification', {img = info['profilepic'], title = "Dispatch ("..info['name']..")", content = info['message'], time = 5000, customPic = true }) + end + cb(true) +end) + +RegisterNUICallback("getCallResponses", function(data, cb) + TriggerServerEvent('mdt:server:getCallResponses', data.callid) + cb(true) +end) + +RegisterNUICallback("sendCallResponse", function(data, cb) + TriggerServerEvent('mdt:server:sendCallResponse', data.message, data.time, data.callid) + cb(true) +end) + +--[[ RegisterNUICallback("impoundVehicle", function(data, cb) + local JobType = GetJobType(PlayerData.job.name) + if JobType == 'police' then + local found = 0 + local plate = string.upper(string.gsub(data['plate'], "^%s*(.-)%s*$", "%1")) + local vehicles = GetGamePool('CVehicle') + + for k,v in pairs(vehicles) do + local plt = string.upper(string.gsub(GetVehicleNumberPlateText(v), "^%s*(.-)%s*$", "%1")) + if plt == plate then + local dist = #(GetEntityCoords(PlayerPedId()) - GetEntityCoords(v)) + if dist < 5.0 then + found = VehToNet(v) + end + break + end + end + + if found == 0 then + QBCore.Functions.Notify('Vehicle not found!', 'error') + return + end + + SendNUIMessage({ type = "greenShit" }) + TriggerServerEvent('mdt:server:impoundVehicle', data, found) + cb('ok') + end +end) ]] + +RegisterNUICallback("removeImpound", function(data, cb) + local ped = PlayerPedId() + local playerPos = GetEntityCoords(ped) + for k, v in pairs(Config.ImpoundLocations) do + if (#(playerPos - vector3(v.x, v.y, v.z)) < 20.0) then + TriggerServerEvent('mdt:server:removeImpound', data['plate'], k) + break + end + end + cb('ok') +end) + +RegisterNUICallback("statusImpound", function(data, cb) + TriggerServerEvent('mdt:server:statusImpound', data['plate']) + cb('ok') +end) + +RegisterNetEvent('mdt:client:attachedUnits', function(sentData, callid) + SendNUIMessage({ type = "attachedUnits", data = sentData, callid = callid }) +end) + +RegisterNetEvent('mdt:client:setWaypoint', function(callInformation) + SetNewWaypoint(callInformation['origin']['x'], callInformation['origin']['y']) +end) + +RegisterNetEvent('mdt:client:callDetach', function(callid, sentData) + local job = PlayerData.job.name + if job == "police" or job == 'ambulance' then SendNUIMessage({ type = "callDetach", callid = callid, data = tonumber(sentData) }) end +end) +RegisterNetEvent('mdt:client:callAttach', function(callid, sentData) + local job = PlayerData.job.name + if job == "police" or job == 'ambulance' then + SendNUIMessage({ type = "callAttach", callid = callid, data = tonumber(sentData) }) + end +end) + +RegisterNetEvent('mdt:client:setWaypoint:unit', function(sentData) + SetNewWaypoint(sentData.x, sentData.y) +end) + +RegisterNetEvent('mdt:client:dashboardMessage', function(sentData) + local job = PlayerData.job.name + if job == "police" or job == 'ambulance' then + SendNUIMessage({ type = "dispatchmessage", data = sentData }) + end +end) + +RegisterNetEvent('mdt:client:dashboardMessages', function(sentData) + SendNUIMessage({ type = "dispatchmessages", data = sentData }) +end) + +RegisterNetEvent('mdt:client:getCallResponses', function(sentData, sentCallId) + SendNUIMessage({ type = "getCallResponses", data = sentData, callid = sentCallId }) +end) + +RegisterNetEvent('mdt:client:sendCallResponse', function(message, time, callid, name) + SendNUIMessage({ type = "sendCallResponse", message = message, time = time, callid = callid, name = name }) +end) + +RegisterNetEvent('mdt:client:notifyMechanics', function(sentData) + --[[if exports["erp-jobsystem"]:CanTow() then + TriggerServerEvent('erp-sounds:PlayWithinDistance', 1.5, 'beep', 0.4) + TriggerEvent('erp_phone:sendNotification', {img = 'vehiclenotif.png', title = "Impound", content = "New vehicle is ready to be impounded!", time = 5000 }) + end]] +end) + +RegisterNetEvent('mdt:client:statusImpound', function(data, plate) + SendNUIMessage({ type = "statusImpound", data = data, plate = plate }) +end) \ No newline at end of file diff --git a/fxmanifest.lua b/fxmanifest.lua new file mode 100644 index 00000000..76aeee8f --- /dev/null +++ b/fxmanifest.lua @@ -0,0 +1,43 @@ +fx_version 'cerulean' +game 'gta5' + +author 'Flawws, Flakey, Idris and the Project Sloth team' +description 'EchoRP MDT Rewrite for QBCore' +version '0.9.9' + +lua54 'yes' + +shared_script 'shared/config.lua' + +server_scripts { + '@oxmysql/lib/MySQL.lua', + 'server/utils.lua', + 'server/dbm.lua', + 'server/main.lua' +} +client_scripts{ + 'client/main.lua', + 'client/cl_impound.lua' +} + +ui_page 'ui/dashboard.html' + +files { + 'ui/img/sasp_badge.png', + 'ui/img/ems_badge.png', + 'ui/img/court.png', + 'ui/img/warrant_pfp.png', + 'ui/img/profile_pic.png', + 'ui/img/not-found.jpg', + 'ui/img/male.png', + 'ui/img/female.png', + 'ui/dashboard.html', + 'ui/dmv.html', + 'ui/bolos.html', + 'ui/incidents.html', + 'ui/penalcode.html', + 'ui/reports.html', + 'ui/warrants.html', + 'ui/app.js', + 'ui/style.css', +} \ No newline at end of file diff --git a/mdt.sql b/mdt.sql new file mode 100644 index 00000000..03bb0ae9 --- /dev/null +++ b/mdt.sql @@ -0,0 +1,111 @@ +CREATE TABLE IF NOT EXISTS `mdt_data` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cid` VARCHAR(20) DEFAULT NULL, + `information` MEDIUMTEXT DEFAULT NULL, + `tags` TEXT NOT NULL, + `gallery` TEXT NOT NULL, + `jobtype` VARCHAR(25) DEFAULT 'police', + `pfp` TEXT DEFAULT NULL, + `fingerprint` VARCHAR(50) DEFAULT NULL, + PRIMARY KEY (`cid`), + KEY `id` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_bulletin` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` TEXT NOT NULL, + `desc` TEXT NOT NULL, + `author` varchar(50) NOT NULL, + `time` varchar(20) NOT NULL, + `jobtype` VARCHAR(25) DEFAULT 'police', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_reports` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `author` varchar(50) DEFAULT NULL, + `title` varchar(255) DEFAULT NULL, + `type` varchar(50) DEFAULT NULL, + `details` text DEFAULT NULL, + `tags` text DEFAULT NULL, + `officersinvolved` text DEFAULT NULL, + `civsinvolved` text DEFAULT NULL, + `gallery` text DEFAULT NULL, + `time` varchar(20) DEFAULT NULL, + `jobtype` varchar(25) DEFAULT 'police', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_bolos` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `author` varchar(50) DEFAULT NULL, + `title` varchar(50) DEFAULT NULL, + `plate` varchar(50) DEFAULT NULL, + `owner` varchar(50) DEFAULT NULL, + `individual` varchar(50) DEFAULT NULL, + `detail` text DEFAULT NULL, + `tags` text DEFAULT NULL, + `gallery` text DEFAULT NULL, + `officersinvolved` text DEFAULT NULL, + `time` varchar(20) DEFAULT NULL, + `jobtype` varchar(25) NOT NULL DEFAULT 'police', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_convictions` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `cid` varchar(50) DEFAULT NULL, + `linkedincident` int(11) NOT NULL DEFAULT 0, + `warrant` varchar(50) DEFAULT NULL, + `guilty` varchar(50) DEFAULT NULL, + `processed` varchar(50) DEFAULT NULL, + `associated` varchar(50) DEFAULT '0', + `charges` text DEFAULT NULL, + `fine` int(11) DEFAULT 0, + `sentence` int(11) DEFAULT 0, + `recfine` int(11) DEFAULT 0, + `recsentence` int(11) DEFAULT 0, + `time` varchar(20) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_incidents` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `author` varchar(50) NOT NULL DEFAULT '', + `title` varchar(50) NOT NULL DEFAULT '0', + `details` text NOT NULL, + `tags` text NOT NULL, + `officersinvolved` text NOT NULL, + `civsinvolved` text NOT NULL, + `evidence` text NOT NULL, + `time` varchar(20) DEFAULT NULL, + `jobtype` varchar(25) NOT NULL DEFAULT 'police', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_logs` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `text` text NOT NULL, + `time` varchar(20) DEFAULT NULL, + `jobtype` varchar(25) DEFAULT 'police', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_vehicleinfo` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `plate` varchar(50) DEFAULT NULL, + `information` text NOT NULL DEFAULT '', + `stolen` tinyint(1) NOT NULL DEFAULT 0, + `code5` tinyint(1) NOT NULL DEFAULT 0, + `image` text NOT NULL DEFAULT '', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE IF NOT EXISTS `mdt_impound` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `vehicleid` int(11) NOT NULL, + `linkedreport` int(11) NOT NULL, + `fee` int(11) DEFAULT NULL, + `time` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..98392104 --- /dev/null +++ b/readme.md @@ -0,0 +1,10 @@ +# Beta Released + +## EchoRP MDT QBCore Edit (WIP) + +EchoRP MDT Released by Flawws#9999 from Echo RP rewritten and restructured for QBCore. + +## Dependencies + +- QBCore +- OxMySQL V1.9.0 (will release docs on how to use an older version) diff --git a/server/dbm.lua b/server/dbm.lua new file mode 100644 index 00000000..b9655174 --- /dev/null +++ b/server/dbm.lua @@ -0,0 +1,153 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- (Start) Opening the MDT and sending data +function AddLog(text) + --print(text) + return MySQL.insert.await('INSERT INTO `mdt_logs` (`text`, `time`) VALUES (?,?)', {text, os.time() * 1000}) + -- return exports.oxmysql:execute('INSERT INTO `mdt_logs` (`text`, `time`) VALUES (:text, :time)', { text = text, time = os.time() * 1000 }) +end + +function GetNameFromId(cid) + -- Should be a scalar? + local result = MySQL.scalar.await('SELECT charinfo FROM players WHERE citizenid = @citizenid', { ['@citizenid'] = cid }) + if result ~= nil then + local charinfo = json.decode(result) + local fullname = charinfo['firstname']..' '..charinfo['lastname'] + return fullname + else + --print('Player does not exist') + return nil + end + -- return exports.oxmysql:executeSync('SELECT firstname, lastname FROM `users` WHERE id = :id LIMIT 1', { id = cid }) +end + +-- idk what this is used for either +function GetPersonInformation(cid, jobtype) + local result = MySQL.query.await('SELECT information, tags, gallery, pfp, fingerprint FROM mdt_data WHERE cid = ? and jobtype = ?', { cid, jobtype}) + return result[1] + -- return exports.oxmysql:executeSync('SELECT information, tags, gallery FROM mdt WHERE cid= ? and type = ?', { cid, jobtype }) +end + +-- idk but I guess sure? +function GetIncidentName(id) + -- Should also be a scalar + return MySQL.query.await('SELECT title FROM `mdt_incidents` WHERE id = :id LIMIT 1', { id = id }) + -- return exports.oxmysql:executeSync('SELECT title FROM `mdt_incidents` WHERE id = :id LIMIT 1', { id = id }) +end + +function GetConvictions(cids) + return MySQL.query.await('SELECT * FROM `mdt_convictions` WHERE `cid` IN(?)', { cids }) + -- return exports.oxmysql:executeSync('SELECT * FROM `mdt_convictions` WHERE `cid` IN(?)', { cids }) +end + +function GetLicenseInfo(cid) + local result = MySQL.query.await('SELECT * FROM `licenses` WHERE `cid` = ?', { cid }) + return result + -- return exports.oxmysql:executeSync('SELECT * FROM `licenses` WHERE `cid`=:cid', { cid = cid }) +end + +function CreateUser(cid, tableName) + AddLog("A user was created with the CID: "..cid) + -- return exports.oxmysql:insert("INSERT INTO `"..dbname.."` (cid) VALUES (:cid)", { cid = cid }) + return MySQL.insert.await("INSERT INTO `"..tableName.."` (cid) VALUES (:cid)", { cid = cid }) +end + +function GetPlayerVehicles(cid, cb) + return MySQL.query.await('SELECT id, plate, vehicle FROM player_vehicles WHERE citizenid=:cid', { cid = cid }) +end + +function GetBulletins(JobType) + return MySQL.query.await('SELECT * FROM `mdt_bulletin` WHERE `jobtype` = ? LIMIT 10', { JobType }) + -- return exports.oxmysql:executeSync('SELECT * FROM `mdt_bulletin` WHERE `type`= ? LIMIT 10', { JobType }) +end + +function GetPlayerProperties(cid, cb) + local result = MySQL.query.await('SELECT houselocations.label, houselocations.coords FROM player_houses INNER JOIN houselocations ON player_houses.house = houselocations.name where player_houses.citizenid = ?', {cid}) + return result +end + +function GetPlayerDataById(id) + local Player = QBCore.Functions.GetPlayerByCitizenId(id) + if Player ~= nil then + local response = {citizenid = Player.PlayerData.citizenid, charinfo = Player.PlayerData.charinfo, metadata = Player.PlayerData.metadata, job = Player.PlayerData.job} + return response + else + return MySQL.single.await('SELECT citizenid, charinfo, job, metadata FROM players WHERE citizenid = ? LIMIT 1', { id }) + end + + -- return exports.oxmysql:executeSync('SELECT citizenid, charinfo, job FROM players WHERE citizenid = ? LIMIT 1', { id }) +end + +-- Probs also best not to use +--[[ function GetImpoundStatus(vehicleid, cb) + cb( #(exports.oxmysql:executeSync('SELECT id FROM `impound` WHERE `vehicleid`=:vehicleid', {['vehicleid'] = vehicleid })) > 0 ) +end ]] + +function GetBoloStatus(plate) + local result = MySQL.scalar.await('SELECT id FROM `mdt_bolos` WHERE LOWER(`plate`)=:plate', { plate = string.lower(plate)}) + return result + -- return exports.oxmysql:scalarSync('SELECT id FROM `mdt_bolos` WHERE LOWER(`plate`)=:plate', { plate = string.lower(plate)}) +end + +function GetOwnerName(cid) + local result = MySQL.scalar.await('SELECT charinfo FROM `players` WHERE LOWER(`citizenid`) = ? LIMIT 1', {cid}) + return result + -- return exports.oxmysql:scalarSync('SELECT charinfo FROM `players` WHERE id=:cid LIMIT 1', { cid = cid}) +end + +function GetVehicleInformation(plate, cb) + local result = MySQL.query.await('SELECT id, information FROM `mdt_vehicleinfo` WHERE plate=:plate', { plate = plate}) + cb(result) +end + +function GetPlayerLicenses(identifier) + local response = false + local Player = QBCore.Functions.GetPlayerByCitizenId(identifier) + if Player ~= nil then + return Player.PlayerData.metadata.licences + else + local result = MySQL.scalar.await('SELECT metadata FROM players WHERE citizenid = @identifier', {['@identifier'] = identifier}) + if result ~= nil then + local metadata = json.decode(result) + if metadata["licences"] ~= nil and metadata["licences"] then + return metadata["licences"] + end + end + end +end + +function ManageLicense(identifier, type, status) + local Player = QBCore.Functions.GetPlayerByCitizenId(identifier) + local licenseStatus = nil + if status == "give" then licenseStatus = true elseif status == "revoke" then licenseStatus = false end + if Player ~= nil then + local licences = Player.PlayerData.metadata["licences"] + local newLicenses = {} + for k, v in pairs(licences) do + local status = v + if k == type then + status = licenseStatus + end + newLicenses[k] = status + end + Player.Functions.SetMetaData("licences", newLicenses) + else + local licenseType = '$.licences.'..type + local result = MySQL.query.await('UPDATE `players` SET `metadata` = JSON_REPLACE(`metadata`, ?, ?) WHERE `citizenid` = ?', {licenseType, licenseStatus, identifier}) --seems to not work on older MYSQL versions, think about alternative + end +end + +function ManageLicenses(identifier, incomingLicenses) + local Player = QBCore.Functions.GetPlayerByCitizenId(identifier) + if Player ~= nil then + Player.Functions.SetMetaData("licences", incomingLicenses) + + else + local result = MySQL.scalar.await('SELECT metadata FROM players WHERE citizenid = @identifier', {['@identifier'] = identifier}) + result = json.decode(result) + for k, v in pairs(result.licences) do + result.licences[k] = incomingLicenses[k] + end + MySQL.query.await('UPDATE `players` SET `metadata` = @metadata WHERE citizenid = @citizenid', {['@metadata'] = json.encode(result), ['@citizenid'] = identifier}) + end +end \ No newline at end of file diff --git a/server/main.lua b/server/main.lua new file mode 100644 index 00000000..e7e2fede --- /dev/null +++ b/server/main.lua @@ -0,0 +1,1265 @@ +local QBCore = exports['qb-core']:GetCoreObject() +-- Maybe cache? +local incidents = {} +local convictions = {} +local bolos = {} + +-- TODO make it departments compatible +local activeUnits = {} + +local impound = {} +local dispatchMessages = {} + +local function IsPolice(job) + for k, v in pairs(Config.PoliceJobs) do + if job == k then + return true + end + end + return false +end + +AddEventHandler("onResourceStart", function(resourceName) + if (resourceName == 'qb-mdt') then + activeUnits = {} + end +end) + +RegisterNetEvent('mdt:server:openMDT', function() + local src = source + local PlayerData = GetPlayerData(src) + if not PermCheck(src, PlayerData) then return end + local Radio = Player(src).state.radioChannel or 0 + --[[ if Radio > 100 then + Radio = 0 + end ]] + + activeUnits[PlayerData.citizenid] = { + cid = PlayerData.citizenid, + callSign = PlayerData.metadata['callsign'], + firstName = PlayerData.charinfo.firstname:sub(1,1):upper()..PlayerData.charinfo.firstname:sub(2), + lastName = PlayerData.charinfo.lastname:sub(1,1):upper()..PlayerData.charinfo.lastname:sub(2), + radio = Radio, + unitType = PlayerData.job.name, + duty = PlayerData.job.onduty + } + + local JobType = GetJobType(PlayerData.job.name) + local bulletin = GetBulletins(JobType) + + --TriggerClientEvent('mdt:client:dashboardbulletin', src, bulletin) + TriggerClientEvent('mdt:client:open', src, bulletin, activeUnits) + --TriggerClientEvent('mdt:client:GetActiveUnits', src, activeUnits) +end) + +QBCore.Functions.CreateCallback('mdt:server:SearchProfile', function(source, cb, sentData) + if not sentData then return cb({}) end + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType ~= nil then + local people = MySQL.query.await("SELECT p.citizenid, p.charinfo, md.pfp FROM players p LEFT JOIN mdt_data md on p.citizenid = md.cid WHERE LOWER(`charinfo`) LIKE :query OR LOWER(`citizenid`) LIKE :query OR LOWER(`fingerprint`) LIKE :query AND jobtype = :jobtype LIMIT 20", { query = string.lower('%'..sentData..'%'), jobtype = JobType }) + local citizenIds = {} + local citizenIdIndexMap = {} + if not next(people) then cb({}) return end + + for index, data in pairs(people) do + people[index]['warrant'] = false + people[index]['convictions'] = 0 + people[index]['licences'] = GetPlayerLicenses(data.citizenid) + people[index]['pp'] = ProfPic(data.gender, data.pfp) + citizenIds[#citizenIds+1] = data.citizenid + citizenIdIndexMap[data.citizenid] = index + end + + local convictions = GetConvictions(citizenIds) + + if next(convictions) then + for _, conv in pairs(convictions) do + if conv.warrant then people[citizenIdIndexMap[conv.cid]].warrant = true end + + local charges = json.decode(conv.charges) + people[citizenIdIndexMap[conv.cid]].convictions = people[citizenIdIndexMap[conv.cid]].convictions + #charges + end + end + + + return cb(people) + end + end + + return cb({}) +end) + +QBCore.Functions.CreateCallback("mdt:server:getWarrants", function(source, cb) + local WarrantData = {} + local data = MySQL.query.await("SELECT * FROM mdt_convictions", {}) + for _, value in pairs(data) do + if value.warrant == "1" then + WarrantData[#WarrantData+1] = { + cid = value.cid, + linkedincident = value.linkedincident, + name = GetNameFromId(value.cid), + time = value.time + } + end + end + cb(WarrantData) +end) + +QBCore.Functions.CreateCallback('mdt:server:OpenDashboard', function(source, cb) + local PlayerData = GetPlayerData(source) + if not PermCheck(source, PlayerData) then return end + local JobType = GetJobType(PlayerData.job.name) + local bulletin = GetBulletins(JobType) + cb(bulletin) +end) + +RegisterNetEvent('mdt:server:NewBulletin', function(title, info, time) + local src = source + local PlayerData = GetPlayerData(src) + if not PermCheck(src, PlayerData) then return end + local JobType = GetJobType(PlayerData.job.name) + local playerName = GetNameFromPlayerData(PlayerData) + local newBulletin = MySQL.insert.await('INSERT INTO `mdt_bulletin` (`title`, `desc`, `author`, `time`, `jobtype`) VALUES (:title, :desc, :author, :time, :jt)', { + title = title, + desc = info, + author = playerName, + time = tostring(time), + jt = JobType + }) + + AddLog(("A new bulletin was added by %s with the title: %s!"):format(playerName, title)) + TriggerClientEvent('mdt:client:newBulletin', -1, src, {id = newBulletin, title = title, info = info, time = time, author = PlayerData.CitizenId}, JobType) +end) + +RegisterNetEvent('mdt:server:deleteBulletin', function(id, title) + if not id then return false end + local src = source + local PlayerData = GetPlayerData(src) + if not PermCheck(src, PlayerData) then return end + local JobType = GetJobType(PlayerData.job.name) + + local deletion = MySQL.query.await('DELETE FROM `mdt_bulletin` where title = ?', {title}) + AddLog("Bulletin with Title: "..title.." was deleted by " .. GetNameFromPlayerData(PlayerData) .. ".") +end) + +QBCore.Functions.CreateCallback('mdt:server:GetProfileData', function(source, cb, sentId) + if not sentId then return cb({}) end + + local src = source + local PlayerData = GetPlayerData(src) + if not PermCheck(src, PlayerData) then return cb({}) end + local JobType = GetJobType(PlayerData.job.name) + local target = GetPlayerDataById(sentId) + local JobName = PlayerData.job.name + + if not target or not next(target) then return cb({}) end + + -- Convert to string because bad code, yes? + if type(target.job) == 'string' then target.job = json.decode(target.job) end + if type(target.charinfo) == 'string' then target.charinfo = json.decode(target.charinfo) end + if type(target.metadata) == 'string' then target.metadata = json.decode(target.metadata) end + + local job, grade = UnpackJob(target.job) + + local person = { + cid = target.citizenid, + firstname = target.charinfo.firstname, + lastname = target.charinfo.lastname, + job = job.label, + grade = grade.name, + pp = ProfPic(target.charinfo.gender, null), + licences = target.metadata['licences'], + dob = target.charinfo.birthdate, + mdtinfo = '', + fingerprint = '', + tags = {}, + vehicles = {}, + properties = {}, + gallery = {}, + isLimited = false + } + + if Config.PoliceJobs[JobName] then + local convictions = GetConvictions({person.cid}) + person.convictions = {} + if next(convictions) then + for _, conv in pairs(convictions) do + if conv.warrant then person.warrant = true end + local charges = json.decode(conv.charges) + for _, charge in pairs(charges) do + person.convictions[_] = charge + end + end + end + local vehicles = GetPlayerVehicles(person.cid) + + if vehicles then + person.vehicles = vehicles + end + local Coords = {} + local Houses = {} + local properties= GetPlayerProperties(person.cid) + for k, v in pairs(properties) do + Coords[#Coords+1] = { + coords = json.decode(v["coords"]), + } + end + for index = 1, #Coords, 1 do + Houses[#Houses+1] = { + label = properties[index]["label"], + coords = tostring(Coords[index]["coords"]["enter"]["x"]..",".. Coords[index]["coords"]["enter"]["y"].. ",".. Coords[index]["coords"]["enter"]["z"]), + } + end + -- if properties then + person.properties = Houses + -- end + end + + local mdtData = GetPersonInformation(sentId, JobType) + if mdtData then + person.mdtinfo = mdtData.information + person.fingerprint = mdtData.fingerprint + person.profilepic = mdtData.pfp + person.tags = json.decode(mdtData.tags) + person.gallery = json.decode(mdtData.gallery) + end + + return cb(person) +end) + +RegisterNetEvent("mdt:server:saveProfile", function(pfp, information, cid, fName, sName, tags, gallery, fingerprint, licenses) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + ManageLicenses(cid, licenses) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'doj' then JobType = 'police' end + MySQL.Async.insert('INSERT INTO mdt_data (cid, information, pfp, jobtype, tags, gallery, fingerprint) VALUES (:cid, :information, :pfp, :jobtype, :tags, :gallery, :fingerprint) ON DUPLICATE KEY UPDATE cid = :cid, information = :information, pfp = :pfp, tags = :tags, gallery = :gallery, fingerprint = :fingerprint', { + cid = cid, + information = information, + pfp = pfp, + jobtype = JobType, + tags = json.encode(tags), + gallery = json.encode(gallery), + fingerprint = fingerprint, + }) + end +end) + +RegisterNetEvent("mdt:server:updateLicense", function(cid, type, status) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + ManageLicense(cid, type, status) + end + end +end) + +-- Incidents + + +RegisterNetEvent('mdt:server:getAllIncidents', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local matches = MySQL.query.await("SELECT * FROM `mdt_incidents` ORDER BY `id` DESC LIMIT 30", {}) + + TriggerClientEvent('mdt:client:getAllIncidents', src, matches) + end + end +end) + +RegisterNetEvent('mdt:server:searchIncidents', function(query) + if query then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local matches = MySQL.query.await("SELECT * FROM `mdt_incidents` WHERE `id` LIKE :query OR LOWER(`title`) LIKE :query OR LOWER(`author`) LIKE :query OR LOWER(`details`) LIKE :query OR LOWER(`tags`) LIKE :query OR LOWER(`officersinvolved`) LIKE :query OR LOWER(`civsinvolved`) LIKE :query OR LOWER(`author`) LIKE :query ORDER BY `id` DESC LIMIT 50", { + query = string.lower('%'..query..'%') -- % wildcard, needed to search for all alike results + }) + + TriggerClientEvent('mdt:client:getIncidents', src, matches) + end + end + end +end) + +RegisterNetEvent('mdt:server:getIncidentData', function(sentId) + if sentId then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local matches = MySQL.query.await("SELECT * FROM `mdt_incidents` WHERE `id` = :id", { + id = sentId + }) + local data = matches[1] + data['tags'] = json.decode(data['tags']) + data['officersinvolved'] = json.decode(data['officersinvolved']) + data['civsinvolved'] = json.decode(data['civsinvolved']) + data['evidence'] = json.decode(data['evidence']) + + + local convictions = MySQL.query.await("SELECT * FROM `mdt_convictions` WHERE `linkedincident` = :id", { + id = sentId + }) + if convictions ~= nil then + for i=1, #convictions do + local res = GetNameFromId(convictions[i]['cid']) + if res ~= nil then + convictions[i]['name'] = res + else + convictions[i]['name'] = "Unknown" + end + convictions[i]['charges'] = json.decode(convictions[i]['charges']) + end + end + TriggerClientEvent('mdt:client:getIncidentData', src, data, convictions) + end + end + end +end) + +RegisterNetEvent('mdt:server:getAllBolos', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + MySQL.query.await("SELECT * FROM `mdt_bolos` WHERE jobtype = :jobtype", { + jobtype = JobType + }, function(matches) + TriggerClientEvent('mdt:client:getAllBolos', src, matches) + end) + end +end) + +RegisterNetEvent('mdt:server:searchBolos', function(sentSearch) + if sentSearch then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + local matches = MySQL.query.await("SELECT * FROM `mdt_bolos` WHERE `id` LIKE :query OR LOWER(`title`) LIKE :query OR `plate` LIKE :query OR LOWER(`owner`) LIKE :query OR LOWER(`individual`) LIKE :query OR LOWER(`detail`) LIKE :query OR LOWER(`officersinvolved`) LIKE :query OR LOWER(`tags`) LIKE :query OR LOWER(`author`) LIKE :query AND jobtype = :jobtype", { + query = string.lower('%'..sentSearch..'%'), -- % wildcard, needed to search for all alike results + jobtype = JobType + }) + TriggerClientEvent('mdt:client:getBolos', src, matches) + end + end +end) + +RegisterNetEvent('mdt:server:getBoloData', function(sentId) + if sentId then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + local matches = MySQL.query.await("SELECT * FROM `mdt_bolos` WHERE `id` = :id AND jobtype = :jobtype LIMIT 1", { + id = sentId, + jobtype = JobType + }) + + local data = matches[1] + data['tags'] = json.decode(data['tags']) + data['officersinvolved'] = json.decode(data['officersinvolved']) + data['gallery'] = json.decode(data['gallery']) + TriggerClientEvent('mdt:client:getBoloData', src, data) + end + end +end) + +RegisterNetEvent('mdt:server:newBolo', function(existing, id, title, plate, owner, individual, detail, tags, gallery, officersinvolved, time) + if id then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + + local function InsertBolo() + MySQL.insert('INSERT INTO `mdt_bolos` (`title`, `author`, `plate`, `owner`, `individual`, `detail`, `tags`, `gallery`, `officersinvolved`, `time`, `jobtype`) VALUES (:title, :author, :plate, :owner, :individual, :detail, :tags, :gallery, :officersinvolved, :time, :jobtype)', { + title = title, + author = fullname, + plate = plate, + owner = owner, + individual = individual, + detail = detail, + tags = json.encode(tags), + gallery = json.encode(gallery), + officersinvolved = json.encode(officersinvolved), + time = tostring(time), + jobtype = JobType + }, function(r) + if r then + TriggerClientEvent('mdt:client:boloComplete', src, r) + TriggerEvent('mdt:server:AddLog', "A new BOLO was created by "..fullname.." with the title ("..title..") and ID ("..id..")") + end + end) + end + + local function UpdateBolo() + MySQL.update.await("UPDATE mdt_bolos SET `title`=:title, plate=:plate, owner=:owner, individual=:individual, detail=:detail, tags=:tags, gallery=:gallery, officersinvolved=:officersinvolved WHERE `id`=:id AND jobtype = :jobtype LIMIT 1", { + title = title, + plate = plate, + owner = owner, + individual = individual, + detail = detail, + tags = json.encode(tags), + gallery = json.encode(gallery), + officersinvolved = json.encode(officersinvolved), + id = id, + jobtype = JobType + }, function(r) + if r then + TriggerClientEvent('mdt:client:boloComplete', src, id) + TriggerEvent('mdt:server:AddLog', "A BOLO was updated by "..fullname.." with the title ("..title..") and ID ("..id..")") + end + end) + end + + if existing then + UpdateBolo() + elseif not existing then + InsertBolo() + end + end + end +end) + +RegisterNetEvent('mdt:server:deleteBolo', function(id) + if id then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' then + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + MySQL.update("DELETE FROM `mdt_bolos` WHERE id=:id", { id = id, jobtype = JobType }) + TriggerEvent('mdt:server:AddLog', "A BOLO was deleted by "..fullname.." with the ID ("..id..")") + end + end +end) + +RegisterNetEvent('mdt:server:deleteICU', function(id) + if id then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'ambulance' then + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + MySQL.update("DELETE FROM `mdt_bolos` WHERE id=:id", { id = id, jobtype = JobType }) + TriggerEvent('mdt:server:AddLog', "A ICU Check-in was deleted by "..fullname.." with the ID ("..id..")") + end + end +end) + +RegisterNetEvent('mdt:server:incidentSearchPerson', function(query) + if query then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local function ProfPic(gender, profilepic) + if profilepic then return profilepic end; + if gender == "f" then return "img/female.png" end; + return "img/male.png" + end + + local result = MySQL.query.await("SELECT p.citizenid, p.charinfo, md.pfp from players p LEFT JOIN mdt_data md on p.citizenid = md.cid WHERE LOWER(`charinfo`) LIKE :query OR LOWER(`citizenid`) LIKE :query AND `jobtype` = :jobtype LIMIT 30", { + query = string.lower('%'..query..'%'), -- % wildcard, needed to search for all alike results + jobtype = JobType + }) + local data = {} + for i=1, #result do + local charinfo = json.decode(result[i].charinfo) + data[i] = {id = result[i].citizenid, firstname = charinfo.firstname, lastname = charinfo.lastname, profilepic = ProfPic(charinfo.gender, result[i].pfp)} + end + TriggerClientEvent('mdt:client:incidentSearchPerson', src, data) + end + end + end +end) + +RegisterNetEvent('mdt:server:getAllReports', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' or JobType == 'ambulance' then + if JobType == 'doj' then JobType = 'police' end + local matches = MySQL.query.await("SELECT * FROM `mdt_reports` WHERE jobtype = :jobtype ORDER BY `id` DESC LIMIT 30", { + jobtype = JobType + }) + TriggerClientEvent('mdt:client:getAllReports', src, matches) + end + end +end) + +RegisterNetEvent('mdt:server:getReportData', function(sentId) + if sentId then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' or JobType == 'ambulance' then + if JobType == 'doj' then JobType = 'police' end + local matches = MySQL.query.await("SELECT * FROM `mdt_reports` WHERE `id` = :id AND `jobtype` = :jobtype LIMIT 1", { + id = sentId, + jobtype = JobType + }) + local data = matches[1] + data['tags'] = json.decode(data['tags']) + data['officersinvolved'] = json.decode(data['officersinvolved']) + data['civsinvolved'] = json.decode(data['civsinvolved']) + data['gallery'] = json.decode(data['gallery']) + TriggerClientEvent('mdt:client:getReportData', src, data) + end + end + end +end) + +RegisterNetEvent('mdt:server:searchReports', function(sentSearch) + if sentSearch then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' or JobType == 'ambulance' then + if JobType == 'doj' then JobType = 'police' end + local matches = MySQL.query.await("SELECT * FROM `mdt_reports` WHERE `id` LIKE :query OR LOWER(`author`) LIKE :query OR LOWER(`title`) LIKE :query OR LOWER(`type`) LIKE :query OR LOWER(`details`) LIKE :query OR LOWER(`tags`) LIKE :query AND `jobtype` = :jobtype ORDER BY `id` DESC LIMIT 50", { + query = string.lower('%'..sentSearch..'%'), -- % wildcard, needed to search for all alike results + jobtype = JobType + }) + + TriggerClientEvent('mdt:client:getAllReports', src, matches) + end + end + end +end) + +RegisterNetEvent('mdt:server:newReport', function(existing, id, title, reporttype, details, tags, gallery, officers, civilians, time) + if id then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType ~= nil then + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + local function InsertReport() + MySQL.insert('INSERT INTO `mdt_reports` (`title`, `author`, `type`, `details`, `tags`, `gallery`, `officersinvolved`, `civsinvolved`, `time`, `jobtype`) VALUES (:title, :author, :type, :details, :tags, :gallery, :officersinvolved, :civsinvolved, :time, :jobtype)', { + title = title, + author = fullname, + type = reporttype, + details = details, + tags = json.encode(tags), + gallery = json.encode(gallery), + officersinvolved = json.encode(officers), + civsinvolved = json.encode(civilians), + time = tostring(time), + jobtype = JobType, + }, function(r) + if r then + TriggerClientEvent('mdt:client:reportComplete', src, r) + TriggerEvent('mdt:server:AddLog', "A new report was created by "..fullname.." with the title ("..title..") and ID ("..id..")") + end + end) + end + + local function UpdateReport() + MySQL.update("UPDATE `mdt_reports` SET `title` = :title, type = :type, details = :details, tags = :tags, gallery = :gallery, officersinvolved = :officersinvolved, civsinvolved = :civsinvolved, jobtype = :jobtype WHERE `id` = :id LIMIT 1", { + title = title, + type = reporttype, + details = details, + tags = json.encode(tags), + gallery = json.encode(gallery), + officersinvolved = json.encode(officers), + civsinvolved = json.encode(civilians), + jobtype = JobType, + id = id, + }, function(affectedRows) + if affectedRows > 0 then + TriggerClientEvent('mdt:client:reportComplete', src, id) + TriggerEvent('mdt:server:AddLog', "A report was updated by "..fullname.." with the title ("..title..") and ID ("..id..")") + end + end) + end + + if existing then + UpdateReport() + elseif not existing then + InsertReport() + end + end + end + end +end) + +QBCore.Functions.CreateCallback('mdt:server:SearchVehicles', function(source, cb, sentData) + if not sentData then return cb({}) end + local src = source + local PlayerData = GetPlayerData(src) + if not PermCheck(source, PlayerData) then return cb({}) end + + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local vehicles = MySQL.query.await("SELECT pv.id, pv.citizenid, pv.plate, pv.vehicle, pv.mods, pv.state, p.charinfo FROM `player_vehicles` pv LEFT JOIN players p ON pv.citizenid = p.citizenid WHERE LOWER(`plate`) LIKE :query OR LOWER(`vehicle`) LIKE :query LIMIT 25", { + query = string.lower('%'..sentData..'%') + }) + + if not next(vehicles) then cb({}) return end + + for _, value in ipairs(vehicles) do + if value.state == 0 then + value.state = "Out" + elseif value.state == 1 then + value.state = "Garaged" + elseif value.state == 2 then + value.state = "Impounded" + end + + value.bolo = false + local boloResult = GetBoloStatus(value.plate) + if boloResult then + value.bolo = true + end + + value.code = false + value.stolen = false + value.image = "img/not-found.jpg" + local info = GetVehicleInformation(value.plate) + if info then + value.code = info['code5'] + value.stolen = info['stolen'] + value.image = info['image'] + end + + local ownerResult = json.decode(value.charinfo) + + value.owner = ownerResult['firstname'] .. " " .. ownerResult['lastname'] + end + -- idk if this works or I have to call cb first then return :shrug: + return cb(vehicles) + end + + return cb({}) + end + +end) + +RegisterNetEvent('mdt:server:getVehicleData', function(plate) + if plate then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'doj' then + local vehicle = MySQL.query.await("select pv.*, p.charinfo from player_vehicles pv LEFT JOIN players p ON pv.citizenid = p.citizenid where pv.plate = :plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1")}) + if vehicle and vehicle[1] then + vehicle[1]['impound'] = false + if vehicle[1].state == 2 then + vehicle[1]['impound'] = true + end + + vehicle[1]['bolo'] = false + vehicle[1]['information'] = "" + + -- Bolo Status + GetBoloStatus(vehicle[1]['plate'], function(boloStatus) + if boloStatus and boloStatus[1] then vehicle[1]['bolo'] = true end + end) -- Used to get BOLO status. + + vehicle[1]['name'] = "Unknown Person" + + local ownerResult = json.decode(vehicle[1].charinfo) + vehicle[1]['name'] = ownerResult['firstname'] .. " " .. ownerResult['lastname'] + + local color1 = json.decode(vehicle[1].mods) + vehicle[1]['color1'] = color1['color1'] + + vehicle[1]['dbid'] = 0 + + local info = GetVehicleInformation(vehicle[1]['plate']) + if info then + vehicle[1]['information'] = info['information'] + vehicle[1]['dbid'] = info['id'] + vehicle[1]['image'] = info['image'] + vehicle[1]['code'] = info['code5'] + vehicle[1]['stolen'] = info['stolen'] + end + + if vehicle[1]['image'] == nil then vehicle[1]['image'] = "img/not-found.jpg" end -- Image + end + + TriggerClientEvent('mdt:client:getVehicleData', src, vehicle) + end + end + end +end) + +RegisterNetEvent('mdt:server:saveVehicleInfo', function(dbid, plate, imageurl, notes, stolen, code5, impound) + print(dbid, plate, imageurl, notes, stolen, code5, impound) + if plate then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + if dbid == nil then dbid = 0 end; + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + TriggerEvent('mdt:server:AddLog', "A vehicle with the plate ("..plate..") has a new image ("..imageurl..") edited by "..fullname) + if tonumber(dbid) == 0 then + MySQL.insert('INSERT INTO `mdt_vehicleinfo` (`plate`, `information`, `image`, `code5`, `stolen`) VALUES (:plate, :information, :image, :code5, :stolen)', { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1"), information = notes, image = imageurl, code5 = code5, stolen = stolen }, function(infoResult) + if infoResult then + TriggerClientEvent('mdt:client:updateVehicleDbId', src, infoResult) + TriggerEvent('mdt:server:AddLog', "A vehicle with the plate ("..plate..") was added to the vehicle information database by "..fullname) + end + end) + elseif tonumber(dbid) > 0 then + MySQL.update("UPDATE mdt_vehicleinfo SET `information`= :information, `image`= :image, `code5`= :code5, `stolen`= :stolen WHERE `plate`= :plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1"), information = notes, image = imageurl, code5 = code5, stolen = stolen }) + end + + if impound.impoundChanged then + local vehicle = MySQL.single.await("SELECT p.id, p.plate, i.vehicleid AS impoundid FROM `player_vehicles` p LEFT JOIN `mdt_impound` i ON i.vehicleid = p.id WHERE plate=:plate", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1") }) + if impound.impoundActive then + local plate, linkedreport, fee, time = impound['plate'], impound['linkedreport'], impound['fee'], impound['time'] + if (plate and linkedreport and fee and time) then + if vehicle.impoundid == nil then + local data = vehicle + MySQL.insert('INSERT INTO `mdt_impound` (`vehicleid`, `linkedreport`, `fee`, `time`) VALUES (:vehicleid, :linkedreport, :fee, :time)', { + vehicleid = data['id'], + linkedreport = linkedreport, + fee = fee, + time = os.time() + (time * 60) + }, function(res) + -- notify? + local data = { + vehicleid = data['id'], + plate = plate, + beingcollected = 0, + vehicle = sentVehicle, + officer = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + number = Player.PlayerData.charinfo.phone, + time = os.time() * 1000, + src = src, + } + local vehicle = NetworkGetEntityFromNetworkId(sentVehicle) + FreezeEntityPosition(vehicle, true) + impound[#impound+1] = data --what does inputting into this table do? + + --TriggerClientEvent("police:client:ImpoundVehicle", src, false, tonumber(args[1])) + TriggerClientEvent("police:client:ImpoundVehicle", src, true, fee) + end) + end + end + else + if vehicle.impoundid ~= nil then + local data = vehicle + local result = MySQL.single.await("SELECT id, vehicle, fuel, engine, body FROM `player_vehicles` WHERE plate=:plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1")}) + if result then + print("comes here?") + local data = result + MySQL.update("DELETE FROM `mdt_impound` WHERE vehicleid=:vehicleid", { vehicleid = data['id'] }) + + result.currentSelection = impound.CurrentSelection + result.plate = plate + print(json.encode(result)) + TriggerClientEvent('qb-mdt:client:TakeOutImpound', src, result) + end + + end + end + end + end + end + end +end) + +RegisterNetEvent('mdt:server:getAllLogs', function() + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if Config.LogPerms[Player.PlayerData.job.name] then + if Config.LogPerms[Player.PlayerData.job.name][Player.PlayerData.job.grade.level] then + local JobType = GetJobType(Player.PlayerData.job.name) + local infoResult = MySQL.query.await('SELECT * FROM mdt_logs WHERE `jobtype` = :jobtype ORDER BY `id` DESC LIMIT 250', {jobtype = JobType}) + + TriggerLatentClientEvent('mdt:client:getAllLogs', src, 30000, infoResult) + end + end + end +end) + +-- Penal Code + +local function IsCidFelon(sentCid, cb) + if sentCid then + local convictions = MySQL.query.await('SELECT charges FROM mdt_convictions WHERE cid=:cid', { cid = sentCid }) + local Charges = {} + for i=1, #convictions do + local currCharges = json.decode(convictions[i]['charges']) + for x=1, #currCharges do + Charges[#Charges+1] = currCharges[x] + end + end + for i=1, #Charges do + for p=1, #PenalCode do + for x=1, #PenalCode[p] do + if PenalCode[p][x]['title'] == Charges[i] then + if PenalCode[p][x]['class'] == 'Felony' then + cb(true) + return + end + break + end + end + end + end + cb(false) + end +end + +exports('IsCidFelon', IsCidFelon) -- exports['erp_mdt']:IsCidFelon() + +RegisterCommand("isfelon", function(source, args, rawCommand) + IsCidFelon(1998, function(res) + end) +end, false) + +RegisterNetEvent('mdt:server:getPenalCode', function() + local src = source + TriggerClientEvent('mdt:client:getPenalCode', src, Config.PenalCodeTitles, Config.PenalCode) +end) + +RegisterNetEvent('mdt:server:setCallsign', function(cid, newcallsign) + local Player = QBCore.Functions.GetPlayerByCitizenId(cid) + Player.Functions.SetMetaData("callsign", newcallsign) +end) + +RegisterNetEvent('mdt:server:saveIncident', function(id, title, information, tags, officers, civilians, evidence, associated, time) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + if id == 0 then + local fullname = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname + MySQL.insert('INSERT INTO `mdt_incidents` (`author`, `title`, `details`, `tags`, `officersinvolved`, `civsinvolved`, `evidence`, `time`, `jobtype`) VALUES (:author, :title, :details, :tags, :officersinvolved, :civsinvolved, :evidence, :time, :jobtype)', + { + author = fullname, + title = title, + details = information, + tags = json.encode(tags), + officersinvolved = json.encode(officers), + civsinvolved = json.encode(civilians), + evidence = json.encode(evidence), + time = time, + jobtype = 'police', + }, function(infoResult) + if infoResult then + for i=1, #associated do + MySQL.insert('INSERT INTO `mdt_convictions` (`cid`, `linkedincident`, `warrant`, `guilty`, `processed`, `associated`, `charges`, `fine`, `sentence`, `recfine`, `recsentence`, `time`) VALUES (:cid, :linkedincident, :warrant, :guilty, :processed, :associated, :charges, :fine, :sentence, :recfine, :recsentence, :time)', { + cid = associated[i]['Cid'], + linkedincident = infoResult, + warrant = associated[i]['Warrant'], + guilty = associated[i]['Guilty'], + processed = associated[i]['Processed'], + associated = associated[i]['Isassociated'], + charges = json.encode(associated[i]['Charges']), + fine = tonumber(associated[i]['Fine']), + sentence = tonumber(associated[i]['Sentence']), + recfine = tonumber(associated[i]['recfine']), + recsentence = tonumber(associated[i]['recsentence']), + time = time + }) + end + TriggerClientEvent('mdt:client:updateIncidentDbId', src, infoResult) + --TriggerEvent('mdt:server:AddLog', "A vehicle with the plate ("..plate..") was added to the vehicle information database by "..player['fullname']) + end + end) + elseif id > 0 then + MySQL.update("UPDATE mdt_incidents SET title=:title, details=:details, civsinvolved=:civsinvolved, tags=:tags, officersinvolved=:officersinvolved, evidence=:evidence WHERE id=:id", { + title = title, + details = information, + tags = json.encode(tags), + officersinvolved = json.encode(officers), + civsinvolved = json.encode(civilians), + evidence = json.encode(evidence), + id = id + }) + for i=1, #associated do + TriggerEvent('mdt:server:handleExistingConvictions', associated[i], id, time) + end + end + end + end +end) + +RegisterNetEvent('mdt:server:handleExistingConvictions', function(data, incidentid, time) + MySQL.query.await('SELECT * FROM mdt_convictions WHERE cid=:cid AND linkedincident=:linkedincident', { + cid = data['Cid'], + linkedincident = incidentid + }, function(convictionRes) + if convictionRes and convictionRes[1] and convictionRes[1]['id'] then + MySQL.update('UPDATE mdt_convictions SET cid=:cid, linkedincident=:linkedincident, warrant=:warrant, guilty=:guilty, processed=:processed, associated=:associated, charges=:charges, fine=:fine, sentence=:sentence, recfine=:recfine, recsentence=:recsentence WHERE cid=:cid AND linkedincident=:linkedincident', { + cid = data['Cid'], + linkedincident = incidentid, + warrant = data['Warrant'], + guilty = data['Guilty'], + processed = data['Processed'], + associated = data['Isassociated'], + charges = json.encode(data['Charges']), + fine = tonumber(data['Fine']), + sentence = tonumber(data['Sentence']), + recfine = tonumber(data['recfine']), + recsentence = tonumber(data['recsentence']), + }) + else + MySQL.insert('INSERT INTO `mdt_convictions` (`cid`, `linkedincident`, `warrant`, `guilty`, `processed`, `associated`, `charges`, `fine`, `sentence`, `recfine`, `recsentence`, `time`) VALUES (:cid, :linkedincident, :warrant, :guilty, :processed, :associated, :charges, :fine, :sentence, :recfine, :recsentence, :time)', { + cid = data['Cid'], + linkedincident = incidentid, + warrant = data['Warrant'], + guilty = data['Guilty'], + processed = data['Processed'], + associated = data['Isassociated'], + charges = json.encode(data['Charges']), + fine = tonumber(data['Fine']), + sentence = tonumber(data['Sentence']), + recfine = tonumber(data['recfine']), + recsentence = tonumber(data['recsentence']), + time = time + }) + end + end) +end) + +RegisterNetEvent('mdt:server:removeIncidentCriminal', function(cid, incident) + MySQL.update('DELETE FROM mdt_convictions WHERE cid=:cid AND linkedincident=:linkedincident', { + cid = cid, + linkedincident = incident + }) +end) + +-- Dispatch + +RegisterNetEvent('mdt:server:setWaypoint', function(callid) + local src = source + local Player = QBCore.Functions.GetPlayer(source) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + local calls = exports['qb-dispatch']:GetDispatchCalls() + TriggerClientEvent('mdt:client:setWaypoint', src, calls[callid]) + end + end +end) + +RegisterNetEvent('mdt:server:callDetach', function(callid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local playerdata = { + fullname = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + job = Player.PlayerData.job, + cid = Player.PlayerData.citizenid, + callsign = Player.PlayerData.metadata.callsign + } + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + TriggerEvent('dispatch:removeUnit', callid, playerdata, function(newNum) + TriggerClientEvent('mdt:client:callDetach', -1, callid, newNum) + end) + end + end +end) + +RegisterNetEvent('mdt:server:callAttach', function(callid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local playerdata = { + fullname = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + job = Player.PlayerData.job, + cid = Player.PlayerData.citizenid, + callsign = Player.PlayerData.metadata.callsign + } + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + TriggerEvent('dispatch:addUnit', callid, playerdata, function(newNum) + TriggerClientEvent('mdt:client:callAttach', -1, callid, newNum) + end) + end + end + +end) + +RegisterNetEvent('mdt:server:attachedUnits', function(callid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + local calls = exports['qb-dispatch']:GetDispatchCalls() + TriggerClientEvent('mdt:client:attachedUnits', src, calls[callid]['units'], callid) + end + end +end) + +RegisterNetEvent('mdt:server:callDispatchDetach', function(callid, cid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local playerdata = { + fullname = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + job = Player.PlayerData.job, + cid = Player.PlayerData.citizenid, + callsign = Player.PlayerData.metadata.callsign + } + local callid = tonumber(callid) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + TriggerEvent('dispatch:removeUnit', callid, playerdata, function(newNum) + TriggerClientEvent('mdt:client:callDetach', -1, callid, newNum) + end) + end + end +end) + +RegisterNetEvent('mdt:server:setDispatchWaypoint', function(callid, cid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local callid = tonumber(callid) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + local calls = exports['qb-dispatch']:GetDispatchCalls() + TriggerClientEvent('mdt:client:setWaypoint', src, calls[callid]) + end + end + +end) + +RegisterNetEvent('mdt:server:callDragAttach', function(callid, cid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local playerdata = { + name = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + job = Player.PlayerData.job.name, + cid = Player.PlayerData.citizenid, + callsign = Player.PlayerData.metadata.callsign + } + local callid = tonumber(callid) + local JobType = GetJobType(Player.PlayerData.job.name) + if JobType == 'police' or JobType == 'ambulance' then + if callid then + TriggerEvent('dispatch:addUnit', callid, playerdata, function(newNum) + TriggerClientEvent('mdt:client:callAttach', -1, callid, newNum) + end) + end + end +end) + +RegisterNetEvent('mdt:server:setWaypoint:unit', function(cid) + local src = source + local Player = QBCore.Functions.GetPlayerByCitizenId(cid) + local PlayerCoords = GetEntityCoords(GetPlayerPed(Player.PlayerData.source)) + TriggerClientEvent("mdt:client:setWaypoint:unit", src, PlayerCoords) +end) + +-- Dispatch chat + +RegisterNetEvent('mdt:server:sendMessage', function(message, time) + if message and time then + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + MySQL.scalar("SELECT pfp FROM `mdt_data` WHERE cid=:id LIMIT 1", { + id = Player.PlayerData.citizenid -- % wildcard, needed to search for all alike results + }, function(data) + if data and data[1] then + local ProfilePicture = ProfPic(Player.PlayerData.charinfo.gender, data[1]['profilepic']) + local callsign = GetResourceKvpString(Player.PlayerData.metadata.callsign..'-callsign') or "000" + local Item = { + profilepic = ProfilePicture, + callsign = Player.PlayerData.metadata.callsign, + cid = Player.PlayerData.citizenid, + name = '('..callsign..') '..Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + message = message, + time = time, + job = Player.PlayerData.job.name + } + dispatchMessages[#dispatchMessages+1] = item + TriggerClientEvent('mdt:client:dashboardMessage', -1, Item) + -- Send to all clients, for auto updating stuff, ya dig. + end + end) + end + end +end) + +RegisterNetEvent('mdt:server:refreshDispatchMsgs', function() + local src = source + local PlayerData = GetPlayerData(src) + if IsJobAllowedToMDT(PlayerData.job.name) then + TriggerClientEvent('mdt:client:dashboardMessages', src, dispatchMessages) + end +end) + +RegisterNetEvent('mdt:server:getCallResponses', function(callid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if IsPolice(Player.PlayerData.job.name) then + local calls = exports['qb-dispatch']:GetDispatchCalls() + TriggerClientEvent('mdt:client:getCallResponses', src, calls[callid]['responses'], callid) + end +end) + +RegisterNetEvent('mdt:server:sendCallResponse', function(message, time, callid) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + local name = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname + if IsPolice(Player.PlayerData.job.name) then + TriggerEvent('dispatch:sendCallResponse', src, callid, message, time, function(isGood) + if isGood then + TriggerClientEvent('mdt:client:sendCallResponse', -1, message, time, callid, name) + end + end) + end +end) + +RegisterNetEvent('mdt:server:setRadio', function(cid, newRadio) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player.PlayerData.citizenid ~= cid then + TriggerClientEvent("QBCore:Notify", src, 'You can only change your radio!', 'error') + return + else + local radio = Player.Functions.GetItemByName("phone") + if radio ~= nil then + TriggerClientEvent('mdt:client:setRadio', src, newRadio) + else + TriggerClientEvent("QBCore:Notify", src, 'You do not have a radio!', 'error') + end + end + +end) + +local function isRequestVehicle(vehId) + local found = false + for i=1, #impound do + if impound[i]['vehicle'] == vehId then + found = true + impound[i] = nil + break + end + end + return found +end +exports('isRequestVehicle', isRequestVehicle) -- exports['erp_mdt']:isRequestVehicle() + +RegisterNetEvent('mdt:server:impoundVehicle', function(sentInfo, sentVehicle) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + if sentInfo and type(sentInfo) == 'table' then + local plate, linkedreport, fee, time = sentInfo['plate'], sentInfo['linkedreport'], sentInfo['fee'], sentInfo['time'] + if (plate and linkedreport and fee and time) then + local vehicle = MySQL.query.await("SELECT id, plate FROM `player_vehicles` WHERE plate=:plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1") }) + if vehicle and vehicle[1] then + local data = vehicle[1] + MySQL.insert('INSERT INTO `mdt_impound` (`vehicleid`, `linkedreport`, `fee`, `time`) VALUES (:vehicleid, :linkedreport, :fee, :time)', { + vehicleid = data['id'], + linkedreport = linkedreport, + fee = fee, + time = os.time() + (time * 60) + }, function(res) + -- notify? + local data = { + vehicleid = data['id'], + plate = plate, + beingcollected = 0, + vehicle = sentVehicle, + officer = Player.PlayerData.charinfo.firstname.. " "..Player.PlayerData.charinfo.lastname, + number = Player.PlayerData.charinfo.phone, + time = os.time() * 1000, + src = src, + } + local vehicle = NetworkGetEntityFromNetworkId(sentVehicle) + FreezeEntityPosition(vehicle, true) + impound[#impound+1] = data + + --TriggerClientEvent("police:client:ImpoundVehicle", src, false, tonumber(args[1])) + TriggerClientEvent("police:client:ImpoundVehicle", src, true, fee) + end) + end + end + end + end + end +end) + +RegisterNetEvent('mdt:server:getImpoundVehicles', function() + TriggerClientEvent('mdt:client:getImpoundVehicles', source, impound) +end) + +RegisterNetEvent('mdt:server:removeImpound', function(plate, currentSelection) + print("Removing impound", plate, currentSelection) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + local result = MySQL.single.await("SELECT id, vehicle FROM `player_vehicles` WHERE plate=:plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1")}) + if result and result[1] then + local data = result[1] + MySQL.update("DELETE FROM `mdt_impound` WHERE vehicleid=:vehicleid", { vehicleid = data['id'] }) + TriggerClientEvent('police:client:TakeOutImpound', src, currentSelection) + end + end + end +end) + +RegisterNetEvent('mdt:server:statusImpound', function(plate) + local src = source + local Player = QBCore.Functions.GetPlayer(src) + if Player then + if GetJobType(Player.PlayerData.job.name) == 'police' then + local vehicle = MySQL.query.await("SELECT id, plate FROM `player_vehicles` WHERE plate=:plate LIMIT 1", { plate = string.gsub(plate, "^%s*(.-)%s*$", "%1")}) + if vehicle and vehicle[1] then + local data = vehicle[1] + local impoundinfo = MySQL.query.await("SELECT * FROM `mdt_impound` WHERE vehicleid=:vehicleid LIMIT 1", { vehicleid = data['id'] }) + if impoundinfo and impoundinfo[1] then + TriggerClientEvent('mdt:client:statusImpound', src, impoundinfo[1], plate) + end + end + end + end +end) + +function GetBoloStatus(plate) + MySQL.query.await("SELECT * FROM mdt_bolos where plate = ?", {plate}, function(bolo) + if bolo and bolo[1] then + return true + else + return false + end + end) +end + +function GetVehicleInformation(plate) + local result = MySQL.query.await('SELECT * FROM mdt_vehicleinfo WHERE plate = @plate', {['@plate'] = plate}) + if result[1] then + return result[1] + else + return false + end +end diff --git a/server/utils.lua b/server/utils.lua new file mode 100644 index 00000000..ed898fbd --- /dev/null +++ b/server/utils.lua @@ -0,0 +1,51 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +function GetPlayerData(source) + local Player = QBCore.Functions.GetPlayer(source) + return Player.PlayerData +end + +function UnpackJob(data) + local job = { + name = data.name, + label = data.label + } + local grade = { + name = data.grade.name, + } + + return job, grade +end + +function PermCheck(src, PlayerData) + local result = true + + if not Config.AllowedJobs[PlayerData.job.name] then + print(("UserId: %s(%d) tried to access the mdt even though they are not authorised (server direct)"):format(GetPlayerName(src), src)) + result = false + end + + return result +end + +function ProfPic(gender, profilepic) + if profilepic then return profilepic end; + if gender == "f" then return "img/female.png" end; + return "img/male.png" +end + +function IsJobAllowedToMDT(job) + if Config.PoliceJobs[job] then + return true + elseif Config.AmbulanceJobs[job] then + return true + elseif Config.DojJobs[job] then + return true + else + return false + end +end + +function GetNameFromPlayerData(PlayerData) + return ('%s %s'):format(PlayerData.charinfo.firstname, PlayerData.charinfo.lastname) +end diff --git a/shared/config.lua b/shared/config.lua new file mode 100644 index 00000000..b06501ac --- /dev/null +++ b/shared/config.lua @@ -0,0 +1,637 @@ +Config = Config or {} + +Config.RosterLink = { + ['police'] = '', + ['ambulance'] = '', + ['bcso'] = '', + ['doj'] = '', +} + +Config.Fuel = "lj-fuel" -- "LegacyFuel", "lj-fuel" + +Config.PenalCodeTitles = { + [1] = 'OFFENSES AGAINST PERSONS', + [2] = 'OFFENSES INVOLVING THEFT', + [3] = 'OFFENSES INVOLVING FRAUD', + [4] = 'OFFENSES INVOLVING DAMAGE TO PROPERTY', + [5] = 'OFFENSES AGAINST PUBLIC ADMINISTRATION', + [6] = 'OFFENSES AGAINST PUBLIC ORDER', + [7] = 'OFFENSES AGAINST HEALTH AND MORALS', + [8] = 'OFFENSES AGAINST PUBLIC SAFETY', + [9] = 'OFFENSES INVOLVING THE OPERATION OF A VEHICLE', + [10] = 'OFFENSES INVOLVING THE WELL-BEING OF WILDLIFE', +} + +Config.PenalCode = { + [1] = { + [1] = {title = 'Simple Assault', class = 'Misdemeanor', id = 'P.C. 1001', months = 7, fine = 500, color = 'green'}, + [2] = {title = 'Assault', class = 'Misdemeanor', id = 'P.C. 1002', months = 15, fine = 850, color = 'orange'}, + [3] = {title = 'Aggravated Assault', class = 'Felony', id = 'P.C. 1003', months = 20, fine = 1250, color = 'orange'}, + [4] = {title = 'Assault with a Deadly Weapon', class = 'Felony', id = 'P.C. 1004', months = 30, fine = 3750, color = 'red'}, + [5] = {title = 'Involuntary Manslaughter', class = 'Felony', id = 'P.C. 1005', months = 60, fine = 7500, color = 'red'}, + [6] = {title = 'Vehicular Manslaughter', class = 'Felony', id = 'P.C. 1006', months = 75, fine = 7500, color = 'red'}, + [7] = {title = 'Attempted Murder of a Civilian', class = 'Felony', id = 'P.C. 1007', months = 50, fine = 7500, color = 'red'}, + [8] = {title = 'Second Degree Murder', class = 'Felony', id = 'P.C. 1008', months = 100, fine = 15000, color = 'red'}, + [9] = {title = 'Accessory to Second Degree Murder', class = 'Felony', id = 'P.C. 1009', months = 50, fine = 5000, color = 'red'}, + [10] = {title = 'First Degree Murder', class = 'Felony', id = 'P.C. 1010', months = 0, fine = 0, color = 'red'}, + [11] = {title = 'Accessory to First Degree Murder', class = 'Felony', id = 'P.C. 1011', months = 0, fine = 0, color = 'red'}, + [12] = {title = 'Murder of a Public Servant or Peace Officer', class = 'Felony', id = 'P.C. 1012', months = 0, fine = 0, color = 'red'}, + [13] = {title = 'Attempted Murder of a Public Servant or Peace Officer', class = 'Felony', id = 'P.C. 1013', months = 65, fine = 10000, color = 'red'}, + [14] = {title = 'Accessory to the Murder of a Public Servant or Peace Officer', class = 'Felony', id = 'P.C. 1014', months = 0, fine = 0, color = 'red'}, + [15] = {title = 'Unlawful Imprisonment', class = 'Misdemeanor', id = 'P.C. 1015', months = 10, fine = 600, color = 'green'}, + [16] = {title = 'Kidnapping', class = 'Felony', id = 'P.C. 1016', months = 15, fine = 900, color = 'orange'}, + [17] = {title = 'Accessory to Kidnapping', class = 'Felony', id = 'P.C. 1017', months = 7, fine = 450, color = 'orange'}, + [18] = {title = 'Attempted Kidnapping', class = 'Felony', id = 'P.C. 1018', months = 10, fine = 450, color = 'orange'}, + [19] = {title = 'Hostage Taking', class = 'Felony', id = 'P.C. 1019', months = 20, fine = 1200, color = 'orange'}, + [20] = {title = 'Accessory to Hostage Taking', class = 'Felony', id = 'P.C. 1020', months = 10, fine = 600, color = 'orange'}, + [21] = {title = 'Unlawful Imprisonment of a Public Servant or Peace Officer.', class = 'Felony', id = 'P.C. 1021', months = 25, fine = 4000, color = 'orange'}, + [22] = {title = 'Criminal Threats', class = 'Misdemeanor', id = 'P.C. 1022', months = 5, fine = 500, color = 'orange'}, + [23] = {title = 'Reckless Endangerment', class = 'Misdemeanor', id = 'P.C. 1023', months = 10, fine = 1000, color = 'orange'}, + [24] = {title = 'Gang Related Shooting', class = 'Felony', id = 'P.C. 1024', months = 30, fine = 2500, color = 'red'}, + [25] = {title = 'Cannibalism', class = 'Felony', id = 'P.C. 1025', months = 0, fine = 0, color = 'red'}, + [26] = {title = 'Torture', class = 'Felony', id = 'P.C. 1026', months = 40, fine = 4500, color = 'red'}, + }, + [2] = { + [1] = {title = 'Petty Theft', class = 'Infraction', id = 'P.C. 2001', months = 0, fine = 250, color = 'green'}, + [2] = {title = 'Grand Theft', class = 'Misdemeanor', id = 'P.C. 2002', months = 10, fine = 600, color = 'green'}, + [3] = {title = 'Grand Theft Auto A', class = 'Felony', id = 'P.C. 2003', months = 15, fine = 900, color = 'green'}, + [4] = {title = 'Grand Theft Auto B', class = 'Felony', id = 'P.C. 2004', months = 35, fine = 3500, color = 'green'}, + [5] = {title = 'Carjacking', class = 'Felony', id = 'P.C. 2005', months = 30, fine = 2000, color = 'orange'}, + [6] = {title = 'Burglary', class = 'Misdemeanor', id = 'P.C. 2006', months = 10, fine = 500, color = 'green'}, + [7] = {title = 'Robbery', class = 'Felony', id = 'P.C. 2007', months = 25, fine = 2000, color = 'green'}, + [8] = {title = 'Accessory to Robbery', class = 'Felony', id = 'P.C. 2008', months = 12, fine = 1000, color = 'green'}, + [9] = {title = 'Attempted Robbery', class = 'Felony', id = 'P.C. 2009', months = 20, fine = 1000, color = 'green'}, + [10] = {title = 'Armed Robbery', class = 'Felony', id = 'P.C. 2010', months = 30, fine = 3000, color = 'orange'}, + [11] = {title = 'Accessory to Armed Robbery', class = 'Felony', id = 'P.C. 2011', months = 15, fine = 1500, color = 'orange'}, + [12] = {title = 'Attempted Armed Robbery', class = 'Felony', id = 'P.C. 2012', months = 25, fine = 1500, color = 'orange'}, + [13] = {title = 'Grand Larceny', class = 'Felony', id = 'P.C. 2013', months = 45, fine = 7500, color = 'orange'}, + [14] = {title = 'Leaving Without Paying', class = 'Infraction', id = 'P.C. 2014', months = 0, fine = 500, color = 'green'}, + [15] = {title = 'Possession of Nonlegal Currency', class = 'Misdemeanor', id = 'P.C. 2015', months = 10, fine = 750, color = 'green'}, + [16] = {title = 'Possession of Government-Issued Items', class = 'Misdemeanor', id = 'P.C. 2016', months = 15, fine = 1000, color = 'green'}, + [17] = {title = 'Possession of Items Used in the Commission of a Crime', class = 'Misdemeanor', id = 'P.C. 2017', months = 10, fine = 500, color = 'green'}, + [18] = {title = 'Sale of Items Used in the Commission of a Crime', class = 'Felony', id = 'P.C. 2018', months = 15, fine = 1000, color = 'orange'}, + [19] = {title = 'Theft of an Aircraft', class = 'Felony', id = 'P.C. 2019', months = 20, fine = 1000, color = 'green'}, + }, + [3] = { + [1] = {title = 'Impersonating', class = 'Misdemeanor', id = 'P.C. 3001', months = 15, fine = 1250, color = 'green'}, + [2] = {title = 'Impersonating a Peace Officer or Public Servant', class = 'Felony', id = 'P.C. 3002', months = 25, fine = 2750, color = 'green'}, + [3] = {title = 'Impersonating a Judge', class = 'Felony', id = 'P.C. 3003', months = 0, fine = 0, color = 'green'}, + [4] = {title = 'Possession of Stolen Identification', class = 'Misdemeanor', id = 'P.C. 3004', months = 10, fine = 750, color = 'green'}, + [5] = {title = 'Possession of Stolen Government Identification', class = 'Misdemeanor', id = 'P.C. 3005', months = 20, fine = 2000, color = 'green'}, + [6] = {title = 'Extortion', class = 'Felony', id = 'P.C. 3006', months = 20, fine = 900, color = 'orange'}, + [7] = {title = 'Fraud', class = 'Misdemeanor', id = 'P.C. 3007', months = 10, fine = 450, color = 'green'}, + [8] = {title = 'Forgery', class = 'Misdemeanor', id = 'P.C. 3008', months = 15, fine = 750, color = 'green'}, + [9] = {title = 'Money Laundering', class = 'Felony', id = 'P.C. 3009', months = 0, fine = 0, color = 'red'}, + }, + [4] = { + [1] = {title = 'Trespassing', class = 'Misdemeanor', id = 'P.C. 4001', months = 10, fine = 450, color = 'green'}, + [2] = {title = 'Felony Trespassing', class = 'Felony', id = 'P.C. 4002', months = 15, fine = 1500, color = 'green'}, + [3] = {title = 'Arson', class = 'Felony', id = 'P.C. 4003', months = 15, fine = 1500, color = 'orange'}, + [4] = {title = 'Vandalism', class = 'Infraction', id = 'P.C. 4004', months = 0, fine = 300, color = 'green'}, + [5] = {title = 'Vandalism of Government Property', class = 'Felony', id = 'P.C. 4005', months = 20, fine = 1500, color = 'green'}, + [6] = {title = 'Littering', class = 'Infraction', id = 'P.C. 4006', months = 0, fine = 200, color = 'green'}, + }, + [5] = { + [1] = {title = 'Bribery of a Government Official', class = 'Felony', id = 'P.C. 5001', months = 20, fine = 3500, color = 'green'}, + [2] = {title = 'Anti-Mask Law', class = 'Infraction', id = 'P.C. 5002', months = 0, fine = 750, color = 'green'}, + [3] = {title = 'Possession of Contraband in a Government Facility', class = 'Felony', id = 'P.C. 5003', months = 25, fine = 1000, color = 'green'}, + [4] = {title = 'Criminal Possession of Stolen Property', class = 'Misdemeanor', id = 'P.C. 5004', months = 10, fine = 500, color = 'green'}, + [5] = {title = 'Escaping', class = 'Felony', id = 'P.C. 5005', months = 10, fine = 450, color = 'green'}, + [6] = {title = 'Jailbreak', class = 'Felony', id = 'P.C. 5006', months = 30, fine = 2500, color = 'orange'}, + [7] = {title = 'Accessory to Jailbreak', class = 'Felony', id = 'P.C. 5007', months = 25, fine = 2000, color = 'orange'}, + [8] = {title = 'Attempted Jailbreak', class = 'Felony', id = 'P.C. 5008', months = 20, fine = 1500, color = 'orange'}, + [9] = {title = 'Perjury', class = 'Felony', id = 'P.C. 5009', months = 0, fine = 0, color = 'green'}, + [10] = {title = 'Violation of a Restraining Order', class = 'Felony', id = 'P.C. 5010', months = 20, fine = 2250, color = 'green'}, + [11] = {title = 'Embezzlement', class = 'Felony', id = 'P.C. 5011', months = 45, fine = 10000, color = 'green'}, + [12] = {title = 'Unlawful Practice', class = 'Felony', id = 'P.C. 5012', months = 15, fine = 1500, color = 'orange'}, + [13] = {title = 'Misuse of Emergency Systems', class = 'Infraction', id = 'P.C. 5013', months = 0, fine = 600, color = 'orange'}, + [14] = {title = 'Conspiracy', class = 'Misdemeanor', id = 'P.C. 5014', months = 10, fine = 450, color = 'green'}, + [15] = {title = 'Violating a Court Order', class = 'Misdemeanor', id = 'P.C. 5015', months = 0, fine = 0, color = 'orange'}, + [16] = {title = 'Failure to Appear', class = 'Misdemeanor', id = 'P.C. 5016', months = 0, fine = 0, color = 'orange'}, + [17] = {title = 'Contempt of Court', class = 'Felony', id = 'P.C. 5017', months = 0, fine = 0, color = 'orange'}, + [18] = {title = 'Resisting Arrest', class = 'Misdemeanor', id = 'P.C. 5018', months = 5, fine = 300, color = 'orange'}, + }, + [6] = { + [1] = {title = 'Disobeying a Peace Officer', class = 'infraction', id = 'P.C. 6001', months = 0, fine = 750, color = 'green'}, + [2] = {title = 'Disorderly Conduct', class = 'Infraction', id = 'P.C. 6002', months = 0, fine = 250, color = 'green'}, + [3] = {title = 'Disturbing the Peace', class = 'infraction', id = 'P.C. 6003', months = 0, fine = 350, color = 'green'}, + [4] = {title = 'False Reporting', class = 'Misdemeanor', id = 'P.C. 6004', months = 10, fine = 750, color = 'green'}, + [5] = {title = 'Harassment', class = 'Misdemeanor', id = 'P.C. 6005', months = 10, fine = 500, color = 'orange'}, + [6] = {title = 'Misdemeanor Obstruction of Justice', class = 'Misdemeanor', id = 'P.C. 6006', months = 10, fine = 500, color = 'green'}, + [7] = {title = 'Felony Obstruction of Justice', class = 'Felony', id = 'P.C. 6007', months = 15, fine = 900, color = 'green'}, + [8] = {title = 'Inciting a Riot', class = 'Felony', id = 'P.C. 6008', months = 25, fine = 1000, color = 'orange'}, + [9] = {title = 'Loitering on Government Properties', class = 'Infraction', id = 'P.C. 6009', months = 0, fine = 500, color = 'green'}, + [10] = {title = 'Tampering', class = 'Misdemeanor', id = 'P.C. 6010', months = 10, fine = 500, color = 'green'}, + [11] = {title = 'Vehicle Tampering', class = 'Misdemeanor', id = 'P.C. 6011', months = 15, fine = 750, color = 'green'}, + [12] = {title = 'Evidence Tampering', class = 'Felony', id = 'P.C. 6012', months = 20, fine = 1000, color = 'green'}, + [13] = {title = 'Witness Tampering', class = 'Felony', id = 'P.C. 6013', months = 0, fine = 0, color = 'green'}, + [14] = {title = 'Failure to Provide Identification', class = 'Misdemeanor', id = 'P.C. 6014', months = 15, fine = 1500, color = 'green'}, + [15] = {title = 'Vigilantism', class = 'Felony', id = 'P.C. 6015', months = 30, fine = 1500, color = 'orange'}, + [16] = {title = 'Unlawful Assembly', class = 'Misdemeanor', id = 'P.C. 6016', months = 10, fine = 750, color = 'orange'}, + [17] = {title = 'Government Corruption', class = 'Felony', id = 'P.C. 6017', months = 0, fine = 0, color = 'red'}, + [18] = {title = 'Stalking', class = 'Felony', id = 'P.C. 6018', months = 40, fine = 1500, color = 'orange'}, + [19] = {title = 'Aiding and Abetting', class = 'Misdemeanor', id = 'P.C. 6019', months = 15, fine = 450, color = 'orange'}, + [20] = {title = 'Harboring a Fugitive', class = 'Misdemeanor', id = 'P.C. 6020', months = 10, fine = 1000, color = 'green'}, + }, + [7] = { + [1] = {title = 'Misdemeanor Possession of Marijuana', class = 'Mask', id = 'P.C. 7001', months = 5, fine = 250, color = 'green'}, + [2] = {title = 'Felony Possession of Marijuana', class = 'Felony', id = 'P.C. 7002', months = 15, fine = 1000, color = 'green'}, + [3] = {title = 'Cultivation of Marijuana A', class = 'Misdemeanor', id = 'P.C. 7003', months = 10, fine = 750, color = 'green'}, + [4] = {title = 'Cultivation of Marijuana B', class = 'Felony', id = 'P.C. 7004', months = 30, fine = 1500, color = 'orange'}, + [5] = {title = 'Possession of Marijuana with Intent to Distribute', class = 'Felony', id = 'P.C. 7005', months = 30, fine = 3000, color = 'orange'}, + [6] = {title = 'Misdemeanor Possession of Cocaine', class = 'Misdemeanor', id = 'P.C. 7006', months = 7, fine = 500, color = 'green'}, + [7] = {title = 'Felony Possession of Cocaine', class = 'Felony', id = 'P.C. 7007', months = 25, fine = 1500, color = 'green'}, + [8] = {title = 'Possession of Cocaine with Intent to Distribute', class = 'Felony', id = 'P.C. 7008', months = 35, fine = 4500, color = 'orange'}, + [9] = {title = 'Misdemeanor Possession of Methamphetamine', class = 'Misdemeanor', id = 'P.C. 7009', months = 7, fine = 500, color = 'green'}, + [10] = {title = 'Felony Possession of Methamphetamine', class = 'Felony', id = 'P.C. 7010', months = 25, fine = 1500, color = 'green'}, + [11] = {title = 'Possession of Methamphetamine with Intent to Distribute', class = 'Felony', id = 'P.C. 7011', months = 35, fine = 4500, color = 'orange'}, + [12] = {title = 'Misdemeanor Possession of Oxy / Vicodin', class = 'Felony', id = 'P.C. 7012', months = 7, fine = 500, color = 'green'}, + [13] = {title = 'Felony Possession of Oxy / Vicodin', class = 'Felony', id = 'P.C. 7013', months = 25, fine = 1500, color = 'green'}, + [14] = {title = 'Felony Possession of Oxy / Vicodin with Intent to Distribute', class = 'Felony', id = 'P.C. 7014', months = 35, fine = 4500, color = 'orange'}, + [15] = {title = 'Misdemeanor Possession of Ecstasy', class = 'Misdemeanor', id = 'P.C. 7015', months = 7, fine = 500, color = 'green'}, + [16] = {title = 'Felony Possession of Ecstasy', class = 'Felony', id = 'P.C. 7016', months = 25, fine = 1500, color = 'green'}, + [17] = {title = 'Possession of Ecstasy with Intent to Distribute', class = 'Felony', id = 'P.C. 7017', months = 35, fine = 4500, color = 'orange'}, + [18] = {title = 'Misdemeanor Possession of Opium', class = 'Misdemeanor', id = 'P.C. 7018', months = 7, fine = 500, color = 'green'}, + [19] = {title = 'Felony Possession of Opium', class = 'Felony', id = 'P.C. 7019', months = 25, fine = 1500, color = 'green'}, + [20] = {title = 'Possession of Opium with Intent to Distribute', class = 'Felony', id = 'P.C. 7020', months = 35, fine = 4500, color = 'orange'}, + [21] = {title = 'Misdemeanor Possession of Adderall', class = 'Misdemeanor', id = 'P.C. 7021', months = 7, fine = 500, color = 'green'}, + [22] = {title = 'Felony Possession of Adderall', class = 'Felony', id = 'P.C. 7022', months = 25, fine = 1500, color = 'green'}, + [23] = {title = 'Possession of Adderall with Intent to Distribute', class = 'Felony', id = 'P.C. 7023', months = 35, fine = 4500, color = 'orange'}, + [24] = {title = 'Misdemeanor Possession of Xanax', class = 'Misdemeanor', id = 'P.C. 7024', months = 7, fine = 500, color = 'green'}, + [25] = {title = 'Felony Possession of Xanax', class = 'Felony', id = 'P.C. 7025', months = 25, fine = 1500, color = 'green'}, + [26] = {title = 'Possession of Xanax with Intent to Distribute', class = 'Felony', id = 'P.C. 7026', months = 35, fine = 4500, color = 'orange'}, + [27] = {title = 'Misdemeanor Possession of Shrooms', class = 'Misdemeanor', id = 'P.C. 7027', months = 7, fine = 500, color = 'green'}, + [28] = {title = 'Felony Possession of Shrooms', class = 'Felony', id = 'P.C. 7028', months = 25, fine = 1500, color = 'green'}, + [29] = {title = 'Possession of Shrooms with Intent to Distribute', class = 'Felony', id = 'P.C. 7029', months = 35, fine = 4500, color = 'orange'}, + [30] = {title = 'Misdemeanor Possession of Lean', class = 'Misdemeanor', id = 'P.C. 7030', months = 7, fine = 500, color = 'green'}, + [31] = {title = 'Felony Possession of Lean', class = 'Felony', id = 'P.C. 7031', months = 25, fine = 1500, color = 'green'}, + [32] = {title = 'Possession of Lean with Intent to Distribute', class = 'Felony', id = 'P.C. 7032', months = 35, fine = 4500, color = 'orange'}, + [33] = {title = 'Sale of a controlled substance', class = 'Misdemeanor', id = 'P.C. 7033', months = 10, fine = 1000, color = 'green'}, + [34] = {title = 'Drug Trafficking', class = 'Felony', id = 'P.C. 7034', months = 0, fine = 0, color = 'red'}, + [35] = {title = 'Desecration of a Human Corpse', class = 'Felony', id = 'P.C. 7035', months = 20, fine = 1500, color = 'orange'}, + [36] = {title = 'Public Intoxication', class = 'Infraction', id = 'P.C. 7036', months = 0, fine = 500, color = 'green'}, + [37] = {title = 'Public Indecency', class = 'Misdemeanor', id = 'P.C. 7037', months = 10, fine = 750, color = 'green'}, + }, + [8] = { + [1] = {title = 'Criminal Possession of Weapon Class A', class = 'Felony', id = 'P.C. 8001', months = 10, fine = 500, color = 'green'}, + [2] = {title = 'Criminal Possession of Weapon Class B', class = 'Felony', id = 'P.C. 8002', months = 15, fine = 1000, color = 'green'}, + [3] = {title = 'Criminal Possession of Weapon Class C', class = 'Felony', id = 'P.C. 8003', months = 30, fine = 3500, color = 'green'}, + [4] = {title = 'Criminal Possession of Weapon Class D', class = 'Felony', id = 'P.C. 8004', months = 25, fine = 1500, color = 'green'}, + [5] = {title = 'Criminal Sale of Weapon Class A', class = 'Felony', id = 'P.C. 8005', months = 15, fine = 1000, color = 'orange'}, + [6] = {title = 'Criminal Sale of Weapon Class B', class = 'Felony', id = 'P.C. 8006', months = 20, fine = 2000, color = 'orange'}, + [7] = {title = 'Criminal Sale of Weapon Class C', class = 'Felony', id = 'P.C. 8007', months = 35, fine = 7000, color = 'orange'}, + [8] = {title = 'Criminal Sale of Weapon Class D', class = 'Felony', id = 'P.C. 8008', months = 30, fine = 3000, color = 'orange'}, + [9] = {title = 'Criminal Use of Weapon', class = 'Misdemeanor', id = 'P.C. 8009', months = 10, fine = 450, color = 'orange'}, + [10] = {title = 'Possession of Illegal Firearm Modifications', class = 'Misdemeanor', id = 'P.C. 8010', months = 10, fine = 300, color = 'green'}, + [11] = {title = 'Weapon Trafficking', class = 'Felony', id = 'P.C. 8011', months = 0, fine = 0, color = 'red'}, + [12] = {title = 'Brandishing a Weapon', class = 'Misdemeanor', id = 'P.C. 8012', months = 15, fine = 500, color = 'orange'}, + [13] = {title = 'Insurrection', class = 'Felony', id = 'P.C. 8013', months = 0, fine = 0, color = 'red'}, + [14] = {title = 'Flying into Restricted Airspace', class = 'Felony', id = 'P.C. 8014', months = 20, fine = 1500, color = 'green'}, + [15] = {title = 'Jaywalking', class = 'Infraction', id = 'P.C. 8015', months = 0, fine = 150, color = 'green'}, + [16] = {title = 'Criminal Use of Explosives', class = 'Felony', id = 'P.C. 8016', months = 30, fine = 2500, color = 'orange'}, + }, + [9] = { + [1] = {title = 'Driving While Intoxicated', class = 'Misdemeanor', id = 'P.C. 9001', months = 5, fine = 300, color = 'green'}, + [2] = {title = 'Evading', class = 'Misdemeanor', id = 'P.C. 9002', months = 5, fine = 400, color = 'green'}, + [3] = {title = 'Reckless Evading', class = 'Felony', id = 'P.C. 9003', months = 10, fine = 800, color = 'orange'}, + [4] = {title = 'Failure to Yield to Emergency Vehicle', class = 'Infraction', id = 'P.C. 9004', months = 0, fine = 600, color = 'green'}, + [5] = {title = 'Failure to Obey Traffic Control Device', class = 'Infraction', id = 'P.C. 9005', months = 0, fine = 150, color = 'green'}, + [6] = {title = 'Nonfunctional Vehicle', class = 'Infraction', id = 'P.C. 9006', months = 0, fine = 75, color = 'green'}, + [7] = {title = 'Negligent Driving', class = 'Infraction', id = 'P.C. 9007', months = 0, fine = 300, color = 'green'}, + [8] = {title = 'Reckless Driving', class = 'Misdemeanor', id = 'P.C. 9008', months = 10, fine = 750, color = 'orange'}, + [9] = {title = 'Third Degree Speeding', class = 'Infraction', id = 'P.C. 9009', months = 0, fine = 225, color = 'green'}, + [10] = {title = 'Second Degree Speeding', class = 'Infraction', id = 'P.C. 9010', months = 0, fine = 450, color = 'green'}, + [11] = {title = 'First Degree Speeding', class = 'Infraction', id = 'P.C. 9011', months = 0, fine = 750, color = 'green'}, + [12] = {title = 'Unlicensed Operation of Vehicle', class = 'Infraction', id = 'P.C. 9012', months = 0, fine = 500, color = 'green'}, + [13] = {title = 'Illegal U-Turn', class = 'Infraction', id = 'P.C. 9013', months = 0, fine = 75, color = 'green'}, + [14] = {title = 'Illegal Passing', class = 'Infraction', id = 'P.C. 9014', months = 0, fine = 300, color = 'green'}, + [15] = {title = 'Failure to Maintain Lane', class = 'Infraction', id = 'P.C. 9015', months = 0, fine = 300, color = 'green'}, + [16] = {title = 'Illegal Turn', class = 'Infraction', id = 'P.C. 9016', months = 0, fine = 150, color = 'green'}, + [17] = {title = 'Failure to Stop', class = 'Infraction', id = 'P.C. 9017', months = 0, fine = 600, color = 'green'}, + [18] = {title = 'Unauthorized Parking', class = 'Infraction', id = 'P.C. 9018', months = 0, fine = 300, color = 'green'}, + [19] = {title = 'Hit and Run', class = 'Misdemeanor', id = 'P.C. 9019', months = 10, fine = 500, color = 'green'}, + [20] = {title = 'Driving without Headlights or Signals', class = 'Infraction', id = 'P.C. 9020', months = 0, fine = 300, color = 'green'}, + [21] = {title = 'Street Racing', class = 'Felony', id = 'P.C. 9021', months = 15, fine = 1500, color = 'green'}, + [22] = {title = 'Piloting without Proper Licensing', class = 'Felony', id = 'P.C. 9022', months = 20, fine = 1500, color = 'orange'}, + [23] = {title = 'Unlawful Use of a Motorvehicle', class = 'Misdemeanor', id = 'P.C. 9023', months = 10, fine = 750, color = 'green'}, + }, + [10] = { + [1] = {title = 'Hunting in Restricted Areas', class = 'Infraction', id = 'P.C. 10001', months = 0, fine = 450, color = 'green'}, + [2] = {title = 'Unlicensed Hunting', class = 'Infraction', id = 'P.C. 10002', months = 0, fine = 450, color = 'green'}, + [3] = {title = 'Animal Cruelty', class = 'Misdemeanor', id = 'P.C. 10003', months = 10, fine = 450, color = 'green'}, + [4] = {title = 'Hunting with a Non-Hunting Weapon', class = 'Misdemeanor', id = 'P.C. 10004', months = 10, fine = 750, color = 'green'}, + [5] = {title = 'Hunting outside of hunting hours', class = 'Infraction', id = 'P.C. 10005', months = 0, fine = 750, color = 'green'}, + [6] = {title = 'Overhunting', class = 'Misdemeanor', id = 'P.C. 10006', months = 10, fine = 1000, color = 'green'}, + [7] = {title = 'Poaching', class = 'Felony', id = 'P.C. 10007', months = 20, fine = 1250, color = 'red'}, + } +} + +Config.PoliceJobs = { + ['police'] = true, + ['lspd'] = true, + ['bcso'] = true, + ['sast'] = true, + ['sasp'] = true, + ['doc'] = true, + ['sapr'] = true, + ['pa'] = true -- yucky +} + +Config.AmbulanceJobs = { + ['ambulance'] = true, + ['doctor'] = true +} + +Config.DojJobs = { + ['lawyer'] = true, +} + +-- Leave my hacky code alone ya goblins +Config.AllowedJobs = {} +for index, value in pairs(Config.PoliceJobs) do + Config.AllowedJobs[index] = value +end +for index, value in pairs(Config.AmbulanceJobs) do + Config.AllowedJobs[index] = value +end +for index, value in pairs(Config.DojJobs) do + Config.AllowedJobs[index] = value +end +-- Leave my hacky code alone ya goblins + +Config.LogPerms = { + ['ambulance'] = { + [4] = true, + }, + ['police'] = { + [4] = true, + }, +} + +Config.ColorNames = { + [0] = "Metallic Black", + [1] = "Metallic Graphite Black", + [2] = "Metallic Black Steel", + [3] = "Metallic Dark Silver", + [4] = "Metallic Silver", + [5] = "Metallic Blue Silver", + [6] = "Metallic Steel Gray", + [7] = "Metallic Shadow Silver", + [8] = "Metallic Stone Silver", + [9] = "Metallic Midnight Silver", + [10] = "Metallic Gun Metal", + [11] = "Metallic Anthracite Grey", + [12] = "Matte Black", + [13] = "Matte Gray", + [14] = "Matte Light Grey", + [15] = "Util Black", + [16] = "Util Black Poly", + [17] = "Util Dark silver", + [18] = "Util Silver", + [19] = "Util Gun Metal", + [20] = "Util Shadow Silver", + [21] = "Worn Black", + [22] = "Worn Graphite", + [23] = "Worn Silver Grey", + [24] = "Worn Silver", + [25] = "Worn Blue Silver", + [26] = "Worn Shadow Silver", + [27] = "Metallic Red", + [28] = "Metallic Torino Red", + [29] = "Metallic Formula Red", + [30] = "Metallic Blaze Red", + [31] = "Metallic Graceful Red", + [32] = "Metallic Garnet Red", + [33] = "Metallic Desert Red", + [34] = "Metallic Cabernet Red", + [35] = "Metallic Candy Red", + [36] = "Metallic Sunrise Orange", + [37] = "Metallic Classic Gold", + [38] = "Metallic Orange", + [39] = "Matte Red", + [40] = "Matte Dark Red", + [41] = "Matte Orange", + [42] = "Matte Yellow", + [43] = "Util Red", + [44] = "Util Bright Red", + [45] = "Util Garnet Red", + [46] = "Worn Red", + [47] = "Worn Golden Red", + [48] = "Worn Dark Red", + [49] = "Metallic Dark Green", + [50] = "Metallic Racing Green", + [51] = "Metallic Sea Green", + [52] = "Metallic Olive Green", + [53] = "Metallic Green", + [54] = "Metallic Gasoline Blue Green", + [55] = "Matte Lime Green", + [56] = "Util Dark Green", + [57] = "Util Green", + [58] = "Worn Dark Green", + [59] = "Worn Green", + [60] = "Worn Sea Wash", + [61] = "Metallic Midnight Blue", + [62] = "Metallic Dark Blue", + [63] = "Metallic Saxony Blue", + [64] = "Metallic Blue", + [65] = "Metallic Mariner Blue", + [66] = "Metallic Harbor Blue", + [67] = "Metallic Diamond Blue", + [68] = "Metallic Surf Blue", + [69] = "Metallic Nautical Blue", + [70] = "Metallic Bright Blue", + [71] = "Metallic Purple Blue", + [72] = "Metallic Spinnaker Blue", + [73] = "Metallic Ultra Blue", + [74] = "Metallic Bright Blue", + [75] = "Util Dark Blue", + [76] = "Util Midnight Blue", + [77] = "Util Blue", + [78] = "Util Sea Foam Blue", + [79] = "Uil Lightning blue", + [80] = "Util Maui Blue Poly", + [81] = "Util Bright Blue", + [82] = "Matte Dark Blue", + [83] = "Matte Blue", + [84] = "Matte Midnight Blue", + [85] = "Worn Dark blue", + [86] = "Worn Blue", + [87] = "Worn Light blue", + [88] = "Metallic Taxi Yellow", + [89] = "Metallic Race Yellow", + [90] = "Metallic Bronze", + [91] = "Metallic Yellow Bird", + [92] = "Metallic Lime", + [93] = "Metallic Champagne", + [94] = "Metallic Pueblo Beige", + [95] = "Metallic Dark Ivory", + [96] = "Metallic Choco Brown", + [97] = "Metallic Golden Brown", + [98] = "Metallic Light Brown", + [99] = "Metallic Straw Beige", + [100] = "Metallic Moss Brown", + [101] = "Metallic Biston Brown", + [102] = "Metallic Beechwood", + [103] = "Metallic Dark Beechwood", + [104] = "Metallic Choco Orange", + [105] = "Metallic Beach Sand", + [106] = "Metallic Sun Bleeched Sand", + [107] = "Metallic Cream", + [108] = "Util Brown", + [109] = "Util Medium Brown", + [110] = "Util Light Brown", + [111] = "Metallic White", + [112] = "Metallic Frost White", + [113] = "Worn Honey Beige", + [114] = "Worn Brown", + [115] = "Worn Dark Brown", + [116] = "Worn straw beige", + [117] = "Brushed Steel", + [118] = "Brushed Black steel", + [119] = "Brushed Aluminium", + [120] = "Chrome", + [121] = "Worn Off White", + [122] = "Util Off White", + [123] = "Worn Orange", + [124] = "Worn Light Orange", + [125] = "Metallic Securicor Green", + [126] = "Worn Taxi Yellow", + [127] = "police car blue", + [128] = "Matte Green", + [129] = "Matte Brown", + [130] = "Worn Orange", + [131] = "Matte White", + [132] = "Worn White", + [133] = "Worn Olive Army Green", + [134] = "Pure White", + [135] = "Hot Pink", + [136] = "Salmon pink", + [137] = "Metallic Vermillion Pink", + [138] = "Orange", + [139] = "Green", + [140] = "Blue", + [141] = "Mettalic Black Blue", + [142] = "Metallic Black Purple", + [143] = "Metallic Black Red", + [144] = "Hunter Green", + [145] = "Metallic Purple", + [146] = "Metaillic V Dark Blue", + [147] = "MODSHOP BLACK1", + [148] = "Matte Purple", + [149] = "Matte Dark Purple", + [150] = "Metallic Lava Red", + [151] = "Matte Forest Green", + [152] = "Matte Olive Drab", + [153] = "Matte Desert Brown", + [154] = "Matte Desert Tan", + [155] = "Matte Foilage Green", + [156] = "DEFAULT ALLOY COLOR", + [157] = "Epsilon Blue", + [158] = "Unknown", +} + +Config.ColorInformation = { + [0] = "black", + [1] = "black", + [2] = "black", + [3] = "darksilver", + [4] = "silver", + [5] = "bluesilver", + [6] = "silver", + [7] = "darksilver", + [8] = "silver", + [9] = "bluesilver", + [10] = "darksilver", + [11] = "darksilver", + [12] = "matteblack", + [13] = "gray", + [14] = "lightgray", + [15] = "black", + [16] = "black", + [17] = "darksilver", + [18] = "silver", + [19] = "utilgunmetal", + [20] = "silver", + [21] = "black", + [22] = "black", + [23] = "darksilver", + [24] = "silver", + [25] = "bluesilver", + [26] = "darksilver", + [27] = "red", + [28] = "torinored", + [29] = "formulared", + [30] = "blazered", + [31] = "gracefulred", + [32] = "garnetred", + [33] = "desertred", + [34] = "cabernetred", + [35] = "candyred", + [36] = "orange", + [37] = "gold", + [38] = "orange", + [39] = "red", + [40] = "mattedarkred", + [41] = "orange", + [42] = "matteyellow", + [43] = "red", + [44] = "brightred", + [45] = "garnetred", + [46] = "red", + [47] = "red", + [48] = "darkred", + [49] = "darkgreen", + [50] = "racingreen", + [51] = "seagreen", + [52] = "olivegreen", + [53] = "green", + [54] = "gasolinebluegreen", + [55] = "mattelimegreen", + [56] = "darkgreen", + [57] = "green", + [58] = "darkgreen", + [59] = "green", + [60] = "seawash", + [61] = "midnightblue", + [62] = "darkblue", + [63] = "saxonyblue", + [64] = "blue", + [65] = "blue", + [66] = "blue", + [67] = "diamondblue", + [68] = "blue", + [69] = "blue", + [70] = "brightblue", + [71] = "purpleblue", + [72] = "blue", + [73] = "ultrablue", + [74] = "brightblue", + [75] = "darkblue", + [76] = "midnightblue", + [77] = "blue", + [78] = "blue", + [79] = "lightningblue", + [80] = "blue", + [81] = "brightblue", + [82] = "mattedarkblue", + [83] = "matteblue", + [84] = "matteblue", + [85] = "darkblue", + [86] = "blue", + [87] = "lightningblue", + [88] = "yellow", + [89] = "yellow", + [90] = "bronze", + [91] = "yellow", + [92] = "lime", + [93] = "champagne", + [94] = "beige", + [95] = "darkivory", + [96] = "brown", + [97] = "brown", + [98] = "lightbrown", + [99] = "beige", + [100] = "brown", + [101] = "brown", + [102] = "beechwood", + [103] = "beechwood", + [104] = "chocoorange", + [105] = "yellow", + [106] = "yellow", + [107] = "cream", + [108] = "brown", + [109] = "brown", + [110] = "brown", + [111] = "white", + [112] = "white", + [113] = "beige", + [114] = "brown", + [115] = "brown", + [116] = "beige", + [117] = "steel", + [118] = "blacksteel", + [119] = "aluminium", + [120] = "chrome", + [121] = "wornwhite", + [122] = "offwhite", + [123] = "orange", + [124] = "lightorange", + [125] = "green", + [126] = "yellow", + [127] = "blue", + [128] = "green", + [129] = "brown", + [130] = "orange", + [131] = "white", + [132] = "white", + [133] = "darkgreen", + [134] = "white", + [135] = "pink", + [136] = "pink", + [137] = "pink", + [138] = "orange", + [139] = "green", + [140] = "blue", + [141] = "blackblue", + [142] = "blackpurple", + [143] = "blackred", + [144] = "darkgreen", + [145] = "purple", + [146] = "darkblue", + [147] = "black", + [148] = "purple", + [149] = "darkpurple", + [150] = "red", + [151] = "darkgreen", + [152] = "olivedrab", + [153] = "brown", + [154] = "tan", + [155] = "green", + [156] = "silver", + [157] = "blue", + [158] = "black", +} + +Config.ClassList = { + [0] = "Compact", + [1] = "Sedan", + [2] = "SUV", + [3] = "Coupe", + [4] = "Muscle", + [5] = "Sport Classic", + [6] = "Sport", + [7] = "Super", + [8] = "Motorbike", + [9] = "Off-Road", + [10] = "Industrial", + [11] = "Utility", + [12] = "Van", + [13] = "Bike", + [14] = "Boat", + [15] = "Helicopter", + [16] = "Plane", + [17] = "Service", + [18] = "Emergency", + [19] = "Military", + [20] = "Commercial", + [21] = "Train" +} + +function GetJobType(job) + if Config.PoliceJobs[job] then + return 'police' + elseif Config.AmbulanceJobs[job] then + return 'ambulance' + elseif Config.DojJobs[job] then + return 'doj' + else + return nil + end +end + +-- this is a hack, because the qb-menu in qb-policejob populates an impound location and passed it through to the event. +-- if this impound locations are changed in qb-policejob, they must also be changed here. +Config.ImpoundLocations = { + [1] = vector4(436.68, -1007.42, 27.32, 180.0), + [2] = vector4(-436.14, 5982.63, 31.34, 136.0), +} \ No newline at end of file diff --git a/ui/app.js b/ui/app.js new file mode 100644 index 00000000..6e327537 --- /dev/null +++ b/ui/app.js @@ -0,0 +1,5048 @@ +let canSearchForProfiles = true; +let canSaveProfile = true; +let canRefreshBolo = true; +let canRefreshReports = true; +let canRefreshIncidents = true; +let canInputTag = true; +let canInputBoloTag = true; +let canInputBoloOfficerTag = true; +let canSearchReports = true; +let canCreateBulletin = 0; +let mouse_is_inside = false; +let currentTab = ".dashboard-page-container"; +let MyName = ""; +let canInputReportTag = true; +let canInputReportOfficerTag = true; +let canInputReportCivilianTag = true; +let canSearchForVehicles = true; +let canSearchForReports = true; +let canSaveVehicle = true; +var LastName = ""; +var DispatchNum = 0; +var playerJob = ""; +let rosterLink = ""; + +let impoundChanged = false; + +// TEMP CONFIG OF JOBS +const PoliceJobs = { + ['police']: true, +} + +const AmbulanceJobs = { + ['ambulance']: true, +} + +const DojJobs = { + ['lawyer']: true +} + +const MONTH_NAMES = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +function getFormattedDate(date, prefomattedDate = false, hideYear = false) { + const day = date.getDate(); + const month = MONTH_NAMES[date.getMonth()]; + const year = date.getFullYear(); + const hours = date.getHours(); + let minutes = date.getMinutes(); + + if (minutes < 10) { + minutes = `0${minutes}`; + } + + if (prefomattedDate) { + return `${prefomattedDate} at ${hours}:${minutes}`; + } + + if (hideYear) { + return `${day}. ${month} at ${hours}:${minutes}`; + } + + return `${day}. ${month} ${year}. at ${hours}:${minutes}`; +} + +function timeAgo(dateParam) { + if (!dateParam) { + return null; + } + + const date = + typeof dateParam === "object" ? dateParam : new Date(dateParam); + const DAY_IN_MS = 86400000; + const today = new Date(); + const yesterday = new Date(today - DAY_IN_MS); + const seconds = Math.round((today - date) / 1000); + const minutes = Math.round(seconds / 60); + const isToday = today.toDateString() === date.toDateString(); + const isYesterday = yesterday.toDateString() === date.toDateString(); + const isThisYear = today.getFullYear() === date.getFullYear(); + + if (seconds < 5) { + return "Just Now"; + } else if (seconds < 60) { + return `${seconds} Seconds ago`; + } else if (seconds < 90) { + return "About a minute ago"; + } else if (minutes < 60) { + return `${minutes} Minutes ago`; + } else if (isToday) { + return getFormattedDate(date, "Today"); + } else if (isYesterday) { + return getFormattedDate(date, "Yesterday"); + } else if (isThisYear) { + return getFormattedDate(date, false, true); + } + + return getFormattedDate(date); +} + +$(document).ready(() => { + $(".header").hover( + function () { + $(".close-all").css("opacity", "0.5"); + }, + function () { + $(".close-all").css("opacity", "1"); + } + ); + $(".incidents-charges-title-container").hover( + function () { + $(".incidents-charges-table-container").css("opacity", "0.1"); + $(".close-all").css("filter", "none"); + }, + function () { + $(".close-all").css("filter", "brightness(30%)"); + $(".incidents-charges-table-container").css("opacity", "1"); + } + ); + $(".nav-item").click(function () { + if ($(this).hasClass("active-nav") == false) { + fidgetSpinner($(this).data("page")); + currentTab = $(this).data("page"); + } + }); + + $(".profile-items").on("click", ".profile-item", async function () { + let id = $(this).data("id"); + let result = await $.post( + `https://${GetParentResourceName()}/getProfileData`, + JSON.stringify({ + id: id, + }) + ); + + if (!canInputTag) { + if ($(".tags-add-btn").hasClass("fa-minus")) { + $(".tags-add-btn") + .removeClass("fa-minus") + .addClass("fa-plus"); + } + $(".tag-input").remove(); + canInputTag = true; + } + + if ($(".gallery-upload-input").css("display") == "block") { + $(".gallery-upload-input").slideUp(250); + setTimeout(() => { + $(".gallery-upload-input").css("display", "none"); + }, 250); + } + + if ($(".gallery-add-btn").hasClass("fa-minus")) { + $(".gallery-add-btn") + .removeClass("fa-minus") + .addClass("fa-plus"); + } + + $(".manage-profile-editing-title").html(`You are currently editing ${result["firstname"]} ${result["lastname"]}`); + $(".manage-profile-citizenid-input").val(result['cid']); + $(".manage-profile-name-input-1").val(result["firstname"]); + $(".manage-profile-name-input-2").val(result["lastname"]); + $(".manage-profile-dob-input").val(result["dob"]); + $(".manage-profile-job-input").val(`${result.job}, ${result.grade}`); + $(".manage-profile-url-input").val(result["profilepic"]); + $(".manage-profile-info").val(result["mdtinfo"]); + $(".manage-profile-info").removeAttr("disabled"); + $(".manage-profile-fingerprint").val(result["fingerprint"]); + $(".manage-profile-fingerprint").removeAttr("disabled"); + $(".manage-profile-pic").attr("src", result["profilepic"]); + + const { vehicles, tags, gallery, convictions, properties } = result + + $(".licenses-holder").empty(); + $(".tags-holder").empty(); + $(".vehs-holder").empty(); + $(".gallery-inner-container").empty(); + $(".convictions-holder").empty(); + + let licencesHTML = '
• ${value.text + } (${timeAgo( + Number(value.time) + )})
` + ); + }); + } else if (eventData.type == "statusImpound") { + const table = eventData.data; + const plate = eventData.plate; + const linkedreport = table["linkedreport"]; + const fee = table["fee"]; + const time = table["time"] * 1000; + + let localDate = new Date(time); + const impoundDate = localDate.toLocaleDateString("en-US", { + timeZone: "UTC", + }); + const impoundTime = localDate.toLocaleTimeString("en-US", { + timeZone: "UTC", + }); + + $(".impound-plate").val(plate).attr("disabled", "disabled"); + $(".impound-linkedreport") + .val(linkedreport) + .attr("disabled", "disabled"); + $(".impound-fee") + .val("$" + fee) + .attr("disabled", "disabled"); + + if (table.paid === 1) { + $(".impound-fee").css("color", "green"); + } else { + $(".impound-fee").css("color", "red"); + } + + $(".impound-time") + .val(impoundDate + " - " + impoundTime) + .attr("disabled", "disabled"); + $(".impound-cancel").html("Close"); + $(".impound-submit").fadeOut(250); + $(".impound-form").slideDown(250); + $(".impound-form").fadeIn(250); + } else if (eventData.type == "greenImpound") { + $(".vehicle-tags") + .find(".impound-tag") + .addClass("green-tag") + .removeClass("red-tag"); + } else if (eventData.type == "redImpound") { + $(".vehicle-tags") + .find(".impound-tag") + .removeClass("green-tag") + .addClass("red-tag"); + } + }); +}); + +function fidgetSpinner(page) { + $(".close-all").fadeOut(0); + $(".container-load").fadeIn(0); + if (page == ".dashboard-page-container"){ + $.post(`https://${GetParentResourceName()}/getAllDashboardData`, JSON.stringify({})); + } + if (page == ".bolos-page-container") { + $.post(`https://${GetParentResourceName()}/getAllBolos`, JSON.stringify({})); + } + if (page == ".reports-page-container") { + $.post(`https://${GetParentResourceName()}/getAllReports`, JSON.stringify({})); + } + if (page == ".stafflogs-page-container") { + $.post(`https://${GetParentResourceName()}/getAllLogs`, JSON.stringify({})); + } + if (page == ".incidents-page-container") { + $.post(`https://${GetParentResourceName()}/getAllIncidents`, JSON.stringify({})); + } + setTimeout(() => { + $(".container-load").fadeOut(0); + $(page).fadeIn(0); + }, 1250); +} + +function timeShit() { + let localDate = new Date(); + const myTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone + date = localDate.toLocaleDateString("en-US", { + timeZone: myTimeZone, + }); + time = localDate.toLocaleTimeString("en-US", { + timeZone: myTimeZone, + }); + $(".date").html(date); + $(".time").html(time); +} + +setInterval(timeShit, 1000); + +function addTag(tagInput) { + $(".tags-holder").prepend(`6%r3Q9KS=fBw=K@_M11Ji?Ik;}Jh zEHvYKFp`296A=GeW+i3XE0l0i7enVuNY)~+z(|UCkbK1rpOv1mH5lXaVG*6S5$DLx zm7_D5`?Pv&;;+}%;)2X_NC}?ACq*G6D~@8Qfuf>LMD56!3}_?rDVX;o$IGC*78Qmi zXKLT)=qEM0kusXKRGAd*2hq 8eQqxjbe*_zbQ`jhfSJ#AG@S)Hfa!V{k&E{vv-J@dh+Y|FeM6zel!;i zv*B=qO3XFv^c>k!Nvjx< C#dZ_jTPbaGUz8fDdPloB6b z+2TWeM*zEPygV4>l+*E2yd5}4cCH-#fvhO$&wn=4gu8x`3qmOvMAE*hyA6d_r@;Kx zZD^p;{HM2SQAzTCno)+e)qcz*C+C_KE+G2tM~R~5y2r`6Mx&4f4?QVuR^rJSGjg)k z16w)SVkUxB&H==i_fK(SF{zx+ deISK~SyBqmTKRbP}GHJyIq( z|7G;rTi-|%8bXh85PRpYYUC$*1!iZs $ZxxWOkHrt+eU zS_*oTQ{_Y-M`0`-L;D#pqR@azq?(T(VuZ`?#o?;1+2rhu+xF4nz**Y4aC9~K&1V-3 z)acG9**A(34ZFjSB;pNO-shf!7K;Zfx7+a5{6?f0B%+%X_Y534Bod#$(2p>=9M8^= z!*lcFk1z$pDB67fU?qNcQ4?;N+AeyqtUJUB(6Nrj)9vaaBd!=@6}983^l%Olgkh&X zUhd}HCx>Q$$1kqMW!VQQX1kG-7YlQ23> Q6pRP9!sQCy`+bb=Q)! zNqzvH^t`su*PX4SA7hd*%;5;H>KBgs0gG^W#_ 612lqed7fD&t!?ZYOUdjM zgm>RND`!6!j^0nBko@Z9M{_Z3xLGhNoNUIB!}A)?wstQnN*wrVeFw4?KG8F9WtAUF zy5Z|S7>wu$g{Z#cGS{9gzdzmFH}0&(a^k;Zi~&(oGO@sp<@+LzDc ItC)CC72ZK=hWvD}Z)gpN)XVedGY6Z0T93?>Js+(4r4#@Ui% z@V5AkMDl}9E?z77p22WV9le)Hi3?C95~d#{@Vz9^O5fWpaJ-VYk@0)c-_(SQ$8}M3 z*AY}W`J(WN1nC~Tv{94}%zP?`ShHha%ecMKs>DB5nMFO!@oR56@^2)lIhQEQ!PyF= zC28>9qE=#gfQ(2fw%7TQo1hfiP;w&5rP Q#5Q+`TM;h7 zS*$-jTSw>3uI@KS$VSW_8G!_0Ri+3;Pi);pJ+ZO7vo}L|?}BCIv}d z1b>q7@usLFPR{?IMg!&ftI9Dd;t+HcsfLV*rvWH~LKTIC)I6x7i0M$Io(N6Yark$k z?lO#XMaKSEj^1MoW8^X9@B;-qBoDR`jP#4SNGxP_cH*ir7UKTp*h-Ki& rN7dcwHwiG7uQP+nAEPbfgte z&)EY-X9Fe>kE_;cU?)m3GcO;ym@z~J7Iw2GeXW!T9Ch+@X+O53_ZU08f+$T*0ewB1 zVdX^W((O}H(&}<}k)?0O Zia3Tum<7(}#Vsvh`ar3LRVZWBBE zX-RY&Ffft=W&|Q56=)=1s;hDktPIGSoFQ&my$)w+XX@yBnMAUFcAhf*&qN72>4si1 zGfr%HWgsZR?I;+N*OheQy=4}ZG L#uL% z&A|9eJDXa?f^DfvgWZ*yBQqi2_j-CZH%_#mqFE^-_`?@9i^8rK7sng}3%MWUs>jQa zLeK4XQn7iRz|E5_*i5qp!%?o^;bOXrN6>Se7A>}-*;PU00Xw@ r)0w#ykNi0H6 zI@Hl~2!iJbX}-5fy8rKF1yJX75d74R-eV+o2{DS`m`d6pYI3-Q)@^70y%Cuinj?{z zjZ2%kuKk9I9U`%0Hs#x$#$(_L+wD^=LeJm?^ra;+g5l!V+h~^PDa&BUIXuga!W1ut z)9n8qQH*Vr%W+GM1Eh%eCq{<5{mz&XkBOs9qJ#1Hp%Iuo&Ww^;4@zpfGW>y12hNak zJQGJ}>f@iY`>CU5Opk?@oJgkRzTOI =n0k0>?{*JfO@Y@fQu&s=QB7!lT=y1i}Z$3!I zXIqX{M0t_IUoYH+P s;0>^*GWP+ee^P%X8rn^IC>_1{(Vo~m#&&Q zI}r`_Zo+(0QYixj7o=jllG4L=d_WOBk@(jGtpOY&$ne+SW?|OYC_!cY&p*TM*-mC$ zIk$W-7Yn(?0_Ms6?d0S-e7QYRBt{>sHwj8>2MHlPV?{x97k9I|eZVegDc$Wq*o3w= zA2PG!U`)DD@XapLRX+J<0~V~ w#hybBtSxei-jDmaFAw<;?~lse9~c!h zw~$I&?hr+D=gp2spv8gGdhZasH^lW2>v0-(y7lOM#dDQPiQYnRg$uT7dB4n59qNc0 z(uBf`3HBVaqlTE4oOC&MHw7_coC&vI7B6t$J#QzUw9K@(9VBt%k{2@Y_RniX&y@G> zslxs5CF5|tO4L}i9jAcaKDh;RqPHVbtw4TZl-Q>yZvZ8lx~Vh@5^$)j5ubgz6b&^c za8sh3WY!`-Q;GVPdPFLfus3w#t!*1HY{W?1a>MyQYEw#{$yV-YgVo)IpTeT0K8#e_ z(0BV(uD!8Cl~#h3G%d}55;WE|; {@K zv9Q#Sd3Sm~iDti8V12wI%jFx7eHxEfZmB02n}rcOJF$~;w>jBz!sQP9{=Fvr;cgZ3 z2}UWqyCk=?xM8(=ke#W= !rm|yi{%w<5G266NbQFjqp~@nhoUmxbWg-zPE@{9!Z-0V 2+UsM^TBJY#kwsem^3;UKc?OH+5sG>Q{tzv8ICHvRlNYJ(8+0N zMWGR #xJ9pL`CIvN)Yw_(np5T2u%{`N-?i`w;x1ZlQbbYk|;_F^ijq~}eE7X9n< zb9FR`B&e=(AtPCX*lK@tgFQ41rxDdpr`^R((OStbZXT_} )8-@jFhY`VySF%qn*3}D81GyeLUEYWS2 z>%W`2`Ep*p{@MLim`ji(#q1NsK|a6iq_~}{=4?(G{<-GZf}=+-Zo~x57WC_96nTQ# zq(*Y~7|Mjs57&W{mDjZ6`4`_s967XJXU60S7Za2ki_t@eL7`4SQ-=z>4m46yMp8L( z=DvUe9X4W-h7E=(Qi~tO+ 92}$QUe~1I42O9DAH_fovgJRz$-jpDps7HfNhT%gZF>-JuhV|1^ zgf}BDPL0j`?Go}gc9xAe#nOONz0>33_0qei_SZ_eKcbnq@{&4&2Wc9(DS_c_Lk>^^ zdZ^fjA(U9`Z4SU1kl}-83TQB>@WR4a(P+QNxcH})63)L|*C1s3FjZ&~EAP2Fk!zhh z=tgjVLb*KLOp{m5AIbP>I=F+xjPd$i$femrOUaADs77=e;c@-sW9m~5lr+MKX1Kb7 z$jKRlLHXH7WPFp+fZU8!4DDZtPv);cT#ODjn-eV^PMD%&keBttJ bEG4|QN$Rg@qK*bWjq#?R>z666fp zAYNDyi#L`>A5$I5QJ%#nUOdjYoqN%+m36Os-BYO(`Bz?YNUSd74b*bYGBi2DyIRkf z93yIVBBR2e$t%9%O$z9Vg=uN&8qnAoWcacQr?C9)RPXeqIWh9tk35)zbQ =_O`;ENRF3^O#rx-BQa|fC7zRy zadNg6ixVMt1I-o^bV!nD0J+GKZK!JQplP@hdx{UErri#8OeU7EKY%E`lyJKgwk|ow zkLrgX#m>Od88^TB=;d&^Jy^H10;glA WV^fs{6NUhb_nF0( zl5Fg-+)aV|$L#Gc6zlWaxz30fv0q= jw zETvdq=@ms^(MB0`6v(W+8y@9eJb1=Wj3w$>fo;32>7=$ZgjtRssDn9GjIQRzzFgq% z_wnKho9I_wO}LyBQr?Al#TF}!G*I^t%VIJq@ta#yL>q_Se;!Yw&WSV?-|`1(0IWY~ zz}QFgMZy1P6!p3H2{S6p(OyQ;Vh4AY>eYFc3%s6~xgTn&NAyr SfJbFS%AhgusiR0ZQK0|8VQ>%!nGi!1m*%IENl(oR zGGmdQ8jU&Q3&diPAKL;%8(Z$(4@s+6^k&J(HXt@skH6nsC1A^`7+)m^t51JXg;$73 z;H8k XAFr#0FwcqV{KHDMVA*c^io6u=zoR9y+XEY~S35 zom< eFcoe&||G#nG95m}!ntQP%4e5tPaN7#e9!R?4L7T-Zg)LP>*1 zl+xBXLb&nbI00+8_h0WWSjXD|q6%4Un?2ZyZ+1k8DQ?Jb^3ngn0z60p(KFx1W5MpD z{lGb~`RA>N(NR%{(c_{8Y|0&w3yHwweG^E73zCtVHv~$;=ToOnLuzU&j7B43Vq%b( zn26ZeSd1Gt4wEKLBCKCP!OTbl!-dT|O8;Z_6BG-=XABk&UYL_eP9KM#!UFYO=(zs? zWJD#$#;S H(5L<}2T4&rAqKMXJ?3ST zT>W_EtQd^wZ@?aczQpcfVzLZxd|Zcn|4@Q|5QcAd15;)vAeWfF_BIa& fqco70(C+XW#VHe#)z9faY{TI8(pFyKfzkOxX6NG33BXj31DEJPB1Mz zjXnHn;t%!|d1Iwn(1%Z4+JqJR3`Z6@FQX}ucXsH#8P;DeFGtyi1GwhqOjMNGF>nM? zh>=(S;A)@qrVodOqQap~OX(XcQA@BTCo>fezYonmO2AwWm<`0fG`A6E&p<*%8;qo; z{ySni P1$N_ zMt_88;g2?k8@Mvn$o&9tK##w8xVOA$MM$&wON(N}@BaQ+4rbiC7YEvin$}AM8#YK! zP @G<^^d0(gbiT7acjPytQWXi0{!cn3B0lsKK=Jk- z4u=B4U=S~V(u|>$fSS}2vDM+jmn6VQD%-OCda?V%F&`!guXYU{cfX(hmXg(bYsBJ< z9y{20tn+eFSDd#3`E!%d*4l;Kf>=llGf#O*Ta;0U{P=Y7)e;P9;$uxvC>6w|tHrAN zAYD{oPce4vA*ymz92A;=k7~&R$&!r+arK-b7&9onPnEV`cDy*)oNh0k{^xS+K3IR! zbIL~+!t{q5j`X=aBXe#h)pWfO+cHRxxXw@}YFk>m+mJAvxqy;Q?rwG$MRG2@V+=lT zlSw?^-g`8q#dt6%-7N~mI^E%GdW!-ccXk*`QF;WiFuQkKkrp8r&qYEs_TF6 (OqJN2Y0_{54%c*bza|8)CxEmh zH8LY4yc$IGEAN-3#f=xn3Hs^rc?m~W3-Pmh g!lM zIJX66iV6dgk5vqF6Fj)FaGzKJ%sWO$M5v&NJx?f}r)nul(P(P#fQLk(+QugAJya%c zVu- ~}@1J8Z~tKEIl*Bz>8# r+= zadKwWG^!A${2}Lvle5)Xl*HP^(2OR*6L+P7CScJjgV;XosrMl@O^4>jus`c4nok`W ziTkcgAkj%L(JLjtre9zEeV2cHMCSm~ 1g&@zCr_c-q=gI5r8bEgmEjO$hbrF(PTnQTTG(Zp<7vL|ju_ zC-(sK(oIu~EpE5&Ie_ZMW?VQW7a9G>{dgSuDO!ML%a=Y{M;w10*1mV|34h;vxE?cZ ze+{kHA6CJGRrPSZR05y$Y_pCosK?6?mmEU@M~1+G3S2)S3U96mi+IelzeH3rZ&1K1 z#@hSnRbSnvAi6o+9-W^9Q LaxYfxvf;u> zCRoW2eFWFIBs@Jsk7pjw!=Nu4BA$G&;Vp8>GMuEk@pwn?eJ3JSa*1T{P@MX}uUeG4U_S~>&wF4s}keA{K==AXRhPft;PCQVb!k577nSNR9 DuUpOj>L%C zh8Bz%)DJCf9WX_NH@LD`?Rey`Z{qs7BxvZ-zA$k_Y!TiDb X68L1@fO34XDW$5RelwtWk^LPU|eE2XHuUSn(kr4xOGey&^R*MynzVH@`ODfQ5 z_Ysbjp}P8
jW=oHP+!JXqO6s%)FTCX$JK5c_(vpF2#K)Bg25ic(rH?&x$BF z{M@*`12rvPy#8G)xbrjb%*_2nZoMR4?5Myv%ik>=umsE$(;u#Ez{kI+7Ui6b%eds0 z_xn8FI$csB3yTZmTHsj?s5{t*QA8z5>12BIP~xr9aVAz(Z$WEwJ-qHleE7{mEZ?vl zOV(}{Kd-xRCMJ#?EUur6yCNb^tBm4DGo?a~>7#N;)%D`lPc~rK70+SfO@G7YJ(YxW z6*zz5k0fY)pOG`VzbJxdwH4;LSc>W w8r-MSP5Wt<)Dc-I;&f>?Fzb-?6b9hVbZx8!0aYPgbQ?|_8j<4*s zYG}?NJ7w5j(d~)3RBrFQm_|DUz=ycEazMqqfP40ePpM=>Axi8Xrl5mrNv_ zTlUw8-xU$9IGO(Y*EdbU=s{`d6W9F679z-Gduk$_{NHrF06$4$C|{TJ9{2 z8JF|#dW#ZH=P|$c`T}_BpDU1)8Y9@EQ0Q!v%+t3(RXbdtmq34AB9fCGa1=L+O?>~p zv>Q&*C9&Mk8!T`u!Kbfnz!VBz+HRJHb(Rl^hkudybKd!kcNE!D7A64p6HBw5-Zw6f zD9fp4%$whgtOyx)*Z7f?t`j96K7!J$)L%Tsj48v6_|>0FVfWhE$@|>Lv@g&3{ls)- z-q>LgC?J`&k`lnoWDP~S5K2qzsI7J5>kS=9r`dfUC!vI&Uwb@P$o;pzmVTrHksI1n zwy03vsuGy}9|UQ5MM{#%D|VdV78JKkwg^!nC&4RJR8RM|;ZqCkm>FM$A;V3g!A*30 z4kY@C-JSG**mmx##OL$Az^=WesBZD2xz&c>{`_W)8Z>~CQXS%AOf)dV+dQJXdskjY z8fHxzjUC0Mxa*g{C4N1CFBk5h= jlNlFyor;}yt;;Ez zzqA$F%L);cduZ0?o$5OAYSLggoNI2a}WWR*0#wO6)ClqKWiZZWzj3rpq3Hn;gHv9>Vn(#)G#Z z;smqzhOdm%TYBy5)8>@nPhZ7h#3T8*_T@CJEHa2@HnRuW;PXk2G{a=!;g>V((9!M@ zOiomkhMauNDJP(ZD_ZdD-~SV}9XdSy%E$QEJ0IZ1m;Z(ZYqyHXXV7WEmDGpIs|XVD zrn6kL(~1RKit**v3S{>mj-kUwp_HP^m `C0fzZf#v_=It}h5B4Y4w3-G`PNjP+zAb9*v+Fx{UF2*GR z`59VNT7wvsEXTg0PV8^=i#@MeTDowM$PzA1x$1@tv0|xzf?^20{m$dB^?2`Lj1x@H z(jzubjZ!*EXQvN!G;?aRb!cjEqq5m2?wQRKf}cjx+(~AUEx*6cgi|r*Oy_Nl!q?lQ zM5W^2Z>uF9_NbK1YY(L@6&N=uR>U8*f!i);p3=GZAFRO_^S*-J>A~|ay$X|w)K7&1 zX=$nOs?AtZ)I_vF7Xoe@3Q}TlsIs2^F2kWVKSoTNg@Z-=F!zGFC@wA{=A{}pO}+^w zB_$|on~jR^4&qj&5(Sy(hUxhE+*DY*PJM5ov$0%nG)zvK#m-?w7n30cI4RBOp`ZtP zy-KX^x |;*}_;Nd53*Pl@Q= z(a|A_w`4LoGBYz_PRS(MUrAzv7F9L1NE X-2;s9b!to$t}(hN5_*o`T+Xi*^vplBU_V9oEk5hhPJi)@bwla zbd>pWSJV&o1#tI;CYqKcSWi<_MXz;*V+nUNHd{-M%mw+|D*IL$SyNiD=P2jO?Vn}V z2Bc?(d&J1p>4ar|*r{}+S`92n!nR$>h)OKPj2SbqW5*6rmDt+aDk>Dmjv0-`D^^3T z)?n!HQT*v}J6$4~+qiBGY}R%>_Q>zBY}qo5prp09xEMu6MTm=wLwb5THg7agVw-|( zyAPhVqci{3wO~C0HU<7Sm;^4U#{|WWAlxu^Y*H;bah$-^+y}GvxGm;WO+L{WX%gjP z8hKD`PWHDY iGo$hK;wDVXkfX^Fqz@q2 zGTAIf?MLgQj)~liyI)xxBaY*DUKmC4FtaE-t2E+9bU2Ux{ekmZ#13`0+@3{=3;kX@ z8mEFSnXgZbHR6A6nt>&o+h`iq!|ircbT1e1g$GSePBuRJ_+zAH<#%t5;)U1Sh3d*G zs3^+caNRXnvt|uOj~ LB}<>uy6@?A@LLKMug@kq^i1nbtU5E8UPcIKEjL@b#7 z$+zLJcH@7G1%H(X{mlu;Ac*TJZWk+u7VS0)zwq?h@c2CX{Rj6{i74M= Z6|~98uL9X{P5{|Ee)o%$i`rTOZen-KxFhI3`j_OApyFVvHGs`bUsT9KfS5S7L96 zlpvva`!UO~dVAsd=~1#AcHZkRh{vANqZz$MuYkLw3sH1(c20&Zy!vARO%psNeY5pw z6CvKAjQ0h4o+3FH_e5y@$G9p>#WQIj%=={}GGdNO*! dk}kSBz hwrSOCuI0iUx;HICfQ4 z6^0BM0;kgntJR7zW5!_1mMs`Cpg)ET9fJ0%#n`gF2>)ltntMT`Xeq=|ot5+}hqs&% zqImyOUWCC;&I5ou^|D&dRgn8@)VQAl-3=3tvV;7Z4+;9P-_IHsMFa?Ohg~62DceYS zV`2(F>u_ q{D zmyxsIch6m@t1cJ6J6OCA7tB4MGWb?XTGQZgIOsdm_cs`5&}hU!VK`G$T|*C1iJXGb zsIEKbo2LEOTY&O6?@lYJ ztV(xR9 z82)e`?t3Q%k6ubxKk{fL=6a&0J;caxP&JdkagZGlC&S|XF0^)pR~~T}UZ$aW=H}h- z1|-O}^iST(@rVQUcJvrN(Fl^;d5C0F#NN8kjymGaGqUxBbv>dLw2elTo8;K3Bf}2K za&2$pT_<*xYeb^Ktr4E2FEo*ag2ygv!q29*Bi-y1oM~yJTI{^c#$$47z6A#Qe9jF7 zU#=N|)3tg^P?8c8(ACu?9G0UxTT4reaDKiK2?+_J4xK;qK+#@A=(yd52l@H=Vg~5n zzdsrp8U(KI*RP)#Bo<1r829tGiV4|Mi7GV+Rqo%Q)BML;hy)zh!WO||@g7AgqOrMK zSC6qE$om`Zt #c(3cDnoodE2pjrv)R&MC0iPGm&7Bokwq<)^ncTj@~F$$&s695UcFB zZEQt$vI@<_nYT7~VbM~G>fDf$w;ZGgJb6Tfs5{?W(ksynh3Ub6-dTgi50&FL7c>h7 zX5%3pW;~fCy0BKYE1;$XWM4J!R~i16-0~tr+f0$5yT*hei}duUH(mtiAV|d1QxX z=M*)^ yB0hW_oAdR#WLQ^1vxS#BKY)v=4!(YE?d zWM+3ex@r(k*J?<}%FQ2$VZ(+|lIa!6XpgZ2v(xiW{u}?SudgR(&p>2kq;P14C+DAk zzF=umQc}daQa;D3RjULgxpe7LVqy*vbPG@K69@RTgaXz_YP~ zf(Ai|OoA_}cCiO38wa(6wUj_MeSdc{H|2c1?R; s88*U9qKOkyjUVUT zQ;ZD4?`g(xkMjX}I ->yTI{gto0pA*h%WoiX~aNyL6MI`W@3-@pC$?Zf-;> z!==3zS4=J-2ecCR&c*GL?(IoV)9iMKz}y_2Ibt((Vc5YqoAEmTH$xbPBo!4EBElCG z6bQ%XzcZHSsJ>*$5?p=t)!4Ren~3Vo&CPIZ+DN>6J #yD(Pel3E~D7!x5cgy` bY7 z#RP;!jU*Z!T<3A#coXW2+C`I@4#MbDM@NZ_dc#5eF|;C+C3Bw65}P`4yNWS64$K^E zNB?9u3X(mD)cFxbQ&@{nc4TDpR~i!6gpjry0?FvQ G~-7Az1@gQGHY z>CB{<&1Mn7*%+3vQ?FRDLi~-Lo}E1@DG47F{Wx*rM4^gmh;LuLdbQ{d@XkB$;`+Pp zLq=9E;eR(J%)@YS-)>0dN(7ZL*tPW#hK|ZVuC$?-bAxt1L(i&`9@|4-pocT%O>4Q$Gap>P!aG_FC0H|u zsKv~9uSiJQM&~#Ya@fRFCyX1_pFv%Q&8@<~fdfTU-?(w3fFq5Kjlz+qOqn8>6vp4% zw{OSr;ll|->I8+zhv3yIA!R35tJT=Qf4`s=@3`|03?4QdBPUNobZi_}%>N43W`aBg z{c&L9eDohP8%9M4`wq5Y=*ZK!Y>GaHU*GT(E2BS^aRZi5DQs849IZrwznKDDf|&Vv zrT??nHHZ@JQr-ox*Znf<{kVB@yJ$e#vkddd8AIVs3Am1Z@~9~M^HV1#rb)4+B7mx9 zAMy(2A~JJuo;ETP>2WH2v8 gdgyvuWX7ksiE z2mb31*^$~L?gbP >sA@F$75gmSg`$YU3C^WPr*EJ4(zuOyM^_&%Jx6ABw| z<>=}z=*%%m@c9-Slq7C;SiE9+CqLW77>fR}G8Xk1pNrKg99B7$s+5GMBO*u`viooz zVXTE4I#3X=K&RCUtHUqWi8k3oNQqa8=8tW5nUH5s*O*RPyeG`4@UR%0=Yq8;AUbM6 zq782pdj{56Q;pYOcm`MBd^;|@@It)w(n~_RpFMlFpc`2*TDWkbNJLrQXV}uz6z-Ti zZQ3*eS4NH;DI$Avaxy;s>~s9_uP?(!SiGr;`ClbwjTwZ7rWWL<3`SeyZsZOekJ3)2 z*1VK=@RpLNxSKRloz}abL7?TFZFT=2W%4u$B7SESr5Qn*Nk?Wmv7;jFsJ!NY70@3t zrl))FlXRdid~fs8!{_NF^|N2J3`@&hcza 1NQhV9*f8u2PbG z-*-JVuk2 E zcK0ttV#$oj^y$+DFV2q4MPBEfcb T)qo@-@hM6#Dlo2~h-J{ywp@HmSto0YI*=kL>=%5WF$Hrjk!g^%o z8S&oYb{sDEqO{(F^Ri^9AXO?oNiAlmdW+)72Dm-OAN<=3TJQkz=aHIlEd#G6;`O1w zcrRJttlpL$;}y*-vvRs!gtsC;1MBioPGQg@f{u{eO1yao?z-zP!JbrBR*D4myYIde zj7w){r${`RhGb~-^2;xafxyfQ4~)9HdX$t`; _9$*yHiS36Z$++h$Bnmx;nD4<)BuMaEXqqCBAl z_x?F8Vj*;I_h*W6A}M5xe_M_Vp36E$EXauQpsB`&;lsKeTz2|w3){LahiLYYi#s`f zP(GF~T7au>xEWKYP8E*KRgCxCa}RF6{dOEUa6lxcEFv+aVa1dqJ7f53ue}y7rwheZ zbx;xOvSIZK |!?z-n5I?jUFain)jA96Yb0^&a&e@C`|4`J|&x(L{YO2!y6<&^-cVd3BJ?Cy*}mi zIvL^#gEv)rVIaO k9zyKsd|caN^+RoD zCk% uLX;1WT+)QgUd%W$nzIvVRMOzl8F+VQX?Rcj(=Z=J;q}|f;O-0(3{u1HbR#Z0 z5_PTR*h$pl`0?XK PBvrH-cJ1elLpvoyU^4b )n4qvo#+SHJQm4MbFnC3OcIRAP}$Hbrd? zjO<4u=27Aq3X+iM3-^U#-%%@rqF0Q+J0WEV+_U*|TA~^&xV?o+0zLUyO$Sle6fAiS zFy{ng2NC`44dU*n9B6fi_R(8*v&T&zo2!xMP&PX*MTf6fwc(2C(PGEBYPwjxg!@$! z==X-ND$W$HQ*SyhqL0dP;jVwAVp*^5TkuQ7y1yDrBvup|@x}cW$WQbLN9S|IYF&s3 zb)Scug-TAX4cxA6htOExg{$YzJ_aU5o5JP78@KPn+5 f9_mS*v0fA|EwWJlc5S@ zbrzLa_2i_K9iH*{Yp=bAn8YNaRqJu6XdlkM^a^@UwHO?&&2GH){- D83E67?VhN%c+TIfYV!Y1Pf88ClcJE} Lw&YVd6m; zdECMsk(mBuHvZ>d$#~++c&t6B6VSH0O(hoCcljm4UwDVR-qzXO=8mguy@7CXA#Vy- zO8LNDPnC)dA6)JLmMm;QM3fRw6J{TnsNnvB5Atx)#K);IaHxr Ml})$JkG01c+Ynpv799{CSz>H?#qM2>z5wEUhHZL zcnDW@pXU@dmtDVlFKX+Ym^pcj*y`kHI1f9n)n>;ZUVfX@%n?H6WVt>uF+mJemi<`_ z>T&Y2vNAN(Hz0}}m!taIZ@-P)euc=+FF^7By_kK$#qbd~?r^)H)$1T_qbb$t5FhF6 zY(g4|6*VoD2uOJlC?HeFd$l5#gylTSYYuHMMN~`#;?j?D#}b(ghNro(?#wiU{|1xF z{g4>9(mint|B2KdZQH?hFZ?^cm%Jm!v)4CZd;wRzR%6l52z 93fqCNQbSI6$H!<(nI(p1EJI}d )8q!_#{_wqLx73mY>(l{6=t5#~#vi_jr$jRu?Zm{i zl3%f}4M}&2qQTzA>M6&PsHpIwd#=5zBW-6uk^*A~8!=&8G!|~`z|cYiF*2ZlWrdtx zqmg3}JNll(P5#Ti{InW(+?$IFE>1#unFGnGT0FGALyR0&p!PKTiNg##H!0)CaOhmi zqEVi>3V}y$e1uONNDOb0?dl4@kC)pa$c#rfMwg!0i_u|sVcRFmaPX_ukPaJ;X_Ka+ z$H_S|v%_|D+OfZ^3SLTXW?XiS=)jemlOyU}m{H-xl9Np~hAzi9)~~9p5;f){CQcQV ziF ;iF) z; z z>uN=f`Pk_*P)<>OB4Kov{4c)hA<@{>zIqjM#*Bq+^9I4bM90L!x@{{$14m;xQI5@D ze+f0+jM!`yGWz!tj_51x-nJfYr4d6kKg|x&|5?V*HT+C@p}SlbIMS4y35ut$s~0%@ z>+KQ8OwhSRIcCRu@#NR>*k5z(EYIcLyeLRGx+IMKb#!Zk^t@wY)zA~9F`HF5Z@day zifp+5xeBxq#Ylm91H0~IjU=RM4H6Bc3`cBfcdH985$;C49L)}|fSh8{GPrM^^qA(B zT$jL3{>8TNMe~kz9L*V?@X9!*iMY||c|+_+W&& h?oEUy?zJ;yMouBpPY7M{BPN)3nIX(BQeX7ETUclGDSw z`kDzw49wDC?XFJD9vg05!Ckp{=U}rMsA~wXk5nqcALKob%;(h~Tg$ZJ0ZaeK+Z=L( zA{_U+<@@vqbVCP)A}AWs9A#RLzSiyN!oqL2W7gyeA}%zkiGQWnX>9Gde(oS>49DIG zw Pbl1*+s1hK^iUvc< dR>#Fp#!lL$ R$s?n2bld41?4uRq;$^!4lGf6ocI`^Z1#(xXA ~3X%MUl>Y3$Xor>C}|FMI!;Ww_wHSz
87DY7+v?4w$SD{?BGNj{9DBKFPr*@~%XYbBm#Y!Qb&_*4PUqD{3}c>u z;U!GJ_)0MdmdyVKW5!QJ(VksMOin>%c_|`kfbij@!NNB+;Acsh_fb-GPiT~z=(NzV zvRc=H-rJnDmoy?GBL>lS2bvEzkfZm;>D**|H@*I3Oq1mFet$ArHY7RqQ=jf K8$ zm}v=~qasa7_YvwoAAfCzzZPc@FTGtuN~=;(kIhX)pyV4dYi=Tz?6hmE+Z>lN9W3j# z`bZ$@L_)F#2{}5f+}??~6HPckS?jKSR_M7ewJ#(hy!e1~ppEb>AkK5d*~9 &3CCJkSZ3 z(=R## jiIPLbfg(AG!jCTm<`I;;jJ&5Fkn hngp?S`4Zf5&1Jabx+}18^>Vmf1oI3LghawK zpPiz+F_D~@u$j;vlA_?Tcj3R&LMJ&+;eFlD)JtrLhw?hf8Tw4mrzw;{!LPeL-8%`= z?;0tZ=f%pAK`h~&m&f7L#jVIFj3h2yi9I_z&{i*2(wU`n4ZC?;MW@3jGS>~On-L;~ zl(G6>=d~b`s7&5%fQxB7-tb3imBBN<=pN(6u8ehkf$pyn@(9osq@=i^wH-@VZKNCI zr(`h*M-w?t|GuO7hhJ^PwjKL0c=$++pFRukzyHCJHWwO=T0j)W>aV@|FN~Zv2RGeu zHwg)e*uG^WUVi$IXl-f6*a?$`O4>pAoN>IGa)Kz688sE<1anlBIFq#H=@K)7h8UcL z aQ;Qy22KJsF(tphD~+?Qu!>11wK)Ug3vk`JR(W>7izy}v2{XKn zqp+Y8T~-$&V`K0#N%%tya)KQ$80fR=>Y9*Mn0~@J+>|h`+TK7y!3+_VXIyXz3`yx? zLBy-Cy@rh&H(~pZ9r(uwA7jMSSt8ji-n$n&HgBY4H5W+~wI6-zIXZu>fGEkS>Dan) zoxtI(t<5yBy>tz{S(yrcua6>%N_5{HPIO~=St%~K^a|W~`&~2`l!&2oRX4W7=l0Sp zpn_B>7pxLrmoAp_g;tyBdGh~{v6F`}I&Y}u4l2&p&tcMq1FX{(5Ye1TLKEe*9tuRc zgvoi=?TmCS?!7h%^GO3*v%Lcqjbe%8KIUWg9&YeD GUO{~`B4N|2rFh_mL|pQVB2-j+ zczi8nPo0w+Ei iy(OtB<_L`YE578I>|c{UPJoi0B@cXoSL@(pkY zHH6i>&qGvHR9Y-rNCYZKjD^ALLc@kl@NPehro**}%RF)OtZ@4?oru5?tcV|0OFJ H#dsJv%Req@$m_wU~I?MEy&BwgU-NI zU=aC*1JKgkOoEdhL5j{@g!eDG>S{`I#zR3u6}P|O{w{Xn?X?j`u{BNefd(|xw_)*T zoALGMo3UjN)zt2TYi%0au;J$(} z#6>pNxsaZy5-q?q-2;;Qq_fHr7jc#kfjqs%ts{1&`#d3sP+ PdraxeU{W>i+}#-b&Q#exgA(Ifg(GPn#C6m@N#9Rgw`5`G>uY&dfA3y2x%63J+0 zb}s!*EpU5FV*^Txi!gb{9BiS;?ITFyaM*Eh-%iw*AIA38OVDL$fz;!~u8nKN$|Yu8 ze3Z SA^y+2cBE89xp7}hfZ@Yq=h5kPN&%> zb1Z6;2V-Mh>nYtD4k(YkU5c%TI`Lm?K?lv+KCuNo=V4VXXWzpny1SaOz0(hhae{nk za|cn|?#E* ^hakddUu7t33* zd7DK{1r_B^ZtVqiH#^kW?hQ9LGWl^f#`8%>mxgAJZs@{^OUQ*8vxXykXqI?IgItfQ ztUOp`YJ9Mw7|U0F2bXQXp!nE$mSN_^MSMBah!#pZdDS_eoLLuk#H`$05w+RL8|&+^ zcjpeIXXW59IXhQ577QGS(t`)!b2-r7+Jcy9Gab>0qo1KJCu}VZDBiPEK$l392?K@< zr$M6;%nm2I?d@&2>CU^c 8%|jPXZ5FI u` zqhzI%)Y5e;mQqxnMl)3{Mvj|+E}{j4L>tx;e}36zm%~6Xh?|r0!VDIh>gwu SjFw}JcR$Hq6hnd1Dg>a zqsI7=eHVA}Aly}Iqx>gK-L2Z)j&{2rD|fbG>5euSG;$HWVu>=#Ogy#4DQ99fO8(#e zXfZw_GC)Cr#~mQXh*aP4~^~-!7LO1eW<1ZMjb_J5`ws{WykK_h)YgKx;_di z>6v)t*{896(*_I~HCoIfydav-EtSc{fXkp+B7eXL 7Sa1@Nh z#K`3*Pu`0v?Re~+5;V{Z7wqOIDx2Jx^U!v&&*|8HQF!x#{y5jhgRGh&_>yH?@XhDn z!Qat RwFY;QwD# z+3G*=n PYxf)d9&d5>K=BOTjTeJAiZbNviixSY1B zp&qT2NN!rYN?`H4euV;$GvCe;xQSp4Ytz-7v{F)PGRL4`@CZc3r=ql`9_>~Krp%s8 z9BqO)XFLs>KmGM(q^4)lLk=sXTuPwTX(_q~#5ptb`_XlHghNM1YLQIeEs3Z_lTq`% zPR;}Nrdi2Y@z+rp+duM{vJoY{?};08u w_SEe0Uc&LD(+!Mrp|4{NlC;aqVq?#5apJQLc1yDCYOni->L|hNRVN6xFR~ zV|wlZWTt$IJIJ+!9i4-3kb+znMfTkM2qY%*UPo$-8f?U}ZJorRNl?}5=a9aG?> 7lA*sIvtTLE;g&k>;L^X|W!HGiT@MwQWiyWJIC1 zdm-(Rei;}%@@5q6m%|hhg^@#t)3vDK37-5WMHzLlw$$SF7oWpBZ@!9U^S{NarHin8 z`&MjPzlOw}WO95p_Uzb>fs~}OvRQWUASK&z`1Xs>aM9&g ufyXtvOZNX|*@gLmADU*CDF7+7kxmV}WrXQOhHbhzWf z)MI{U)XH)H6&aA8r9jtX+)-@bfeJkH(#JUOg1hj?2OmYSd?WHAx^S{>nb;>b*GjRu zP6|!P31#gz{N>RnF>}tHI9y(L;u``Ef`vBmJ~btUhZ@g*n@QgKhilcXl!&%co*1Q; zh>hc9l#@lL>ce|pb=%m123QIDJ@i%`TAaa^^s8;%#o!pJCI|S}{8qGC!_b7M+)~P5 zGc)xVnWjWFIeMf@idrJz9V7sxa2pHl*-?z#7{1vPfevdo-uE}+z^=W(2I8ATC8(}% zri+tOGW`9~K%+O~w!7{_|AB)^jSN7o(Grz7MXdj9ZEhkb--fv)?y!>|BBxFvN8Yl2 zE#_QwDLOhVLJehhAR;0X9qk>^Y7L~SnFyZvgy6#z;-#|( v@d0f5@ yT{<5#9~2{A;vJ;O*m)3gW;6(VM*8E&7h5Fa}7I)Z>-E ze|(}4 jVa5Bct2{Z8yPx^vqW;>HUF$F9-n5TBGJ z20_08gRo}#QXvo>rfAO 5-`@{n>Gn1Z$T;<=$g@g$RRiAr zU;z#lm!Peo7E-Sp(b^DFG`z#eajy^X+RJd4tiBKy&n7c7gQ5`5(2G2&=m2z;^&+P^ zirMLTxhh3Zpq55AG~|(+59>~Pt=yK-?*9Hj#-3Byz1_?HQm43{NFqq~HU((l0S6A* zv1DC4atXF%Wax?Y;_gxbL}?_N4jY6**K>7UEv+8dC{w*^S`^|<3M|>!f$e*(Vxj4V z4Q-+{GFmGUMO0h&+aVz@9hUjSd(lrWu;cm(mXrQ{{>}(!baGg`qq@Rgj##N29eX^; zh;E~3qdKORxi4eYHa#m_-0-^UD#4EM(O6$kLeW^l(K;O1zZV7M 9yXkt qk=x#wY*T7IRn-ZtTy*EwAD}Q;4@)tj`LFXem zBTG~`@~l%uNo{6y5V0mb&eWWQKj-~^D6W_kFCJ!mlnU=ZG!T;qn@+8aiUvw)MLnK; z=574uw=ZJL;zcmq4kO>-Lavzt!AYuuTU-(mbi_9~34GxJU{rS3gm+&|4iR_m_GTO^ ztv&ie68 SAdjOjS?-^V|(gAHl~^)W<4g$K=q(OT%} zLmc}Zh)_16tg;0%d_TjZ=w4Euo|y$5MPPRJ_O=#cQ$`6VZxNM=62ZH!Te%E#F1|!? z?jCm+lF6aVNIlIWMU)+zTg)YpQW}?-NQsn%xOfAq%MXcY7YxZT>%vO}o5C|hY;=rJ zW4)d(q{OIj^?3u&s;JXQG-AKBV+RVarWu|a@xABe#yzDJSs!`wpSbRt`%$#?Ynbha zkR25`rQ$ojGrVZWtUdW~;xZj>=*fq}7oG(e%5ZrAh(LG0^8SMr*s-sal2dw-zXkrT zW`TVj_5e0jnqlu h0cvtA3O>GlvjwRny6v<04`=xXVc(rbOCt@hz@`e;qdMQQ> zG$NJq$ty`5+C->hz%W+6U8o_7R~pWj;`#r2IG+#dUAV&!i9C%$e=|)v`*&NB+s}yG zE{VhXT{alIl_=)aH|`88c7xOU?gkqd<-_Z@)zWlBjzoOBny8_}uN0%MO@#+PNW$*p zmP#G$h((&KL3BVhCn^z>rh=u`2@?&jMw(@$%I}9RGO0hjm#9K63bz=-u$aS;qpGr8 zblVy+b{v*``xU0lI!_c~)l?Oc_%l?nE(}u+l7KX7+;~!7%h74GiJJ2+q93`IWx;m~ z@zgJ8!5pE0M(Ra-ODjrHBw9-IKq%g~8 8UFxPQnv04^Z@`>W5sL!rql z9QJgLcUk7BoI tUiP|ZQn}`*vXwfF4+#hUQHp=Au-_LU*9O`)vDSi#F&h5Sr3W@ z?{&2fC`g&!ecTRoyvOLz-%1s@U8f8RAxOi0JKs<4)5+NwpX*cvnfEz_FPXJc=!srb z>J`KghloFq5F5-*92$vaq8O`NePXN7R;K`2+>-8?UFja&T;+9a+G`V)i`-Z!Hd=** z1P$CaFXjx@VLS2Z3}JlakR0Wsw0;cB>Kmu?zBAk_k9RI$v4E4-!}WbP%3o8ghbc80 zhr4S#vZkAf#tNaiT88wb0AXgwNik|u8}Z#2UV|Ov*n5dm+_rJONM1*cn@CD&s%Sd2 z=<6>qWA24S8OD)#)G8J>7w_APjcZn6#(5W# pQvTB`q@tQ8aV#aywQ)$4s6IhgaI?%C#TUUU+pL{`%Nc7!Y9>j&~+T z>@6`D;UrYuz0nUlvgbJVg?DY{WV?=-nW|11YNW~d^>1!RE~%wyNimePYYFOAqs!Su zObbc=wQlTgPJp$KM3eio?5fm?Rc3YM7Z#_C$c~dIth^aB+nR0RX19?P3?t~U+I)y1 zN;VKUy5i_Ef-tXr(trhXHqV@#B%HeZGWUCRI zt%IZ5j@&da!3EDr@f$CK<%UD7dS;Vg!&QomyS-kIaQ+2feU4dkFA@tWd?ed*@>yPT zP}G@^8b3+M_}t(qlITPhhqwg>uU6uP7QDKswyIhPBMx7fZfLXEF=Y<-bc_{c2ki8# zmn=fSX~9|QXQyloSH51j9$$Slk7%^uS##{{AnfoIAVtp}Zn7^q=JUwGZRC_l#N;ck z#l63}1K)l1GOoFN`jI|4$#Ev=2+;8YdN t{KJUPKu@>3?FTVwcUf%1eLf LeN~BpSB&^?*6L zJw%v`_j<2vRpQ!LGDy`rxjMLTpILCPTxQAlE_JdAUG)KIHN>MUTtq?jjnkte^=PX* zEF!I*7!!sd`*-ielKEeY(ts >K7m*JDUb^s~7}a+ro=4xJ4a1 z5-XjS_Eu5oB@$Ok$UOv5nRQ|;{^8qi5xqDH{RR!j 2LR@4;i~k{}S3h7aEUBYt=Pbr>`#7Y3c?*hDwvho$KdqKGLf+S`GMlsIfI zJxP?N_x<_n1T5KWL=}mKx4oVwf?Z$6LJ{wQ)X?fh2?>h{1_|nI;oT9>&r@Pwu?+`H z>^O9=Q^Z1^O u zu_R~_CB=>+J0*%<3`*}3jognn7K3{4LCRgazHgxh-xOnVoY>}?E&YlF*a>?0{BqI! z$m!}Er)y}wO(AyS&TFs4C-44KtPs)?wK!|;#mFrvL|aQsxV;5M-~GFHi3n6hP-V)T z^ThkTq6*PRG-@RF(E#C9M*R97{+# u@v}k0+#bP`Y6z$o9 zi)IeTk8P}KE?vC?cl`V@6zDpL?!`GbjsZ+V+B{(|=KrrJ?R?^dVaQAm@BbPX6GajB z*a#f*R6-hR7hRZ{LEGCLcT(r&&)_| QutcR eip#-H6Opv#t( z-{PLj@-U>|vC@4gbnbD?{zH|x` V>gy8PjA!gr-GCK)zTD-aP^!6<3%UEo@k$sD 5~O5;*FP%>=az++1h(+%;+~H8hdw#qk6QajIwk!B8fKZ za-X~`m }P!#bCC+@IxAcin` z4Mq8(BgdeaM58Z0dWW370WBmrnJIG1 H1DA zIQuc+dMQB`z1$}lkRQ|d+O^#p4_7pBlKSqO@8j#m8^!NbMC0bA_uAdt*8s1h0y<*o zN-58(4<=)O?ddltI^G5nechiZ>L!9Mt0@u9GD*Y5X3V@0%hI2?R2HU&=IYSFYmXH& zV%*%Qu5&ZwW+$@toa=Zj@#?x_m+MbYA4D;!qJ?y#frBDpGAW^ $PvvZqITMJ>YFS}id(Ro-Nph8$uPN9ibeG#a5*`CzfyPWnErRt0@Tcr7MR zwY+mhYDT7r((L@a@M0lx=;NnNC(1Ou>oqHyyt==wmFuSE*t%&01`Hk|mf@vSg2}T0 z|DCHAWt4qKL>OVQh2c{K0oj4+lq}Qd#GCpKt>>CkXIcLV5`^Ma?juh4V_J`*pKXFz z`PE6VGb5#s?Q22-cx^>O%iz^wyh`+oo$)vq#`_(y2D7)sLC@cZ1oAm9JY#AxF ENwcyX7f+4G3KEmtM0;1YQLqkS3!UPK(s;b1A0!&-?k{(_LY(0;udP(d5J^G_ zpDZ(7iwl`%Z15wQ;7bdM0xP##p(hM|HL0KH!k%4_41-B0`dh2$3neG2M2lgk^W@Ma z!k|Z|<*1a*aD*H5Gn=w`{aWnWvKbd&c{MrfQ5Jv;xw!Ua5Q#=SFp`o}1pgkNm>^1d zoen#qi6>Vo)uMTzo(9~Z^5!G&pENQRtLA@!)cBvK$2r%M7<4L8#Pm~JEajAg`lX>D zC+);P`fA~`?-2g6YH1_nNjX@w=j`tZhl$&!wTTEmZ!tkNVsm1MKW`!^Yju+VSxc;# z=BWE7@=kTm0G@ok8XK#8jHUn5`+R+6hwX%OB=c6a;p4?EVl!Qa0xAM-jigM?o?wQa zSQ#aOIi{mNUeGMA>hdvGf!9yP2Bo@1UX)M=uZVsFlj<$XO35MH3MlLkUwVIj(iM^~scG#&E( z@}!uir-^w|PUqt_r=DI3SQk-n$r(9Bx&Abq;`ZH#VW*i%^Akk# L&D B&`3YSZn|BIdgx>dy^I9?dBn4sY zHm@v;M@+m<^l4`^=(+3F$*DAxQH2$Y7mCFfYz#+uW8InOUkEADgdClX1Z|G=MyDrN zQE~{m#Je+f$Svh~+r@%@{e_b=HOk6rq$ye~x{%4Ff~$9Vk3K*>-J`3f#-lJh2|tB# zu~s4}xbA;bV*oeNuQ%NM2!8kLKjVY {Y87`h;#-)=?*s$FyR>Uy{t{~)* zLiBggD_ZVuJpO!r>1hpiZzxo9&?YG;jKByIZ{!q4%IdmEwsoS6l(9BP0EQ&OY*7-T zk^Ff3t0w&R))ZVxQGMN^zN_~b$McRC8U=|+{cWO?F5^J%%)75fb4(fU!Fn~Cm6@>Z zJ0LdO !-b_p`;hgm9eI$mj}3Benpfj&1<$o=i4vGBRy+L(6C>L~uqfh6B^pZts% zFGf&syLfn~c)qNd@2}DHc}?8bVrrpH^bmUE(|SRCXyjqG 5J4^pCO^9jXi<$_wsT^%$t8}xN%T_ZrbWrAsVF&oNH}}(-d$poFAu4!I)hFmrM&Ox&K*19aQ}O{ZSl?? zw$`T8QbC!Y=6Y?eXQ}R#z Nu~iCYPKm{ULZhfP*}cO8w=o@y z`@rUrx&U6GNdD2iRpQ$J@$(wIdsh__B2PXwO(i8XpU90J#dd7iX+s9V7T%mIAx4SZ zTmuqQi3qU!Fk`$Kt=_Q6SX1T1l0}W8W(wg|bH`X;j&32xdv0@$KV+xKos^=%jkjeZ zJI6@Oixe|PMPmL-g@{NuVsA?T`DS8aYTcN>wjHrXKdu?mcYSe5qY4*2n?<8JoK5ja z{@EL;_~$CK=%07u>(y9VlPh@j4&p|opeN>Bb)<1+ZyPf-9o2_-!A4Ho+}tDt9UbBC z%-lTDm}vKotpcKO lKE8+9_#BLTzDJdf2d+7!+CvUZOiWz~c zB0KFi3>i5Rdk<9M-(g#}A3W`%Emk<0Z|BuG235Eem+8XOfj_+MCg)S5l0%~2rGkW> zGII>BzAC)i_dcS69Ud9}upsN0?S9yJ7FcsokMp0-LUZ>viu|ZAcqW@5j 8h+cI0LLw;FS^QkSz>4Sz>-$b7 zRX$VO8C-MEpG)zte^iJiN<3X8C2J8)(*u9_jguo$Psu?9krGNuLjUo3gShbPCtAd| zEq$3uA3#h*0B@~|#vyY2f~4>7F#FE>So9m8jkT+~` T*V$yat7CTTx0daGUkK@`a z=mVvl%+a~f{PM%Cf$~LroG78BkWU`wpCzcRb|R4+f6j;qly`(E3zlQ$<_;Vvr-Uxi zgX_lk9n}+Qy5gOs|L32{824CSxX~z1ZtWC39{w^52D1SN_IG!!a;~H~`NY<4+zORj zT9~6_F>(4Vfva;Vxg0fa68$blR3>tdml$GUxT}|gB6M0tmWbf&C`MzXSRZO70V$eZ z&vi1K+$xnyI#l8{o%A=8>EF{kQLR#tvz+*QE?+oR6_&?CvN7!N I7r?T N-Xg*G+cQLg2 z)dkIBAFMY&<6TEcxQvsbfnf5IvhdD{n|D|Vs)RqEebPn1eFy0|&wSQG12gnK{rbJW z&i#EFo11RTZT%A!ag@S|ej^&SRLn;_h1h7swKuu3rL+r!W2Go<4+?gklLg*v>B9{s zQSZ{*8myGmHmgM4co+SROErJ!5mmIP&@V@UuBtL*WEsfmAXc$JoquGH95x$$;mn~k zh$igc9pR1+p}LxC$`O<# %o%w-{QQBFAMi 0iKxj*K*#tDbQQ*6WQ-KA4Gw|?P?`ZLl2>{0#*V=#s{4Kww6ax2up&H4 zx&G-uL*HLwVu1sXU)Bhl!-xBxE<=PQEKIJh31C9H0>8XD1z)VPU_b_` L`NMBkG#RHoZv~gLqG-`V$Y%w5|dIy63griNA~EL zI8-#8Iv0E{Eiu6?@{Mz090GDop%bDU-@3{Uir@`$RL{tTZCWOb$&pZL>AR~XczIw5 zv8r$z=yj``Q7-L|Wko-{Xghcc&z+c-w$dPSV^Ew_tbrJkrozl|QJ6I? z7O9jowzT`OVNJ8}g;Y|gTib=0v4D<~VxsSLbb9xpS_1eBz94+4mqAC7u3x+ym(L)| zj=phQP$m)-VrftqOFT0vQ-AxYUcC3=OPWPd&bcrPMS)+<%R?N^yQ`OVH`I5Nqq~d4 z*GU=9R+?EFD0274tGu3*TUZ!LzGuN`#o`4>NlPaOPCn-R{EmkD8qx`d2(ii9*(pi@ zc=wB@`UaROs&jKuZja$tm fdlKT&^0VRa z qOxovpbEX zgkDMhmYqk>uq%M8W-}&tibl9x_iiKh#n~0)2lBlgS%0X(`TV_gzQEx_HbfYeVxs`w z2#L3V$xcw>%}2BF?8A9jRvADC(QY{=iD(B_OjtcD&V$>goPAYekL|D3U}a;z_=07n zc2Q#?SQuZkfIo()IFuYbC|b>h*V%?dl;#GctxZjIG(e} 88Lb z6|^(6G+cU;nQcI9yawr%8!{o1MgLt{VaL4BYtd-+v!j)L-}!&24(_1C7n W+=VH->I z#GR_pR8&rhC0&Dt?u54-5>*Da?zdv(#HnJ@g@zoJ>BOe`dg1I0ulV; $x?`&X(i!^f5)%q&RtyZ!pXR;xf+?tXRr#n$Ibww zM;i6mxN#S>o_3szdNJnM8K-l1gG4=J^>Fhdn>_$uju~1K;8I$ CJ%0Y_%NbKbKwqg%-vT~M1TD544Q>Cl#t8B zVIoR#Evce9x~5N;TTtHWf16J8)%ShI4|$-EW~$q^RQtC6^0`X9|3Q@~^kQR0OdOP; zjvk=Llp#hj&Gd_wVjf|v)!RG8D(|~yomx+DVdR8Yeot{2?~-x%+i8d!T!4*hTVSVY z4nfiete- D zRSlwL$Mf&hibBDG6wT|2X<1$Ahs({|D+WaAc#kE;D6r#@6My@p0c#JrtLRrR{Ltr| zN~q;2VP 9`=4q!~0 z91q`+fVEpY(9qyMLNx5%WkEIh;Fp_hhw0KTre9V6(C3_L3BQM?uG=mhtW6j ccRV@wuyi->CgvnkOA**F zL7Lf1w4&mqSe`p|y?=K#)QTW}@#ivBR=O}TRgN7E0ZbjJ!_*Oxxar0;Y(M0p*+wGv zaOK4vdv;pz)mlri)#~SIdIwIyP8B>$D4 Ip30BU(kXF=C;7)k(`ugXKDGt zFA6beOd{reTq#-}MEN!$qW;%7yla^#&YCc7CJYpvdFz)pN+!8M5%)6Z7IeJf7Pp&I zYqdvqUiNuBqL!UIAn+n g+z3 zgQA_pp!A4v>xvz1a;!DQBX(pu+{CoZjFsTqF+pT%L*kyUUfu|Kd 39$+x?-uRr5JUo(0aVv}7$(%>6zz0ElGEl2Tw@OUB^i|QvTN+z)`plU1+r7s zm^C^I*If{YW&2$ys_Y_Oodlt3FL* 9Nz5&tRMga4#R6yU4v<&SPt2_>9x;5$ zrvK(HT)YD`C#L+V>sGEne!oKTnykEh(aVuL1HAjjt9brTk73A&kwP)uuxbS^KY!Gj zCxvGa(KRHC78Yb;)AoZ Fe6yetOZPYr&}se#;FPkK z)1gC_H#tJvR`0dLL;NMzzFc-?8b*wYMrx`aaj|M>2(#ZZH3|xY0xixE1`v#S;k{bS zU)L_S&ilg^4JUm-xg;|26!)7ClhE8L$Iy&ZYvr(|OozKaEI{5sGnOrGB>FJ`E$T4d z^gar6qeR=eq|{WA?9^3N6NIUyBsG`Vmk@Sr*-Y$FcxPxoMQ|0tk^n&zR!kYkt2JtT z{=vIg`pxI~^wUp?Vl`mt!f!EX$Vj-oXV)_OOsp_36_;Oqp(wOEUE`|0Ru?Zr19mq_ z;EFfGF*_SUbvQA dUy{j-r@o2Drcxb- zWVlcITJGWU`zsoSNc7N46{2)xSYkMux4D9dA(W7ssTX1ptDgk~fe%-^#A>%)+uQNQ z3Q+>GhK^G%JUSgm=S88{t}6GH-uuVH`0$fjl$Y6QHkF9Nd$l@zp=J{|u8%`IiAj0U zGLX;XKEhv3q-I?`){5&Vbo9AX1~;V{{YWmJUl@x4zbfb{w>%}=Qm(^YpA1A&ejFBj zT_>s?l_3lIc^^fze=V5K;bJdtYQ$S7a`L>JHwGYqi1!{9l|aw`1qJ;FAV>*mc3uHt z_&O|__YoF;@hRSW?>$^{$t7qb$ivBQd_oec>wdTk?7241EoMv}3Xkmcma1`y?(QZj ziWLT|j!i<{1r(ug7=$2&c9!$sA5OGIH@ni==@Xk>wQJJFJ+-(_%|*<5zo=t+^s@v^ z{zDE9H>mooV`Ari=DG%Qb|1c3)rPm`H3|ikJ8*q{(1((CO1fLR#Oqv+aIog*<6~ix z1fwl`Y$&mWINN;|r(vg?O?8lqE~>Evk`B~(3Uq3zB!d!BUKqiNf{N~xA%b}B8Yec= z1@VpJl_C4 XK<@Ovra)dxaKNt;fQW3i|I3XSnc!r^1Tn(_0)_*`h>IQzWjQ zEk)^`2EhoKqognf_MoM<7%o{Z(sK$$2d `piS_0bXp=zA!- zpPs1fInJvXrjW#ea+36`Bsy3pW}$gPCc1Ko6o^tI!Aisdan)arAs#z6+#j=*9Bk8u zR-)PZ;gN50(dIbyK6foP1!~&EWfe?1@M4 cJ)=J6?OQ2J^SsT@G*PZuGH=== eL5;@7a?ze_@K6(p4+oGdb>pjz2Z*Yw!LU(dg>!uI z;d|IfPTfYr(8Irbh;ofYJo)6Gux#0K#KnbIyz@4SytQK#VfEbHTvFwt#onc@mVZyV z?()?;;B#0JMY%{52|oT{xKx^pvp7FyhiA&Lg7O|yN(N*xU9e9`f?e00#L|tG(|gVz zq{cG?gCx#`e|_*kCwA?&VT)%J-dGuXPF2qyn?}OWA1-ece(}%?6{sg?zq~+!YHJV) z1XpH_G+@TuBo=_^{`$~S=OB!(hsENd8Mqy5id={3H0 !hJ>`woeV=itc)H-eqJFapX36EM6%Zk1w~KMOnl5m_`}ITesH;T+Ykm z?tQWh$p)Z+ayTwUAZ`OQCPpJRUX7S&75e8I;3n25E6;$oHWJBdooKT1Dq|eL8QK|d z{zyLX#HteSyv*I!h>NaBK~BDr*oY_*W!QUp*I*AN5073GhZjGp$ALC-_7s`u1M@$B zEX60!_QNYT)uEEG__?f`=rK-aUt1B4?}+w#=gw-BmR5-_PPufQ5%OAGG@uRv1LfDA z9Kn wb?3G;mCfBk=lpxW?P@W|Cy*d z6dOwrwBT(Kt2)EGkSJ;DjMau)$OV(tctziVE@gOO%Xu{M{AGj!g~>dDhXez~qDxbg z7n>YI@xqr$$3PXH%(!>C!*ljEN{q|@NDRwG!#i=f%8iTeDI%gEh)JY+*4u+P)E0z9 zA;*CJMrcWCR4T)t$<>rm QHM9G|;bi;SB9;nwyoAA9oeid8U`|?~r;r zy)?aR#UAXE?l|30@22FB93)5Et*SQ=)pB0gDLr5Tsq5)Bg zyu3VAS69<(B2Zpl4x`a1qB+Bs9Xoai+%A 1w>1jfjPP-HLJop0C&L&ZO z#Y@|GnVU`(u2tb~x1yxgDM4G86b*B8;3I-y2oV8)8^SP!W}`@{NKji_yRd#u3v5yo zUfDhjA8(F6#>shw*i6FGw@$TS-2nr GB{(oEmMkE?%m02S44Ov{v$ zG8@F |Jb1VqqXws*KKwdO zM_;^RD;9qKjp$+C*%fZ>oM;Gdm*^sxCh4YYP4xT9kQ!-oQt`X&ApY7ffI(5=rlDM~ zvT1z_sz}5;80d%Je32(gpL^qRgDQY$u5G~XsxW*jAZ=r3<(aqay?k^Bsh ~n?c>>*xM z@hBZ QQxSPIn zEMaq(KipxqnSw-onh|fDmw=%(a~TNA=v7LQY;uR&CSo|+6)AXTV}_u2`?6?508XNI z8Sb&gMFd23q4NwN%(Q-Xyz#SIf>Z%K`%WF+`?g6WpXm`&tgH56#t;MM&W=WIff22a z5K4)_kUKocBWP)9^WcjGO;~fl-Auv#4LaRl={Tz@y0dlk9y`$G4_P$MF4K8q6X2$i zX(uRCU*|?nt`?K%OYy3tqH;H7+cgNacrY$SjwR(jT=76L7QQ|J aPydY96s17RJZJ0a`=W$ zZ2tBonu#Kzw`@baI))gQR2sZt@q*zC_k2;S)yN$<5Y;V}NQ`s99C_y3E`FbF+){+9 ziYiP<0cva^QA5rfYB4 z!hj9N~VL5vnryaaKI_gg6m_rR`B!iRg) zh~+Y#(2}CJHChQPr*`3q%bKvZSWoJ&3~e?UYW<4Sf-t;Q;+Ma!B (QH(ZG#|jIEDcKQy0Ev_hbifD>~HYnvt?~Kf3g|F zb3$T)>}pC*t*5bfJd02lj|jJ}{$RZs88JT0-);O}E|{a#hQoTiyFMOjqaOV-0_bS8 zlI$;|h;5+RD1hzj_CaFb54~pvQsY^I4xy~P8c8X+qVUV%aG;=YFqW<>B1N$sM!lK_ zfZ~Ut%xOTE=U(~