Prison System Architecture
A prison system is one of the most impactful features you can add to a FiveM roleplay server because it creates meaningful consequences for criminal activity while giving incarcerated players something engaging to do instead of staring at a wall for thirty minutes. The architecture revolves around a jail timer that counts down in real time, a set of prison activities that allow inmates to reduce their sentence through labor, and a boundary system that restricts players to the prison compound until their time is served. The Bolingbroke Penitentiary location in GTA V is the standard choice because it has a fully modeled interior with cell blocks, a yard, and surrounding facilities. Your system needs three core components: the sentencing logic on the server that assigns jail time and strips weapons and contraband, the client-side boundary enforcement that teleports players back if they leave the prison perimeter, and the activity framework that gives inmates productive tasks to pass the time.
Database Schema and Sentencing
The database tracks active sentences, sentence history for criminal records, and inmate progress on activities. Store the sentence end time as an absolute timestamp rather than a remaining duration so that time continues counting down even when the player is offline, preventing the exploit where players log off to pause their sentence. Include fields for the arresting officer, charges, and original sentence length for record-keeping purposes:
CREATE TABLE IF NOT EXISTS prison_sentences (
id INT AUTO_INCREMENT PRIMARY KEY,
citizenid VARCHAR(50) NOT NULL,
sentence_minutes INT NOT NULL,
time_served INT DEFAULT 0,
reduction_earned INT DEFAULT 0,
jailed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
release_at TIMESTAMP NOT NULL,
charges TEXT DEFAULT NULL,
arresting_officer VARCHAR(50) DEFAULT NULL,
status ENUM('active', 'released', 'escaped', 'pardoned') DEFAULT 'active',
parole_eligible BOOLEAN DEFAULT FALSE,
INDEX idx_citizen (citizenid),
INDEX idx_status (status)
);
CREATE TABLE IF NOT EXISTS prison_activity_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
citizenid VARCHAR(50) NOT NULL,
sentence_id INT NOT NULL,
activity_type ENUM('mining', 'gym', 'cleaning', 'library', 'yard') NOT NULL,
reduction_minutes INT DEFAULT 0,
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sentence_id) REFERENCES prison_sentences(id)
);
Sentencing a Player
When police use the jail command, the server validates the officer's permissions, calculates the sentence based on charges, strips the inmate's weapons and illegal items, and teleports them to a spawn point inside the prison. The sentence must be persisted to the database immediately so it survives server restarts. Implement the sentencing as a server-side command that only authorized police officers can execute:
RegisterCommand('jail', function(source, args)
local src = source
local Officer = QBCore.Functions.GetPlayer(src)
if not Officer then return end
-- Check police job
if Officer.PlayerData.job.name ~= 'police' then
TriggerClientEvent('QBCore:Notify', src, 'Unauthorized', 'error')
return
end
local targetId = tonumber(args[1])
local minutes = tonumber(args[2])
local charges = table.concat(args, ' ', 3)
if not targetId or not minutes or minutes <= 0 then
TriggerClientEvent('QBCore:Notify', src, 'Usage: /jail [id] [minutes] [charges]', 'error')
return
end
local Target = QBCore.Functions.GetPlayer(targetId)
if not Target then
TriggerClientEvent('QBCore:Notify', src, 'Player not found', 'error')
return
end
local citizenid = Target.PlayerData.citizenid
local releaseAt = os.date('!%Y-%m-%d %H:%M:%S', os.time() + (minutes * 60))
-- Insert sentence
local sentenceId = MySQL.insert.await(
'INSERT INTO prison_sentences (citizenid, sentence_minutes, release_at, charges, arresting_officer) VALUES (?, ?, ?, ?, ?)',
{ citizenid, minutes, releaseAt, charges, Officer.PlayerData.citizenid }
)
-- Strip weapons and contraband
local contraband = {'weapon_pistol', 'weapon_smg', 'lockpick', 'thermite'}
for _, item in ipairs(contraband) do
local playerItem = Target.Functions.GetItemByName(item)
if playerItem then
Target.Functions.RemoveItem(item, playerItem.amount)
end
end
-- Set jail metadata and teleport
Target.Functions.SetMetaData('injail', sentenceId)
TriggerClientEvent('prison:client:enter', targetId, sentenceId, minutes)
TriggerClientEvent('QBCore:Notify', src, 'Jailed ' .. GetPlayerName(targetId) .. ' for ' .. minutes .. ' minutes', 'success')
end, false)
Prison Activities
Prison activities are the core gameplay loop that keeps incarcerated players engaged. Without them, jail time is a punishment that drives players to log off, which is bad for server population. Design activities that offer a trade-off: performing labor reduces your sentence, giving inmates agency over their experience. Each activity should have a cooldown to prevent spam and a maximum reduction cap per activity cycle so that a 60-minute sentence cannot be cleared in 5 minutes. The most common activities are mining in the quarry, working out in the gym, cleaning the facility, and spending time in the prison library. Each activity uses a different minigame or interaction pattern to provide variety:
Config.PrisonActivities = {
mining = {
label = 'Quarry Mining',
coords = vector3(1690.64, 2592.63, 45.56),
radius = 15.0,
reductionPerCycle = 2, -- minutes reduced per completion
cycleDuration = 45, -- seconds per mining cycle
cooldown = 30, -- seconds between cycles
maxReductionPerSession = 10,
animation = { dict = 'amb@world_human_hammering@male@base', anim = 'base' },
requiredProp = 'prop_tool_pickaxe',
},
gym = {
label = 'Prison Gym',
coords = vector3(1662.67, 2527.84, 45.56),
radius = 10.0,
reductionPerCycle = 1,
cycleDuration = 30,
cooldown = 60,
maxReductionPerSession = 6,
animation = { dict = 'amb@world_human_muscle_free_weights@male@barbell@base', anim = 'base' },
},
cleaning = {
label = 'Facility Cleaning',
coords = vector3(1653.18, 2499.26, 45.56),
radius = 20.0,
reductionPerCycle = 1,
cycleDuration = 25,
cooldown = 20,
maxReductionPerSession = 8,
animation = { dict = 'amb@world_human_janitor@male@base', anim = 'base' },
requiredProp = 'prop_cs_broom',
},
library = {
label = 'Prison Library',
coords = vector3(1680.21, 2513.45, 45.56),
radius = 8.0,
reductionPerCycle = 1,
cycleDuration = 60,
cooldown = 45,
maxReductionPerSession = 4,
animation = { dict = 'amb@world_human_clipboard@male@base', anim = 'base' },
},
}
-- Server-side activity completion handler
RegisterNetEvent('prison:server:completeActivity', function(activityType, sentenceId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local citizenid = Player.PlayerData.citizenid
local activity = Config.PrisonActivities[activityType]
if not activity then return end
-- Validate sentence is active
local sentence = MySQL.single.await(
'SELECT * FROM prison_sentences WHERE id = ? AND citizenid = ? AND status = "active"',
{ sentenceId, citizenid }
)
if not sentence then return end
-- Check session reduction cap
local sessionReduction = MySQL.scalar.await(
'SELECT COALESCE(SUM(reduction_minutes), 0) FROM prison_activity_log WHERE sentence_id = ? AND activity_type = ? AND completed_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)',
{ sentenceId, activityType }
)
if sessionReduction >= activity.maxReductionPerSession then
TriggerClientEvent('QBCore:Notify', src, 'Maximum reduction reached for this activity', 'error')
return
end
-- Apply reduction
local reduction = activity.reductionPerCycle
MySQL.update('UPDATE prison_sentences SET reduction_earned = reduction_earned + ?, release_at = DATE_SUB(release_at, INTERVAL ? MINUTE) WHERE id = ?',
{ reduction, reduction, sentenceId })
MySQL.insert('INSERT INTO prison_activity_log (citizenid, sentence_id, activity_type, reduction_minutes) VALUES (?, ?, ?, ?)',
{ citizenid, sentenceId, activityType, reduction })
TriggerClientEvent('QBCore:Notify', src, 'Sentence reduced by ' .. reduction .. ' minute(s)', 'success')
TriggerClientEvent('prison:client:updateTimer', src, reduction)
end)
Parole System
A parole system adds a layer of roleplay between prison and full freedom. Once an inmate has served a configurable percentage of their sentence, typically 60-75%, they become eligible for parole. Parole can be granted by a judge or senior police officer through a command, which releases the player early but places them under restrictions. Paroled players might have a curfew that requires them to be in their property between certain hours, a requirement to check in with a parole officer at regular intervals, and prohibitions on possessing weapons or entering certain areas. If a paroled player violates any condition, they are automatically returned to prison with their remaining sentence plus additional time for the violation. Track parole status in the player metadata and run periodic checks on the server to verify compliance with conditions.
Prison Break Events
Prison breaks are server-wide events that create high-stakes roleplay for both criminals and law enforcement. A prison break should require significant preparation, multiple participants, and give police enough warning to mount a response. Implement it as a multi-phase event: an outside team must first acquire specific items like a helicopter, explosives, and disguises. Then they must hack the prison security system through a difficult minigame at an exterior control panel. Once security is down, cell doors open for a limited window and inmates can attempt to escape through predetermined routes while guard NPCs and responding police try to stop them. The system should only allow prison breaks when a minimum number of police officers are online to ensure a fair response:
Config.PrisonBreak = {
minPoliceOnline = 5,
cooldown = 28800, -- 8 hours between break attempts
requiredItems = {
{ name = 'electronickit', amount = 2, label = 'Electronic Kit' },
{ name = 'thermite', amount = 3, label = 'Thermite Charge' },
},
phases = {
{
name = 'hack_security',
label = 'Hack Security Grid',
coords = vector3(1746.23, 2488.90, 45.80),
duration = 60,
minigame = { type = 'hack', difficulty = 'hard', attempts = 3 },
},
{
name = 'breach_wall',
label = 'Breach Perimeter Wall',
coords = vector3(1651.78, 2569.33, 45.56),
duration = 30,
minigame = { type = 'thermite', time = 10 },
},
{
name = 'disable_lockdown',
label = 'Disable Lockdown Protocol',
coords = vector3(1691.45, 2565.12, 45.56),
duration = 45,
minigame = { type = 'hack', difficulty = 'expert', attempts = 2 },
},
},
escapeWindow = 300, -- 5 minutes to escape after all phases complete
escapeRoutes = {
{ label = 'Main Gate', coords = vector3(1845.12, 2585.87, 45.67) },
{ label = 'Drainage Tunnel', coords = vector3(1617.34, 2523.90, 44.12) },
{ label = 'Helipad', coords = vector3(1700.89, 2565.34, 52.34) },
},
}
Guard NPC System
Guard NPCs provide ambient security around the prison and prevent inmates from casually walking out. Place guards at key chokepoints like the main gate, cell block entrances, and along the perimeter wall. Each guard should have a patrol route with waypoints that it cycles through, creating windows where certain areas are unguarded. Guards detect inmates who leave authorized zones and respond by giving a verbal warning, then pursuing and attacking if the inmate does not comply. Implement guard detection using proximity checks that account for line of sight so that inmates can sneak past guards by staying behind cover. During a prison break event, spawn additional guards at key positions and increase their aggression level. Use the FiveM native functions TaskPatrol and SetPedCombatAttributes to give guards realistic patrol behavior. Make sure to set their relationship group to be hostile toward inmates while remaining neutral to police and visitors, preventing friendly fire incidents during prison break responses.
Boundary Enforcement and Release
The boundary system keeps inmates within the prison perimeter using a polygon zone that covers the entire facility. When a player with an active sentence moves outside this zone, they receive a warning notification and have 10 seconds to return before being teleported back to the prison spawn point with additional time added to their sentence as a penalty for attempted escape. On the client side, run a periodic check every 2 seconds that compares the player's position against the prison boundary polygon. For release, the server runs a timer loop that checks all active sentences every 30 seconds. When a sentence expires, the system updates the database status to released, clears the player's jail metadata, restores their inventory from a pre-jail backup if you implemented one, and teleports them to the prison exit with a notification that they are free. If the player is offline when their sentence expires, the release is handled during their next login by checking for expired active sentences in the player load event. Include a command for police to manually release players early for situations like wrongful imprisonment or plea deals negotiated through roleplay.
Integration with Police and Legal Systems
Your prison system should integrate tightly with the police MDT and any court system on the server. When a player is jailed, their criminal record in the MDT should automatically update with the charges, sentence length, and arresting officer. If your server has a court system, sentences issued by a judge should flow through the same jailing function to ensure consistency. Implement a sentence appeal system where inmates can submit an appeal from the prison library that creates a ticket for judges to review, potentially leading to sentence reductions or early release. Track recidivism by counting how many times each citizen has been jailed and use this data to implement harsher sentencing for repeat offenders through a multiplier system. Export your jailing function so other resources like the court system, automated crime detection, or admin commands can all send players to prison through the same validated pathway.