From 4b0e9fa68570c0793e7ac51db54d21b5aaba10ff Mon Sep 17 00:00:00 2001 From: Jarrett B Date: Sat, 1 Dec 2018 11:03:42 -0800 Subject: [PATCH] Initial License update distributed json README and other things Add image Woops README License Update mute settings and README credits --- README.md | 49 +++++- __resource.lua | 34 ++++ cl_jailnjury.lua | 212 +++++++++++++++++++++++++ jailed.json | 1 + json.lua | 400 +++++++++++++++++++++++++++++++++++++++++++++++ sh_config.lua | 131 ++++++++++++++++ sv_jailnjury.lua | 240 ++++++++++++++++++++++++++++ 7 files changed, 1064 insertions(+), 3 deletions(-) create mode 100644 __resource.lua create mode 100644 cl_jailnjury.lua create mode 100644 jailed.json create mode 100644 json.lua create mode 100644 sh_config.lua create mode 100644 sv_jailnjury.lua diff --git a/README.md b/README.md index c2bd7a2..aabe3d3 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,57 @@ A Jail and Justice System that gives power back to the players. Copyright (C) 2018 Jarrett Boice This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by +it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +GNU Affero General Public License for more details. -You should have received a copy of the GNU General Public License +You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +--- + +## Description +FiveM-JailNJury or Jail N' Jury is a advanced jail, and justice system / resource for FiveM servers. Jail N' Jury offer's advanced jail features with a democratic justice system. If a arrestee feels that they have been falsely jailed, they may request a trial by a jury of their peers, the people of your server's state. Fear of arrestees escaping? Have no worry, this is literally impossible. People disconnecting to avoid jail? No problem, the justice system has your back and will jail them upon return! + +## Rundown + +### Arrest System +Law Enforcement Officers are able to jail arrestees via the jail command. The arrest is then logged via json and placed into the jailed players file. If the arrestee disconnects, their time is paused at whatever time was remaining when they left the server. When the player reconnects, they are re-jailed for the same time, and charges as prior. The prison is able to detect if a arrestee is attempting to escape and will teleport them back to the jail, it is literally impossible to escape the jail. + +### Justice System +An arrestee may only request a court case if their time is longer than 10 minutes. Once the court case has been granted, their jail time is paused and an advertisement is given to all players regarding an upcoming court case. Players are given 5 minutes to make their way to the courthouse for a chance at becoming a juror. A minimum of 3 jurors must be present for the court case to proceed. There is a maximum of 5 jurors. Jurors are selected randomly from players at the courthouse and cannot be any police personnel. Selected jurors are then briefed on the suspect's charges. The arrestee and jurors are then teleported to the court room. The arrestee is given 1 minute to explain their charges to the jurors, at this time the jurors are muted and cannot speak or type. After the 1 minute has passed, the arrestee is teleported back to the prison and awaits the verdict. The jurors are then unmuted and are able to type and must deliberate for 1 minute. After the 1 minute has passed, the jurors are then muted again and asked to cast their verdict, they are given 30 seconds. After this, jurors are relieved from their duty and teleported back to the courthouse entrance. The verdict is then calculated. For a arrestee to be found not guilty, they must have a 2/3 or 3/5 vote. The verdict is then displayed to the public and the arrestee is either re-jailed for their remaining time or released depending on the outcome of the trial. + +## Commands +| Command | Arguments | Explanation +| --- | --- | --- +| `/jail` | `PlayerID Time "Charges"` | This command is used by police to jail arrestees. The `PlayerID` is the ID of the player, the `Time` is the time in minutes, the `"Charges"` are the charges. The charges must be surrounded by quotes in order to capture all of the seperated words. | +| `/unjail` | `PlayerID` | This command is used by police to unjail an arrestee. The `PlayerID` is the ID of the player. | +| `/requesttrial` | None | This command is used by arrestees to request a court trial. There are no arguments for this command. | +| `/jurorverdict` | `yes` or `no` | This command is by jurors to send their verdict regarding the trial. `yes` is interpreted as guilty. `no` is interpreted as not guilty. | + +## Configuration +The configuration file is located in `sh_config.lua` + +| Configuration Key | Explanation +| --- | --- +| `JailConfig.jailFile` | A string of the filepath for the json file in which the jailed players are stored. +| `JailConfig.stateName` | This is a string that is used in many factors of the system for the state. Ex. Superior Court of San Andreas. | +| `JailConfig.policePeds` | This is an array that the system checks against to see if a player is a police officer via checking the ped model. | +| `JailConfig.courtStartTime` | The system interprets this integer as the amount of minutes to give potential jurors to show up to the courthouse location. | +| `JailConfig.courtEntraceLocation` | This is an array that the system interprets as a vector for the courthouse front entrance. This is used to display the blip as well as the area in which the system will select jurors. | +| `JailConfig.defendantLocation` | This is an array that the system interprets as a vector for the position at which the defendant or arrestee will be during the court case. | +| `JailConfig.jurorLocations` | This is an array of vector arrays for each of the juror locations inside the courthouse. +| `JailConfig.prisonLocation` | This is an array that the system interprets as a vector for the prison location where arrestees are sent to jail. +| `JailConfig.prisonEntraceLocation` | This an array that the system interprets as a vector for the entrance to the prison. This is where arrestees are sent after they are released. + +## Credits +- [@rxi](https://github.com/rxi) - [json.lua](https://github.com/rxi/json.lua) - Rxi supplies the lightweight json lua encoding and decoding library this resource uses to save jailed players. +- [Gaming-Asylum](http://www.gaming-asylum.com/forums/index.php) - [Gaming-Asylum-Wiki](http://gaming-asylumwiki.com/wiki/Prison_Guide) - Gaming Asylum Servers have a similar jail and justice system on their Arma 3 servers. I drew inspiration from this server because their system is very effective and fun. + +## Pictures +![Map Image of Courthouse](https://i.imgur.com/SHSOipy.jpg) diff --git a/__resource.lua b/__resource.lua new file mode 100644 index 0000000..76eea65 --- /dev/null +++ b/__resource.lua @@ -0,0 +1,34 @@ +--[[ +FiveM-JailNJury +A Jail and Justice System that gives power back to the players. +Copyright (C) 2018 Jarrett Boice + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +]] + +resource_manifest_version "679-996150c95a1d251a5c0c7841ab2f0276878334f7" +description "Jail n' Jury" +author "Slavko Avsenik" +version "1.0.0" + +client_scripts { + "sh_config.lua", + "cl_jailnjury.lua" +} + +server_scripts { + "json.lua", + "sh_config.lua", + "sv_jailnjury.lua" +} diff --git a/cl_jailnjury.lua b/cl_jailnjury.lua new file mode 100644 index 0000000..eba6716 --- /dev/null +++ b/cl_jailnjury.lua @@ -0,0 +1,212 @@ +--[[ +FiveM-JailNJury +A Jail and Justice System that gives power back to the players. +Copyright (C) 2018 Jarrett Boice + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +]] + +local isJailedG = false +local jailTimeG = 0 + +local courtCase = false +local muted = false +local notifyCourtHouseIn = false +local notifyCourtHouseOut = false + +RegisterCommand("jail", function(source, args, rawCommand) + local _source = source + local targetPedId = args[1] + local jailTime = tonumber(args[2]) or 0 + local jailCharges = args[3] + if isPolice(GetEntityModel(GetPlayerPed())) then + if targetPedId == nil then + return TriggerEvent("chatMessage", "^1You must enter the suspect's ID number.") + elseif jailTime <= 0 then + return TriggerEvent("chatMessage", "^1Invalid Jail Time, enter an amount greater than 0.") + elseif jailCharges == nil then + return TriggerEvent("chatMessage", "^1You must enter the suspect's charges.") + else + TriggerServerEvent("jnj:sendToJail", targetPedId, jailTime, jailCharges) + end + else + TriggerEvent("chatMessage", "^1You are not authorized to use this command. Consider joining a Police Department.") + end +end) + +RegisterCommand("unjail", function(source, args, rawCommand) + local _source = source + local targetPedId = args[1] + if isPolice(GetEntityModel(GetPlayerPed())) then + if targetPedId == nil then + return TriggerEvent("chatMessage", "^1You must enter the prisoner's ID number.") + else + TriggerServerEvent("jnj:releaseFromJail", targetPedId) + end + else + TriggerEvent("chatMessage", "^1You are not authorized to use this command. Consider joining a Police Department.") + end +end) + +RegisterNetEvent("jnj:sendToJail") +AddEventHandler("jnj:sendToJail", function(jailArray) + local targetPed = PlayerPedId() + local jailTime = jailArray[1] + RemoveAllPedWeapons(targetPed, true) + SetEntityInvincible(GetPlayerPed(targetPed), true) + SetEntityCoords(targetPed, JailConfig.prisonLocation.x, JailConfig.prisonLocation.y, JailConfig.prisonLocation.z, 0.0, 0.0, 0.0, false) + isJailedG = true + jailTimeG = jailTime +end) + +RegisterNetEvent("jnj:releaseFromJail") +AddEventHandler("jnj:releaseFromJail", function() + local targetPed = PlayerPedId() + jailTimeG = 0 + isJailedG = false + SetEntityInvincible(GetPlayerPed(targetPed), false) + SetEntityCoords(targetPed, JailConfig.prisonEntraceLocation.x, JailConfig.prisonEntraceLocation.y, JailConfig.prisonEntraceLocation.z, 0.0, 0.0, 0.0, false) +end) + +RegisterNetEvent("jnj:teleportToCourt") +AddEventHandler("jnj:teleportToCourt", function(pmuted, vector) + local targetPed = PlayerPedId() + RemoveAllPedWeapons(targetPed, true) + SetEntityCoords(targetPed, vector.x, vector.y, vector.z, 0.0, 0.0, 0.0, false) + SetEntityHeading(targetPed, vector.h) + FreezeEntityPosition(targetPed, true) + if not pmuted then + DisableControlAction(0, 245, false) + DisableControlAction(0, 249, false) + else + muted = true + end +end) + +RegisterNetEvent("jnj:teleportAwayCourt") +AddEventHandler("jnj:teleportAwayCourt", function(vector) + local targetPed = PlayerPedId() + SetEntityCoords(targetPed, vector.x, vector.y, vector.z, 0.0, 0.0, 0.0, false) + FreezeEntityPosition(targetPed, false) + muted = false + DisableControlAction(0, 245, false) + DisableControlAction(0, 249, false) +end) + +RegisterNetEvent("jnj:courtCaseStatusAll") +AddEventHandler("jnj:courtCaseStatusAll", function(boolean) + courtCase = boolean +end) + +RegisterNetEvent("jnj:courtCaseStatus") +AddEventHandler("jnj:courtCaseStatus", function(boolean) + isJailedG = not boolean +end) + + Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if muted then + DisableControlAction(0, 245, true) + DisableControlAction(0, 249, true) + end + end + end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if isJailedG then + local playerPed = PlayerPedId() + if GetDistanceBetweenCoords(GetEntityCoords(playerPed), JailConfig.prisonLocation.x, JailConfig.prisonLocation.y, JailConfig.prisonLocation.z) > 50 then + SetEntityCoords(playerPed, JailConfig.prisonLocation.x, JailConfig.prisonLocation.y, JailConfig.prisonLocation.z, 0.0, 0.0, 0.0, false) + TriggerEvent("chatMessage", "^1Do not attempt to escape.") + end + Citizen.Wait(1000) + end + end +end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if courtCase and not isPolice(GetEntityModel(GetPlayerPed())) then + local playerPed = PlayerPedId() + if GetDistanceBetweenCoords(GetEntityCoords(playerPed), JailConfig.courtEntraceLocation.x, JailConfig.courtEntraceLocation.y, JailConfig.courtEntraceLocation.z) < 10 then + if not notifyCourtHouseIn then + TriggerServerEvent("jnj:requestJuror", true) + notifyCourtHouseIn = true + notifyCourtHouseOut = false + end + else + if not notifyCourtHouseOut then + TriggerServerEvent("jnj:requestJuror", false) + notifyCourtHouseIn = false + notifyCourtHouseOut = true + end + end + Citizen.Wait(1000) + end + end +end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if isJailedG then + if jailTimeG == 0 then + isJailedG = false + TriggerServerEvent("jnj:releaseFromJail", GetPlayerServerId(PlayerId())) + end + Citizen.Wait(1000 * 60) + jailTimeG = jailTimeG - 1 + TriggerServerEvent("jnj:updateJailTime", jailTimeG) + end + end +end) + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + if jailTimeG > 0 then + SetTextFont(0) + SetTextProportional(1) + SetTextScale(0.0, 0.3) + SetTextColour(128, 128, 128, 255) + SetTextDropshadow(0, 0, 0, 0, 255) + SetTextEdge(1, 0, 0, 0, 255) + SetTextDropShadow() + SetTextOutline() + SetTextEntry("STRING") + if isJailedG then + AddTextComponentString("Jail Time Remaining: " .. tostring(jailTimeG) .. " Minutes") + else + AddTextComponentString("Jail Time Remaining: " .. tostring(jailTimeG) .. " Minutes - PAUSED") + end + DrawText(0.5, 0.005) + end + local courthouseBlip = AddBlipForCoord(JailConfig.courtEntraceLocation.x, JailConfig.courtEntraceLocation.y, JailConfig.courtEntraceLocation.z) + SetBlipSprite(courthouseBlip, 419) + SetBlipDisplay(courthouseBlip, 4) + SetBlipScale(courthouseBlip, 1.0) + SetBlipAsShortRange(courthouseBlip, true) + BeginTextCommandSetBlipName("STRING") + AddTextComponentString("Superior Court of " .. JailConfig.stateName) + EndTextCommandSetBlipName(courthouseBlip) + end +end) + +AddEventHandler("playerSpawned", function(spawnInfo) + TriggerServerEvent("jnj:checkJailed") +end) diff --git a/jailed.json b/jailed.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/jailed.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/json.lua b/json.lua new file mode 100644 index 0000000..a3d7530 --- /dev/null +++ b/json.lua @@ -0,0 +1,400 @@ +-- +-- json.lua +-- +-- Copyright (c) 2018 rxi +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy of +-- this software and associated documentation files (the "Software"), to deal in +-- the Software without restriction, including without limitation the rights to +-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +-- of the Software, and to permit persons to whom the Software is furnished to do +-- so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in all +-- copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. +-- + +local json = { _version = "0.1.1" } + +------------------------------------------------------------------------------- +-- Encode +------------------------------------------------------------------------------- + +local encode + +local escape_char_map = { + [ "\\" ] = "\\\\", + [ "\"" ] = "\\\"", + [ "\b" ] = "\\b", + [ "\f" ] = "\\f", + [ "\n" ] = "\\n", + [ "\r" ] = "\\r", + [ "\t" ] = "\\t", +} + +local escape_char_map_inv = { [ "\\/" ] = "/" } +for k, v in pairs(escape_char_map) do + escape_char_map_inv[v] = k +end + + +local function escape_char(c) + return escape_char_map[c] or string.format("\\u%04x", c:byte()) +end + + +local function encode_nil(val) + return "null" +end + + +local function encode_table(val, stack) + local res = {} + stack = stack or {} + + -- Circular reference? + if stack[val] then error("circular reference") end + + stack[val] = true + + if val[1] ~= nil or next(val) == nil then + -- Treat as array -- check keys are valid and it is not sparse + local n = 0 + for k in pairs(val) do + if type(k) ~= "number" then + error("invalid table: mixed or invalid key types") + end + n = n + 1 + end + if n ~= #val then + error("invalid table: sparse array") + end + -- Encode + for i, v in ipairs(val) do + table.insert(res, encode(v, stack)) + end + stack[val] = nil + return "[" .. table.concat(res, ",") .. "]" + + else + -- Treat as an object + for k, v in pairs(val) do + if type(k) ~= "string" then + error("invalid table: mixed or invalid key types") + end + table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) + end + stack[val] = nil + return "{" .. table.concat(res, ",") .. "}" + end +end + + +local function encode_string(val) + return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' +end + + +local function encode_number(val) + -- Check for NaN, -inf and inf + if val ~= val or val <= -math.huge or val >= math.huge then + error("unexpected number value '" .. tostring(val) .. "'") + end + return string.format("%.14g", val) +end + + +local type_func_map = { + [ "nil" ] = encode_nil, + [ "table" ] = encode_table, + [ "string" ] = encode_string, + [ "number" ] = encode_number, + [ "boolean" ] = tostring, +} + + +encode = function(val, stack) + local t = type(val) + local f = type_func_map[t] + if f then + return f(val, stack) + end + error("unexpected type '" .. t .. "'") +end + + +function json.encode(val) + return ( encode(val) ) +end + + +------------------------------------------------------------------------------- +-- Decode +------------------------------------------------------------------------------- + +local parse + +local function create_set(...) + local res = {} + for i = 1, select("#", ...) do + res[ select(i, ...) ] = true + end + return res +end + +local space_chars = create_set(" ", "\t", "\r", "\n") +local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") +local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") +local literals = create_set("true", "false", "null") + +local literal_map = { + [ "true" ] = true, + [ "false" ] = false, + [ "null" ] = nil, +} + + +local function next_char(str, idx, set, negate) + for i = idx, #str do + if set[str:sub(i, i)] ~= negate then + return i + end + end + return #str + 1 +end + + +local function decode_error(str, idx, msg) + local line_count = 1 + local col_count = 1 + for i = 1, idx - 1 do + col_count = col_count + 1 + if str:sub(i, i) == "\n" then + line_count = line_count + 1 + col_count = 1 + end + end + error( string.format("%s at line %d col %d", msg, line_count, col_count) ) +end + + +local function codepoint_to_utf8(n) + -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa + local f = math.floor + if n <= 0x7f then + return string.char(n) + elseif n <= 0x7ff then + return string.char(f(n / 64) + 192, n % 64 + 128) + elseif n <= 0xffff then + return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) + elseif n <= 0x10ffff then + return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, + f(n % 4096 / 64) + 128, n % 64 + 128) + end + error( string.format("invalid unicode codepoint '%x'", n) ) +end + + +local function parse_unicode_escape(s) + local n1 = tonumber( s:sub(3, 6), 16 ) + local n2 = tonumber( s:sub(9, 12), 16 ) + -- Surrogate pair? + if n2 then + return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) + else + return codepoint_to_utf8(n1) + end +end + + +local function parse_string(str, i) + local has_unicode_escape = false + local has_surrogate_escape = false + local has_escape = false + local last + for j = i + 1, #str do + local x = str:byte(j) + + if x < 32 then + decode_error(str, j, "control character in string") + end + + if last == 92 then -- "\\" (escape char) + if x == 117 then -- "u" (unicode escape sequence) + local hex = str:sub(j + 1, j + 5) + if not hex:find("%x%x%x%x") then + decode_error(str, j, "invalid unicode escape in string") + end + if hex:find("^[dD][89aAbB]") then + has_surrogate_escape = true + else + has_unicode_escape = true + end + else + local c = string.char(x) + if not escape_chars[c] then + decode_error(str, j, "invalid escape char '" .. c .. "' in string") + end + has_escape = true + end + last = nil + + elseif x == 34 then -- '"' (end of string) + local s = str:sub(i + 1, j - 1) + if has_surrogate_escape then + s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape) + end + if has_unicode_escape then + s = s:gsub("\\u....", parse_unicode_escape) + end + if has_escape then + s = s:gsub("\\.", escape_char_map_inv) + end + return s, j + 1 + + else + last = x + end + end + decode_error(str, i, "expected closing quote for string") +end + + +local function parse_number(str, i) + local x = next_char(str, i, delim_chars) + local s = str:sub(i, x - 1) + local n = tonumber(s) + if not n then + decode_error(str, i, "invalid number '" .. s .. "'") + end + return n, x +end + + +local function parse_literal(str, i) + local x = next_char(str, i, delim_chars) + local word = str:sub(i, x - 1) + if not literals[word] then + decode_error(str, i, "invalid literal '" .. word .. "'") + end + return literal_map[word], x +end + + +local function parse_array(str, i) + local res = {} + local n = 1 + i = i + 1 + while 1 do + local x + i = next_char(str, i, space_chars, true) + -- Empty / end of array? + if str:sub(i, i) == "]" then + i = i + 1 + break + end + -- Read token + x, i = parse(str, i) + res[n] = x + n = n + 1 + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "]" then break end + if chr ~= "," then decode_error(str, i, "expected ']' or ','") end + end + return res, i +end + + +local function parse_object(str, i) + local res = {} + i = i + 1 + while 1 do + local key, val + i = next_char(str, i, space_chars, true) + -- Empty / end of object? + if str:sub(i, i) == "}" then + i = i + 1 + break + end + -- Read key + if str:sub(i, i) ~= '"' then + decode_error(str, i, "expected string for key") + end + key, i = parse(str, i) + -- Read ':' delimiter + i = next_char(str, i, space_chars, true) + if str:sub(i, i) ~= ":" then + decode_error(str, i, "expected ':' after key") + end + i = next_char(str, i + 1, space_chars, true) + -- Read value + val, i = parse(str, i) + -- Set + res[key] = val + -- Next token + i = next_char(str, i, space_chars, true) + local chr = str:sub(i, i) + i = i + 1 + if chr == "}" then break end + if chr ~= "," then decode_error(str, i, "expected '}' or ','") end + end + return res, i +end + + +local char_func_map = { + [ '"' ] = parse_string, + [ "0" ] = parse_number, + [ "1" ] = parse_number, + [ "2" ] = parse_number, + [ "3" ] = parse_number, + [ "4" ] = parse_number, + [ "5" ] = parse_number, + [ "6" ] = parse_number, + [ "7" ] = parse_number, + [ "8" ] = parse_number, + [ "9" ] = parse_number, + [ "-" ] = parse_number, + [ "t" ] = parse_literal, + [ "f" ] = parse_literal, + [ "n" ] = parse_literal, + [ "[" ] = parse_array, + [ "{" ] = parse_object, +} + + +parse = function(str, idx) + local chr = str:sub(idx, idx) + local f = char_func_map[chr] + if f then + return f(str, idx) + end + decode_error(str, idx, "unexpected character '" .. chr .. "'") +end + + +function json.decode(str) + if type(str) ~= "string" then + error("expected argument of type string, got " .. type(str)) + end + local res, idx = parse(str, next_char(str, 1, space_chars, true)) + idx = next_char(str, idx, space_chars, true) + if idx <= #str then + decode_error(str, idx, "trailing garbage") + end + return res +end + + +return json diff --git a/sh_config.lua b/sh_config.lua new file mode 100644 index 0000000..76e303f --- /dev/null +++ b/sh_config.lua @@ -0,0 +1,131 @@ +--[[ +FiveM-JailNJury +A Jail and Justice System that gives power back to the players. +Copyright (C) 2018 Jarrett Boice + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +]] + +JailConfig = {} +JailConfig = setmetatable(JailConfig, {}) + +jurors = {} +jailedPlayers = {} +JailConfig.jailFile = "jailed.json" + +JailConfig.stateName = "San Andreas" + +JailConfig.policePeds = { + "s_m_y_cop_01", + "s_f_y_cop_01" +} + +JailConfig.courtStartTime = 5 + +JailConfig.courtEntraceLocation = { x = 237.55, y = -406.18, z = 47.59 } +JailConfig.defendantLocation = { x = 218.19, y = -424.23, z = 55.677, h = 165.24 } +JailConfig.jurorLocations = { + { x = 218.25, y = -429.33, z = 55.677, h = 337.87 }, + { x = 217.30, y = -428.98, z = 55.677, h = 337.87 }, + { x = 216.62, y = -428.70, z = 55.677, h = 337.87 }, + { x = 215.94, y = -428.47, z = 55.677, h = 337.87 }, + { x = 214.90, y = -428.11, z = 55.677, h = 348.87 } +} +JailConfig.prisonLocation = { x = 1727.49, y = 2538.06, z = 44.94 } +JailConfig.prisonEntraceLocation = { x = 1854.42, y = 2586.12, z = 45.05} + +function shuffle(tbl) + size = #tbl + for i = size, 1, -1 do + local rand = math.random(i) + tbl[i], tbl[rand] = tbl[rand], tbl[i] + end + return tbl +end + +function getPlayerID(source) + local identifiers = GetPlayerIdentifiers(source) + local player = getIdentifiant(identifiers) + return player +end + +function getIdentifiant(id) + for _, v in ipairs(id) do + return v + end +end + +function isPolice(modelHash) + for i, policePed in ipairs(JailConfig.policePeds) do + if modelHash == GetHashKey(policePed) then + return true + end + return false + end +end + +function isJailed(permId) + for i, jailedPlayer in ipairs(jailedPlayers) do + if permId == jailedPlayer[1] then + return jailedPlayer + end + return false + end +end + +function updateJailTime(permId, newTime) + for i, jailedPlayer in ipairs(jailedPlayers) do + if permId == jailedPlayer[1] then + jailedPlayer[2] = newTime + end + end +end + +function removedJailedPlayer(permId) + for i, jailedPlayer in ipairs(jailedPlayers) do + if permId == jailedPlayer[1] then + table.remove(jailedPlayers, i) + end + end +end + +function updateTrialRequest(permId, boolean) + for i, jailedPlayer in ipairs(jailedPlayers) do + if permId == jailedPlayer[1] then + jailedPlayer[4] = boolean + end + end +end + +function getJuror() + shuffle(jurors) + return jurors[1] +end + +function inJurorPool(id) + for i, juror in ipairs(jurors) do + if id == juror then + return true + end + return false + end +end + +function removeJuror(id) + for i, juror in ipairs(jurors) do + if id == juror then + jurors[i] = nil + end + end +end diff --git a/sv_jailnjury.lua b/sv_jailnjury.lua new file mode 100644 index 0000000..105fc91 --- /dev/null +++ b/sv_jailnjury.lua @@ -0,0 +1,240 @@ +--[[ +FiveM-JailNJury +A Jail and Justice System that gives power back to the players. +Copyright (C) 2018 Jarrett Boice + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +]] + +local jailed_file = LoadResourceFile(GetCurrentResourceName(), JailConfig.jailFile) +jailedPlayers = json.decode(jailed_file) + +local votes = 0 +local votesNeeded = 2 +local timeToVerdict = false + +Citizen.CreateThread(function() + while true do + Citizen.Wait(0) + Citizen.Wait(1000 * 60) + SaveResourceFile(GetCurrentResourceName(), JailConfig.jailFile, json.encode(jailedPlayers), -1) + end +end) + +Citizen.CreateThread(function() + Citizen.Wait(0) + RegisterCommand("requesttrial", function(source, args, rawCommand) + local _source = source + local targetPedPermId = getPlayerID(_source) + local isJailedInfo = isJailed(targetPedPermId) + if isJailedInfo then + local jailedTime = isJailedInfo[2] + local jailedCharges = isJailedInfo[3] + local hasRequestedTrial = isJailedInfo[4] + TriggerClientEvent("chatMessage", _source, "^1Requesting a trial... Please Wait...") + Citizen.Wait(math.random(5000, 15000)) + if jailedTime >= 10 and hasRequestedTrial ~= true then + updateTrialRequest(targetPedPermId, true) + TriggerClientEvent("chatMessage", _source, "^1Your request for trial has been approved, and your jail time has been paused... The court case will start soon...") + TriggerClientEvent("chatMessage", _source, "^1Once the court case starts, you will have 1 minutes to explain your charge(s) to the jury: ^2" .. jailedCharges .. "^1.") + TriggerClientEvent("chatMessage", -1, "^1Jurors are needed at the courthouse for a court case. Be there within " .. JailConfig.courtStartTime .. " minutes to enter the juror pool.") + TriggerClientEvent("jnj:courtCaseStatus", _source, true) + TriggerClientEvent("jnj:courtCaseStatusAll", -1, true) + Citizen.Wait(1000 * 60 * 5) + TriggerEvent("jnj:startCourtCase", _source) + else + TriggerClientEvent("chatMessage", _source, "^1Your request for trial has been disapproved.") + end + else + TriggerClientEvent("chatMessage", _source, "^1You are not in jail, so you may not request a trial.") + end + end) +end) + +RegisterCommand("jurorverdict", function(source, args, rawCommand) + local _source = source + local isJuror = inJurorPool(_source) + local vote = string.lower(tostring(args[1])) + if isJuror and timeToVerdict then + if vote == "yes" then + TriggerClientEvent("chatMessage", _source, "^1You have casted your descision. A verdict will be reached soon...") + elseif vote == "no" then + votes = votes + 1 + TriggerClientEvent("chatMessage", _source, "^1You have casted your descision. A verdict will be reached soon...") + else + TriggerClientEvent("chatMessage", _source, "^1You have not placed your descision correctly. Vote ^2yes ^1for ^2guilty, vote ^2no ^1for ^2not guilty^1. Correct syntax: ^2/jurorverdict yes/no") + end + elseif not isJuror then + TriggerClientEvent("chatMessage", _source, "^1You are not a juror.") + elseif not timeToVerdict then + TriggerClientEvent("chatMessage", _source, "^1It is not time to reach a verdict.") + end +end) + +RegisterServerEvent("jnj:sendToJail") +AddEventHandler("jnj:sendToJail", function(targetPedId, jailTime, jailCharges) + local _source = source + if not GetPlayerName(targetPedId) then + TriggerClientEvent("chatMessage", _source, "^2" .. targetPedId .. " ^1is an invalid player ID.") + elseif isJailed(getPlayerID(targetPedId)) then + local targetPedName = GetPlayerName(targetPedId) + TriggerClientEvent("chatMessage", _source, "^2" .. targetPedName .. " ^1is already in jail.") + else + local officerName = GetPlayerName(_source) + local targetPedName = GetPlayerName(targetPedId) + local targetPedPermId = getPlayerID(targetPedId) + table.insert(jailedPlayers, {targetPedPermId, jailTime, jailCharges, false}) + TriggerClientEvent("jnj:sendToJail", targetPedId, {jailTime, jailCharges, false}) + TriggerClientEvent("chatMessage", -1, "^2" .. officerName .. " ^1jailed ^2" .. targetPedName .. " ^1for ^2" .. jailTime .. " ^1minutes.") + end +end) + +RegisterServerEvent("jnj:releaseFromJail") +AddEventHandler("jnj:releaseFromJail", function(targetPedId) + local _source = source + if not GetPlayerName(targetPedId) then + TriggerClientEvent("chatMessage", _source, "^2" .. targetPedId .. " ^1is an invalid player ID.") + elseif isJailed(getPlayerID(targetPedId)) then + local targetPedPermId = getPlayerID(targetPedId) + local targetPedName = GetPlayerName(targetPedId) + TriggerClientEvent("chatMessage", -1, "^2" .. targetPedName .. " ^1has finished their sentence and has been released from jail.") + removedJailedPlayer(targetPedPermId) + TriggerClientEvent("jnj:releaseFromJail", targetPedId) + else + TriggerClientEvent("chatMessage", _source, "^2" .. GetPlayerName(targetPedId) .. " ^1is not in jail.") + end +end) + +RegisterServerEvent("jnj:updateJailTime") +AddEventHandler("jnj:updateJailTime", function(newJailTime) + local _source = source + local targetPedPermId = getPlayerID(_source) + updateJailTime(targetPedPermId, newJailTime) +end) + +RegisterServerEvent("jnj:checkJailed") +AddEventHandler("jnj:checkJailed", function(newJailTime) + local _source = source + local targetPedPermId = getPlayerID(_source) + local isJailedInfo = isJailed(targetPedPermId) + if isJailedInfo then + local jailTime = isJailedInfo[2] + local jailCharges = isJailedInfo[3] + TriggerClientEvent("jnj:sendToJail", _source, {jailTime, jailCharges}) + TriggerClientEvent("chatMessage", _source, "^1Welcome back. You have resumed your jail sentence at ^2" .. isJailedInfo[2] .. " ^1Minutes.") + end +end) + +Citizen.CreateThread(function() + Citizen.Wait(0) + RegisterServerEvent("jnj:startCourtCase") + AddEventHandler("jnj:startCourtCase", function(targetPedId) + local targetPedPermId = getPlayerID(targetPedId) + local isJailedInfo = isJailed(targetPedPermId) + local playerCount = #GetPlayers() + local targetPedName = GetPlayerName(targetPedId) + local jailTime = isJailedInfo[2] + local jailCharges = isJailedInfo[3] + if #jurors < 3 then + jurors = {} + TriggerClientEvent("jnj:courtCaseStatus", targetPedPermId, false) + TriggerClientEvent("jnj:courtCaseStatusAll", -1, false) + TriggerClientEvent("chatMessage", -1, "^1The court case has been cancelled due to juror availability.") + else + local jurorNumber = 3 + if playerCount >= 5 then + jurorNumber = 5 + votesNeeded = 3 + end + local confirmedJurors = {} + for i = 1, jurorNumber do + local juror = getJuror() + table.insert(confirmedJurors, juror) + TriggerClientEvent("chatMessage", juror, "^1You have been selected to participate in the jury for the court case. It will be starting soon...") + end + for i, unconfirmedJuror in ipairs(jurors) do + TriggerClientEvent("chatMessage", unconfirmedJuror, "^1You have not made the final selection of the jury. You may now leave the courthouse.") + end + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("chatMessage", juror, "^2" .. targetPedName .. " ^1has been arrested for the following charge(s): ^2" .. jailCharges .. + "^1, amounting to a total time of ^2" .. jailTime .. " ^1minutes. The arestee has requested a trial. You and the other jurors must reach a verdict to find the arestee ^2guilty ^1or ^2not guity^1.") + TriggerClientEvent("chatMessage", juror, "^1The arestee will have 1 minute to explain their charges. You will not be able to comminucate with the jury or the arestee during this time." .. + "Once the arestee has explained their charges, you will deliberate with the rest of the jurors for 1 minute.") + TriggerClientEvent("jnj:teleportToCourt", juror, true, JailConfig.jurorLocations[i]) + end + TriggerClientEvent("jnj:teleportToCourt", targetPedId, true, JailConfig.defendantLocation) + Citizen.Wait(5000) + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("chatMessage", juror, "^1The arestee will now explain the charges.") + end + TriggerClientEvent("jnj:teleportToCourt", targetPedId, false, JailConfig.defendantLocation) + TriggerClientEvent("chatMessage", targetPedId, "^1The jury is ready to hear your explanation.") + Citizen.Wait(1000 * 30) + TriggerClientEvent("chatMessage", targetPedId, "^1You have 30 seconds left for your explaination.") + Citizen.Wait(1000 * 30) + TriggerClientEvent("jnj:teleportAwayCourt", targetPedId, JailConfig.prisonLocation) + TriggerClientEvent("chatMessage", targetPedId, "^1The jury will now deliberate...") + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("jnj:teleportToCourt", juror, false, JailConfig.jurorLocations[i]) + TriggerClientEvent("chatMessage", juror, "^1You must now deliberate for 1 minute with the rest of the jurors and reach a verdict.") + end + Citizen.Wait(1000 * 30) + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("chatMessage", juror, "^1You have 30 seconds left to reach a verdict.") + end + Citizen.Wait(1000 * 30) + timeToVerdict = true + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("jnj:teleportToCourt", juror, true, JailConfig.jurorLocations[i]) + TriggerClientEvent("chatMessage", juror, "^1You must now cast your verdict.") + TriggerClientEvent("chatMessage", juror, "^2/jurorverdict yes ^1 for ^2guilty^1.") + TriggerClientEvent("chatMessage", juror, "^2/jurorverdict no ^1 for ^2not guilty^1.") + TriggerClientEvent("chatMessage", juror, "^1You have 30 seconds to cast your verdict.") + end + Citizen.Wait(1000 * 30) + timeToVerdict = false + for i, juror in ipairs(confirmedJurors) do + TriggerClientEvent("jnj:teleportAwayCourt", juror, JailConfig.courtEntraceLocation) + TriggerClientEvent("chatMessage", juror, "^1Thank you for participating in our great justice system.") + end + if votes >= votesNeeded then + TriggerEvent("jnj:releaseFromJail", targetPedId) + TriggerClientEvent("chatMessage", -1, "^1In the case of ^2" .. targetPedName .. " ^1vs. the people of ^2" .. JailConfig.stateName .. + " ^1the jury has found ^2" .. targetPedName .. " ^2not guilty ^1of all charges.") + else + TriggerClientEvent("chatMessage", -1, "^1In the case of ^2" .. targetPedName .. " ^1vs. the people of ^2" .. JailConfig.stateName .. + " ^1the jury has found ^2" .. targetPedName .. " ^2guilty ^1of all charges.") + end + TriggerClientEvent("jnj:courtCaseStatusAll", -1, false) + jurors = {} + end + end) +end) + +RegisterServerEvent("jnj:requestJuror") +AddEventHandler("jnj:requestJuror", function(insideRange) + local _source = source + local targetPedPermId = getPlayerID(_source) + local isJailedInfo = isJailed(targetPedPermId) + if not isJailedInfo then + if inJurorPool(_source) then + removeJuror(_source) + TriggerClientEvent("chatMessage", _source, "^1You been removed from the juror pool.") + elseif not inJurorPool(_source) and insideRange then + table.insert(jurors, _source) + TriggerClientEvent("chatMessage", _source, "^1You have entered the juror pool. You will be notified soon if you will make it through the selection process. Do not leave the courthouse.") + end + end + print(json.encode(jurors)) +end)