Sistema penitenciario de FiveM: un script para que la cárcel tenga sentido
Crea una prisión que invite a participar: trabajos, fugas, guardias, libertad condicional y los mejores scripts de FiveM para un rol profundo.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
Arquitectura del sistema penitenciario
Un sistema penitenciario es una de las funciones más importantes que puedes añadir a un servidor de roleplay de FiveM, porque crea consecuencias significativas para la actividad delictiva y ofrece a los jugadores encarcelados algo entretenido que hacer en lugar de quedarse mirando una pared durante treinta minutos. La arquitectura gira en torno a un temporizador de prisión que cuenta el tiempo en tiempo real, un conjunto de actividades penitenciarias que permite a los reclusos reducir su condena trabajando y un sistema de límites que mantiene a los jugadores dentro del recinto de la prisión hasta que cumplan su condena. La penitenciaría de Bolingbroke en GTA V es la opción habitual porque tiene un interior completamente modelado, con bloques de celdas, un patio y las instalaciones circundantes. Tu sistema necesita tres componentes principales: la lógica de condena en el servidor, que asigna tiempo de prisión y retira armas y contrabando; la aplicación de límites en el cliente, que teletransporta a los jugadores de vuelta si salen del perímetro de la prisión; y el sistema de actividades, que ofrece a los reclusos tareas productivas para pasar el tiempo.
Esquema de la base de datos y condenas
La base de datos registra las condenas activas, el historial de condenas para los antecedentes penales y el progreso de los reclusos en las actividades. Guarda la hora de finalización de la condena como una marca de tiempo absoluta, en lugar de como una duración restante, para que el tiempo siga descontándose aunque el jugador no esté conectado. Así se evita el exploit que permite a los jugadores cerrar sesión para pausar su condena. Incluye campos para el agente que realizó el arresto, los cargos y la duración original de la condena, con fines de registro:
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)
);
Condenar a un jugador
Cuando la policía usa el comando de encarcelamiento, el servidor valida los permisos del agente, calcula la condena según los cargos, retira las armas y los objetos ilegales del recluso y lo teletransporta a un punto de aparición dentro de la prisión. La condena debe guardarse inmediatamente en la base de datos para que sobreviva a los reinicios del servidor. Implementa la condena como un comando del servidor que solo puedan ejecutar los agentes de policía autorizados:
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)
Actividades penitenciarias
Las actividades penitenciarias son el ciclo de juego principal que mantiene entretenidos a los jugadores encarcelados. Sin ellas, el tiempo en prisión se convierte en un castigo que lleva a los jugadores a cerrar sesión, lo cual perjudica la población del servidor. Diseña actividades que ofrezcan una compensación: trabajar reduce la condena y permite que los reclusos decidan cómo vivir su experiencia. Cada actividad debe tener un tiempo de espera para evitar el abuso y un límite máximo de reducción por ciclo, para que una condena de 60 minutos no se pueda cumplir en 5 minutos. Las actividades más habituales son extraer mineral en la cantera, entrenar en el gimnasio, limpiar las instalaciones y pasar tiempo en la biblioteca de la prisión. Cada actividad usa un minijuego o patrón de interacción distinto para ofrecer variedad:
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 libertad condicional
Un sistema de libertad condicional añade una fase de roleplay entre la prisión y la libertad plena. Cuando un recluso ha cumplido un porcentaje configurable de su condena, normalmente 60-75%, puede optar a la libertad condicional. Un juez o un agente de policía de alto rango puede concederla mediante un comando, que libera al jugador antes de tiempo, pero lo somete a restricciones. Los jugadores en libertad condicional pueden tener un toque de queda que les obliga a permanecer en su propiedad durante ciertas horas, la obligación de presentarse periódicamente ante un agente de libertad condicional y la prohibición de poseer armas o entrar en determinadas zonas. Si un jugador en libertad condicional incumple alguna condición, se le devuelve automáticamente a prisión para cumplir el tiempo restante, más tiempo adicional por el incumplimiento. Registra el estado de libertad condicional en los metadatos del jugador y ejecuta comprobaciones periódicas en el servidor para verificar que se cumplan las condiciones.
Eventos de fuga de prisión
Las fugas de prisión son eventos que afectan a todo el servidor y crean situaciones de roleplay de alto riesgo tanto para los criminales como para las fuerzas del orden. Una fuga de prisión debe requerir una preparación considerable y varios participantes, y dar a la policía tiempo suficiente para organizar una respuesta. Implementa el evento en varias fases: primero, un equipo externo debe conseguir objetos específicos, como un helicóptero, explosivos y disfraces. Luego, debe hackear el sistema de seguridad de la prisión mediante un minijuego difícil en un panel de control exterior. Cuando se desactiva la seguridad, las puertas de las celdas se abren durante un tiempo limitado y los reclusos pueden intentar escapar por rutas predeterminadas mientras los NPC guardias y la policía que acude al lugar intentan detenerlos. El sistema solo debe permitir fugas de prisión cuando haya conectado un número mínimo de agentes de policía, para garantizar una respuesta equilibrada:
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 de NPC guardias
Los NPC guardias proporcionan seguridad ambiental alrededor de la prisión y evitan que los reclusos simplemente salgan caminando. Coloca guardias en puntos clave de paso, como la entrada principal, los accesos a los bloques de celdas y el perímetro de la muralla. Cada guardia debe tener una ruta de patrulla con puntos de referencia por los que vaya pasando, creando momentos en los que ciertas zonas quedan sin vigilancia. Los guardias detectan a los reclusos que salen de las zonas autorizadas y reaccionan con una advertencia verbal, seguida de una persecución y un ataque si el recluso no obedece. Implementa la detección de los guardias mediante comprobaciones de proximidad que tengan en cuenta la línea de visión, para que los reclusos puedan escabullirse si se mantienen a cubierto. Durante una fuga de prisión, genera guardias adicionales en posiciones clave y aumenta su nivel de agresividad. Usa las funciones nativas de FiveM TaskPatrol y SetPedCombatAttributes para dar a los guardias un comportamiento de patrulla realista. Asegúrate de configurar su grupo de relación para que sean hostiles con los reclusos, pero neutrales con la policía y los visitantes, evitando incidentes de fuego amigo durante las respuestas a fugas de prisión.
Control de límites y liberación
El sistema de límites mantiene a los reclusos dentro del perímetro de la prisión mediante una zona poligonal que cubre toda la instalación. Cuando un jugador con una condena activa sale de esta zona, recibe una notificación de advertencia y tiene 10 segundos para volver antes de ser teletransportado al punto de aparición de la prisión, con tiempo adicional añadido a su condena como penalización por intento de fuga. En el lado del cliente, ejecuta una comprobación periódica cada 2 segundos que compare la posición del jugador con el polígono del perímetro de la prisión. Para las liberaciones, el servidor ejecuta un bucle de temporizador que comprueba todas las condenas activas cada 30 segundos. Cuando vence una condena, el sistema actualiza el estado en la base de datos a liberado, borra los metadatos de encarcelamiento del jugador, restaura su inventario desde una copia de seguridad previa al encarcelamiento si implementaste una y lo teletransporta a la salida de la prisión con una notificación que le indica que está libre. Si el jugador está desconectado cuando vence su condena, la liberación se procesa en su próximo inicio de sesión, comprobando si hay condenas activas vencidas en el evento de carga del jugador. Incluye un comando para que la policía pueda liberar jugadores antes de tiempo en casos como encarcelamientos injustos o acuerdos de culpabilidad negociados mediante roleplay.
Integración con los sistemas policiales y legales
Tu sistema penitenciario debe integrarse estrechamente con el MDT policial y cualquier sistema judicial del servidor. Cuando encarcelen a un jugador, su historial delictivo en el MDT debe actualizarse automáticamente con los cargos, la duración de la condena y el agente que realizó el arresto. Si tu servidor tiene un sistema judicial, las condenas dictadas por un juez deben pasar por la misma función de encarcelamiento para garantizar la coherencia. Implementa un sistema de apelación de condenas que permita a los reclusos presentar una apelación desde la biblioteca de la prisión, lo que creará un ticket para que los jueces lo revisen y que podría llevar a una reducción de la condena o a una liberación anticipada. Lleva un registro de la reincidencia contando cuántas veces han encarcelado a cada ciudadano y usa estos datos para aplicar condenas más severas a los reincidentes mediante un sistema de multiplicadores. Exporta tu función de encarcelamiento para que otros recursos, como el sistema judicial, la detección automatizada de delitos o los comandos de administración, puedan enviar jugadores a prisión mediante el mismo proceso validado.