A clean, secure prison system is a common server feature that must be server-authoritative, resilient to disconnects, and easy for admins to operate. This guide walks through design choices, persistence, escape prevention, admin commands and a sample implementation pattern that fits modern QBCore servers.
Overview & implementation plan
This guide explains how to build a QBCore prison script that: enforces jail time server-side, persists jail state across disconnects, supports admin commands and qb-target interactions, and prevents exploits. Implementation plan:
- Decide data persistence (oxmysql or ghmattimysql) and schema changes.
- Create server-side APIs that validate permission and set jail state in the DB.
- Create a client script to teleport, limit controls, play animations and sync timers from the server.
- Integrate qb-target for in-world interactions and add safe admin commands.
- Test disconnect, reconnect, and failure scenarios.
Assumptions and framework dependencies
- Server uses QBCore (v2+ or compatible). APIs like
QBCore.Functions.GetPlayerexist. - MySQL resource available (oxmysql or ghmattimysql). Examples use exports.oxmysql:execute but you can swap to your DB wrapper.
- qb-target or equivalent is installed for interaction targets.
- The users table stores a unique identifier (steam/fivem identifier). If your schema differs, adjust SQL and metadata keys accordingly.
Core design principles
Keep authority on the server. The server must be the source of truth for who is jailed and for how long. Clients should only present visuals and report input; they must not be trusted with countdown logic, granting parole, or modifying jail state.
Server responsibilities
- Validate admin actions and permission checks.
- Record jail state in persistent storage.
- Emit events to clients to apply confinement (teleport, freeze).
- Run periodic timers and release players when time elapses.
Client responsibilities
- Receive server events and apply local restrictions (disable controls, UI).
- Play animations or cell camera effects.
- Attempt reconnection gracefully and re-sync jail state on connect.
Minimal safe code example
Below is a compact pattern showing a server command to jail a player, persistent DB write, and an event emitted to the client. This is an illustrative example; update identifiers and SQL for your schema.
-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'YourName'
description 'QBCore Prison Script'
version '1.0.0'
shared_script '@qb-core/import.lua'
server_script 'server/server.lua'
client_script 'client/client.lua'
-- server/server.lua
local QBCore = exports['qb-core']:GetCoreObject()
QBCore.Commands.Add('jail', 'Jail a player', {{name='id', help='Player ID'}, {name='minutes', help='Minutes to jail'}}, true, function(source, args)
local src = source
local targetId = tonumber(args[1])
local minutes = tonumber(args[2]) or 5
local caller = QBCore.Functions.GetPlayer(src)
if not caller or not caller.PlayerData.job or caller.PlayerData.job.name ~= 'police' then
return TriggerClientEvent('QBCore:Notify', src, 'No permission', 'error')
end
local target = QBCore.Functions.GetPlayer(targetId)
if not target then
return TriggerClientEvent('QBCore:Notify', src, 'Player not online', 'error')
end
local identifier = target.PlayerData.citizenid or target.PlayerData.license
local jailTime = os.time() + (minutes * 60)
-- Persist jail state (example uses oxmysql)
exports.oxmysql:execute('UPDATE users SET jail = ?, jail_time = ? WHERE citizenid = ?', {1, jailTime, identifier})
-- Notify target client
TriggerClientEvent('qb-prison:client:setJailed', target.PlayerData.source, minutes)
TriggerClientEvent('QBCore:Notify', src, 'Player jailed for ' .. minutes .. ' minutes')
end, 'police')
-- On server start / player load: re-sync jailed players
AddEventHandler('QBCore:Server:PlayerLoaded', function(playerId)
local src = playerId
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local identifier = Player.PlayerData.citizenid
exports.oxmysql:execute('SELECT jail, jail_time FROM users WHERE citizenid = ?', {identifier}, function(result)
if result and result[1] and result[1].jail == 1 then
local remaining = math.max(0, result[1].jail_time - os.time())
if remaining > 0 then
TriggerClientEvent('qb-prison:client:setJailed', src, math.floor(remaining / 60))
else
-- cleanup expired jail
exports.oxmysql:execute('UPDATE users SET jail = ?, jail_time = ? WHERE citizenid = ?', {0, 0, identifier})
end
end
end)
end)
-- client/client.lua
local jailed = false
RegisterNetEvent('qb-prison:client:setJailed', function(minutes)
jailed = true
-- simple example: teleport to cell, freeze and open UI
local ped = PlayerPedId()
DoScreenFadeOut(500)
Wait(600)
SetEntityCoords(ped, vector3(1790.0, 2570.0, 45.8)) -- adjust to your cell
FreezeEntityPosition(ped, true)
DoScreenFadeIn(500)
-- show countdown or UI driven by server
end)
-- Prevent actions while jailed
Citizen.CreateThread(function()
while true do
Citizen.Wait(0)
if jailed then
DisableControlAction(0, 1, true) -- look
DisableControlAction(0, 2, true) -- move
-- keep network alive and short waits
else
Citizen.Wait(1000)
end
end
end)
Checklist: server vs client responsibilities
| Responsibility | Server | Client |
|---|---|---|
| Validate admin action | ✔️ | |
| Persist jail state | ✔️ | |
| Timer and release | ✔️ | Display only |
| Teleport & freeze visuals | Trigger | Apply |
| Escape detection | ✔️ (pos checks) | Report pos |
Security considerations
Never rely on client-side timers or client requests to end jail. When you release someone, update the DB and then emit a release event. On reconnect the server must re-check DB state; do not trust a reconnecting client's memory. Monitor for teleport/vehicle exploits by periodically checking jailed players' coordinates server-side and re-teleport them if they leave the cell area.
Failure & resilience
If your DB is temporarily unavailable, queue actions in memory with conservative defaults and refuse admin jail commands until persistence is confirmed, or show a clear admin error. On player load, if DB query fails, log a server warning and keep the player disconnected from jail areas until persistence is restored or an admin intervenes.
Operational tips & integrations
Use qb-target to add interaction points in the prison (visiting, release desk) and register server callbacks (server-side validation for visit requests). Test disconnect/reconnect flows thoroughly. For quick scaffolding and to auto-generate file templates, consider using the Stellar AI app to produce destination-labelled files and iteration-ready plans: https://trystellarai.com/app. When you want deeper design posts or implementation walkthroughs, read the Stellar AI blog: https://trystellarai.com/blog.
Stellar AI can help generate and revise the project files for this workflow. Describe your framework, existing dependencies, and one testable feature, then bring back errors for a focused revision. You remain responsible for running the tests and managing deployment in your own development environment.
Next steps
- Implement periodic server-side checks that enforce position and release.
- Add an admin UI to inspect jailed players and remaining times.
- Extend with visitor systems, contraband detection, and jobs inside the prison.