Arquitetura do Sistema Prisional
Um sistema prisional é um dos recursos mais impactantes que você pode adicionar a um servidor de RPG FiveM porque cria consequências significativas para atividades criminosas, ao mesmo tempo que dá aos jogadores encarcerados algo envolvente para fazer, em vez de ficar olhando para uma parede por trinta minutos. A arquitetura gira em torno de um cronômetro de prisão que faz a contagem regressiva em tempo real, um conjunto de atividades prisionais que permitem aos presidiários reduzir sua pena por meio do trabalho e um sistema de limites que restringe os jogadores ao complexo prisional até que sua pena seja cumprida. A localização da Penitenciária Bolingbroke em GTA V é a escolha padrão porque possui um interior totalmente modelado com blocos de celas, um pátio e instalações adjacentes. Seu sistema precisa de três componentes principais: a lógica da sentença no servidor que atribui a pena de prisão e retira armas e contrabando, a aplicação de limites do lado do cliente que teletransporta os jogadores de volta se eles deixarem o perímetro da prisão e a estrutura de atividades que dá aos presos tarefas produtivas para passar o tempo.
Esquema de banco de dados e sentença
O banco de dados rastreia sentenças ativas, histórico de sentenças para registros criminais e progresso das atividades dos presidiários. Armazene o horário de término da frase como um carimbo de data/hora absoluto, em vez de uma duração restante, para que o tempo continue a contagem regressiva mesmo quando o jogador estiver offline, evitando a exploração em que os jogadores fazem logoff para pausar a frase. Inclua campos para o policial que fez a prisão, acusações e duração da sentença original para fins de manutenção de registros:
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)
);Sentenciando um jogador
Quando a polícia usa o comando de prisão, o servidor valida as permissões do policial, calcula a sentença com base nas acusações, retira as armas e itens ilegais do preso e os teletransporta para um ponto de desova dentro da prisão. A sentença deve ser persistida no banco de dados imediatamente para que sobreviva às reinicializações do servidor. Implemente a sentença como um comando do lado do servidor que somente policiais autorizados podem executar:
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)Atividades Prisionais
As atividades na prisão são o ciclo central do jogo que mantém os jogadores encarcerados envolvidos. Sem eles, a pena de prisão é uma punição que leva os jogadores a se desconectarem, o que é ruim para a população do servidor. Projete atividades que ofereçam uma compensação: realizar trabalho reduz sua sentença, dando aos presos liberdade de ação sobre sua experiência. Cada atividade deve ter um tempo de espera para evitar spam e um limite máximo de redução por ciclo de atividade, para que uma frase de 60 minutos não possa ser resolvida em 5 minutos. As atividades mais comuns são a mineração na pedreira, a prática de exercícios na academia, a limpeza das instalações e a permanência na biblioteca da prisão. Cada atividade usa um minijogo ou padrão de interação diferente para fornecer variedade:
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)Sistema de liberdade condicional
Um sistema de liberdade condicional adiciona uma camada de dramatização entre a prisão e a liberdade total. Depois que um preso cumpre uma porcentagem configurável de sua sentença, normalmente 60-75%, ele se torna elegível para liberdade condicional. A liberdade condicional pode ser concedida por um juiz ou policial sênior por meio de um comando, que libera o jogador antecipadamente, mas o coloca sob restrições. Os jogadores em liberdade condicional podem ter um toque de recolher que exige que eles estejam em suas propriedades entre determinados horários, uma exigência de verificar com um oficial de liberdade condicional em intervalos regulares e proibições de posse de armas ou de entrada em determinadas áreas. Se um jogador em liberdade condicional violar qualquer condição, ele será automaticamente devolvido à prisão com a sentença restante mais tempo adicional pela violação. Rastreie o status da liberdade condicional nos metadados do jogador e execute verificações periódicas no servidor para verificar a conformidade com as condições.
Eventos de fuga da prisão
As fugas da prisão são eventos que abrangem todo o servidor e criam jogos de alto risco tanto para criminosos quanto para autoridades. Uma fuga da prisão deve exigir uma preparação significativa, vários participantes e dar à polícia aviso suficiente para preparar uma resposta. Implemente-o como um evento multifásico: uma equipe externa deve primeiro adquirir itens específicos como helicóptero, explosivos e disfarces. Em seguida, eles devem hackear o sistema de segurança da prisão através de um minijogo difícil em um painel de controle externo. Uma vez que a segurança é baixa, as portas das celas se abrem para uma janela limitada e os presos podem tentar escapar por rotas predeterminadas enquanto os NPCs da guarda e a polícia em resposta tentam detê-los. O sistema só deve permitir fugas de prisão quando um número mínimo de agentes da polícia estiver online para garantir uma resposta justa:
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) },
},
}Sistema NPC de Guarda
Os NPCs de guarda fornecem segurança ambiental ao redor da prisão e evitam que os presos saiam casualmente. Coloque guardas nos principais pontos de estrangulamento, como o portão principal, as entradas dos blocos de celas e ao longo da parede do perímetro. Cada guarda deve ter uma rota de patrulha com pontos de referência pelos quais ele percorre, criando janelas onde certas áreas estão desprotegidas. Os guardas detectam os presos que saem das zonas autorizadas e respondem dando uma advertência verbal, perseguindo e atacando se o preso não obedecer. Implemente a detecção de guardas usando verificações de proximidade que levam em conta a linha de visão para que os presos possam passar furtivamente pelos guardas permanecendo atrás de cobertura. Durante um evento de fuga da prisão, coloque guardas adicionais em posições-chave e aumente seu nível de agressão. Use as funções nativas do FiveM TaskPatrol e SetPedCombatAttributes para fornecer aos guardas um comportamento de patrulha realista. Certifique-se de definir seu grupo de relacionamento para ser hostil com os presos e, ao mesmo tempo, permanecer neutro em relação à polícia e aos visitantes, evitando incidentes de fogo amigo durante as respostas à fuga da prisão.
Aplicação e Liberação de Limites
O sistema de limites mantém os presos dentro do perímetro da prisão usando uma zona poligonal que cobre toda a instalação. Quando um jogador com uma sentença ativa se move para fora desta zona, ele recebe uma notificação de aviso e tem 10 segundos para retornar antes de ser teletransportado de volta ao ponto de spawn da prisão com tempo adicional adicionado à sua sentença como penalidade por tentativa de fuga. No lado do cliente, execute uma verificação periódica a cada 2 segundos que compare a posição do jogador com o polígono dos limites da prisão. Para liberação, o servidor executa um loop de cronômetro que verifica todas as sentenças ativas a cada 30 segundos. Quando uma sentença expira, o sistema atualiza o status do banco de dados para liberado, limpa os metadados da prisão do jogador, restaura seu inventário de um backup pré-prisão, se você implementou um, e os teletransporta para a saída da prisão com uma notificação de que estão livres. Se o jogador estiver offline quando sua sentença expirar, a liberação será tratada durante seu próximo login, verificando se há sentenças ativas expiradas no evento de carregamento do jogador. Inclui um comando para a polícia liberar manualmente os jogadores antecipadamente em situações como prisão injusta ou acordos judiciais negociados por meio de roleplay.
Integração com a Polícia e Sistemas Jurídicos
Seu sistema prisional deve integrar-se estreitamente com o MDT da polícia e com qualquer sistema judicial no servidor. Quando um jogador é preso, seu registro criminal no MDT deve ser atualizado automaticamente com as acusações, duração da sentença e oficial de prisão. Se o seu servidor tiver um sistema judicial, as sentenças emitidas por um juiz deverão fluir pela mesma função de prisão para garantir consistência. Implementar um sistema de recurso de sentença onde os reclusos possam apresentar um recurso a partir da biblioteca da prisão que crie um bilhete para os juízes analisarem, potencialmente levando a reduções de sentença ou libertação antecipada. Rastreie a reincidência contando quantas vezes cada cidadão foi preso e use esses dados para implementar sentenças mais severas para reincidentes por meio de um sistema multiplicador. Exporte sua função de prisão para que outros recursos, como sistema judicial, detecção automatizada de crimes ou comandos administrativos, possam enviar jogadores para a prisão pelo mesmo caminho validado.
