Sistema de robos y atracos para FiveM: bancos, tiendas y bóvedas
Crea robos y atracos con buenas recompensas en FiveM. Fleeca, Paleto, joyerías, policías y los mejores scripts de atracos para servidores de rol realista.
Agency Scripts
Fundador y desarrollador principal en Agency Scripts
Resumen de la arquitectura del sistema de robos
Los robos y atracos son algunos de los ciclos de juego más emocionantes de cualquier servidor de rol de FiveM. Reúnen a criminales que planean una operación compleja, policías que responden con precisión táctica, negociadores de rehenes que gestionan situaciones tensas y civiles atrapados en el fuego cruzado. Un sistema de robos bien diseñado va mucho más allá de un simple aviso de interacción que entrega dinero a los jugadores. Requiere una arquitectura por capas que gestione ubicaciones, comprobaciones de requisitos previos, fases progresivas de minijuegos, distribución del botín, integración con el sistema de despacho policial, gestión de tiempos de espera y protecciones contra exploits. El sistema debería construirse con tres capas principales: una capa de configuración que define cada ubicación de robo con sus requisitos y recompensas, una máquina de estados del servidor que hace un seguimiento de los robos activos y aplica todas las reglas, y una capa de presentación del cliente que muestra minijuegos, animaciones e interfaces NUI. Todas las decisiones críticas deben tomarse en el servidor, ya que la lógica del cliente se puede explotar con mucha facilidad.
Configuración de ubicaciones y niveles de robo
Tu sistema de robos debería admitir varios tipos de ubicaciones con niveles de dificultad crecientes. Las tiendas de conveniencia representan el nivel inicial, con requisitos mínimos de presencia policial y herramientas básicas. Las sucursales bancarias Fleeca ocupan un nivel intermedio, con capas de seguridad moderadas y mayores requisitos policiales. La cámara acorazada de Pacific Standard es el atraco de máximo nivel y exige un equipo completo, equipamiento avanzado y una presencia policial considerable en línea. Cada ubicación necesita una entrada de configuración que defina sus coordenadas, objetos necesarios, número mínimo de policías, duración del tiempo de espera, rango de recompensas y secuencia de capas de seguridad que hay que superar:
Config.Robberies = {
['store_1'] = {
label = 'LTD Gasoline - Davis',
type = 'store',
coords = vector3(-47.02, -1757.23, 29.42),
register = vector3(-43.43, -1748.60, 29.42),
tier = 1,
cooldown = 1800, -- 30 minutes
minPolice = 2,
requiredItems = {'weapon_pistol'},
reward = {min = 2500, max = 6000, type = 'cash'},
securityLayers = {
{type = 'intimidate', time = 30},
{type = 'grab_cash', time = 15},
},
},
['fleeca_legion'] = {
label = 'Fleeca Bank - Legion Square',
type = 'bank',
coords = vector3(149.73, -1042.65, 29.37),
vault = vector3(144.87, -1044.16, 29.37),
tier = 2,
cooldown = 7200, -- 2 hours
minPolice = 4,
requiredItems = {'electronickit', 'thermite'},
reward = {min = 45000, max = 90000, type = 'markedbills'},
securityLayers = {
{type = 'hack', difficulty = 'medium', time = 25},
{type = 'thermite', time = 10},
{type = 'drill', time = 40},
},
},
['pacific_standard'] = {
label = 'Pacific Standard Bank',
type = 'vault',
coords = vector3(255.85, 225.60, 101.88),
vault = vector3(262.20, 222.10, 101.68),
tier = 3,
cooldown = 14400, -- 4 hours
minPolice = 6,
requiredItems = {'electronickit', 'thermite', 'advancedlaptop', 'duffelbag'},
reward = {min = 250000, max = 500000, type = 'markedbills'},
securityLayers = {
{type = 'hack', difficulty = 'hard', time = 20},
{type = 'hack', difficulty = 'hard', time = 20},
{type = 'thermite', time = 8},
{type = 'drill', time = 60},
{type = 'hack', difficulty = 'expert', time = 15},
},
},
}
Cada nivel debería aumentar no solo las recompensas, sino también la complejidad y el riesgo. Los robos a tiendas son acciones rápidas que duran menos de dos minutos, mientras que el atraco a Pacific Standard es una operación de varias fases que puede tardar quince minutos o más en completarse, lo que da a la policía tiempo de sobra para establecer un perímetro.
Máquina de estados del servidor
La máquina de estados del robo es el núcleo del sistema. Hace un seguimiento del ciclo de vida de cada ubicación de robo mediante los estados: inactivo, en curso, en tiempo de espera y bloqueado. Cuando un jugador inicia un robo, el servidor valida todos los requisitos previos antes de cambiar la ubicación de inactiva a en curso. Durante el estado en curso, el servidor registra en qué capa de seguridad se encuentran los criminales, qué jugadores participan y cuánto tiempo ha transcurrido. Si todos los jugadores abandonan la zona o son arrestados, el robo fracasa automáticamente y entra en un tiempo de espera reducido. Al completarse, la ubicación entra en el estado de tiempo de espera durante el período configurado antes de volver a estar disponible:
local RobberyState = {}
function InitializeRobbery(robberyId)
RobberyState[robberyId] = {
status = 'idle', -- idle | active | cooldown | locked
participants = {},
currentLayer = 0,
startedAt = 0,
cooldownUntil = 0,
}
end
RegisterNetEvent('robbery:server:startRobbery', function(robberyId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local config = Config.Robberies[robberyId]
local state = RobberyState[robberyId]
if not config or not state then return end
-- Validate state
if state.status ~= 'idle' then
TriggerClientEvent('QBCore:Notify', src, 'This location is not available', 'error')
return
end
-- Check cooldown
if os.time() < state.cooldownUntil then
TriggerClientEvent('QBCore:Notify', src, 'Location on cooldown', 'error')
return
end
-- Check minimum police online
local policeCount = GetOnlinePolicCount()
if policeCount < config.minPolice then
TriggerClientEvent('QBCore:Notify', src, 'Not enough police online', 'error')
return
end
-- Check required items
for _, item in ipairs(config.requiredItems) do
if not Player.Functions.GetItemByName(item) then
TriggerClientEvent('QBCore:Notify', src, 'Missing required equipment', 'error')
return
end
end
-- Start robbery
state.status = 'active'
state.participants = {src}
state.currentLayer = 1
state.startedAt = os.time()
-- Alert police dispatch
TriggerEvent('dispatch:server:notify', {
type = 'robbery',
coords = config.coords,
message = 'Silent alarm triggered at ' .. config.label,
tier = config.tier,
})
-- Send first security layer to client
TriggerClientEvent('robbery:client:startLayer', src, robberyId, config.securityLayers[1])
end)
La máquina de estados también debería gestionar casos límite, como los reinicios del servidor durante robos activos. Al iniciar el servidor, restablece todos los estados de robo a inactivo y elimina a los participantes activos. Guarda las marcas de tiempo de los tiempos de espera en una tabla persistente para que se conserven después de los reinicios.
Integración de minijuegos
Los minijuegos convierten los robos de una experiencia pasiva de espera en un desafío activo que pone a prueba las habilidades. Cada tipo de capa de seguridad debería corresponder a un estilo de minijuego distinto que ponga a prueba diferentes capacidades de los jugadores. Los minijuegos de hackeo pueden usar rompecabezas de memoria, desafíos para conectar circuitos o secuencias numéricas rápidas. Colocar termita requiere precisión para poner las cargas antes de que se apaguen. Las mecánicas de perforación ponen a prueba el control estable del ratón mientras el jugador guía una broca por un mecanismo de cerradura sin sobrecalentarla. El principio clave de diseño es que los niveles más difíciles deberían usar variantes más complejas de estos minijuegos, con ventanas de tiempo más ajustadas, más elementos que controlar o mayor velocidad:
-- Client-side minigame dispatcher
function StartMinigame(layerType, difficulty, callback)
if layerType == 'hack' then
local config = HackDifficulty[difficulty]
exports['ps-ui']:Circle(function(success)
callback(success)
end, config.circles, config.speed)
elseif layerType == 'thermite' then
exports['ps-ui']:Thermite(function(success)
callback(success)
end, 10, 5, 3)
elseif layerType == 'drill' then
exports['drilling']:StartDrill(function(success)
callback(success)
end)
elseif layerType == 'intimidate' then
-- Store clerk intimidation: aim weapon at NPC
StartClerkIntimidation(function(success)
callback(success)
end)
end
end
RegisterNetEvent('robbery:client:startLayer', function(robberyId, layer)
local ped = PlayerPedId()
-- Play appropriate animation
if layer.type == 'hack' then
PlayHackingAnimation(ped)
elseif layer.type == 'drill' then
PlayDrillingAnimation(ped)
end
StartMinigame(layer.type, layer.difficulty, function(success)
ClearPedTasks(ped)
TriggerServerEvent('robbery:server:layerResult', robberyId, success)
end)
end)
Los intentos fallidos en los minijuegos deberían tener consecuencias. Un hackeo fallido podría activar alarmas adicionales, reducir el tiempo restante o bloquear al jugador durante un breve período de penalización. En los atracos de alto nivel, fallar el hackeo final podría bloquear la cámara acorazada por completo y obligar al equipo a abandonar el robo con solo una parte del botín. Estos estados de fracaso añaden tensión y hacen que completar el atraco con éxito resulte realmente gratificante.
Integración con el despacho y la respuesta policial
Un sistema de robos solo está completo a medias sin integración policial. Cuando comienza un robo, el sistema de despacho debería enviar alertas de distintos niveles según la categoría del robo. Un robo a una tienda de conveniencia genera una notificación de alarma silenciosa con la ubicación de la tienda. Un atraco a una sucursal de Fleeca activa una alarma bancaria con el nombre de la sucursal y un nivel de respuesta recomendado. El atraco a Pacific Standard activa una alerta de emergencia general que recomienda la intervención de todas las unidades disponibles. Ofrece a la policía actualizaciones en tiempo real a medida que avanza el robo: cuando los criminales superan una nueva capa de seguridad, cuando se disparan armas dentro del edificio o cuando los criminales intentan huir. Estas actualizaciones ayudan a la policía a coordinar su respuesta y crean situaciones dinámicas en las que los agentes pueden decidir si irrumpen de inmediato o establecen un perímetro y negocian. Implementa una mecánica de rehenes que permita a los criminales tomar como rehenes a los empleados NPC del banco. Así, los negociadores policiales tienen un papel y los criminales consiguen una ventaja para retrasar la respuesta policial. El sistema de rehenes debería registrar cuántos hay retenidos, si alguno ha sufrido daños y ofrecer opciones de negociación mediante llamadas telefónicas entre los criminales y los agentes que responden.
Distribución del botín y blanqueo de dinero
Lo que reciben los jugadores tras un robo importa tanto como el propio robo. En lugar de depositar directamente dinero limpio en cuentas bancarias, recompensa a los criminales con billetes marcados o bienes robados que requieren pasos adicionales para convertirse en dinero utilizable. Este diseño prolonga el ciclo de juego criminal más allá del atraco y crea sistemas interconectados. Los billetes marcados se pueden blanquear en ubicaciones específicas del mapa a cambio de una comisión porcentual, lo que genera decisiones sobre riesgos y recompensas acerca de dónde y cuándo blanquear. Las joyas o los aparatos electrónicos robados deben venderse a través de compradores NPC que ofrecen tarifas variables según la rareza del objeto y las condiciones actuales del mercado. Implementa un sistema de bolsas de botín en el que los jugadores deban llevar físicamente el botín, lo que limita su velocidad de movimiento e impide que usen armas. Esto añade consideraciones tácticas a la fase de escape:
RegisterNetEvent('robbery:server:distributeLoot', function(robberyId)
local src = source
local config = Config.Robberies[robberyId]
local state = RobberyState[robberyId]
if not config or not state then return end
if state.status ~= 'active' then return end
local rewardAmount = math.random(config.reward.min, config.reward.max)
local participants = state.participants
local perPlayer = math.floor(rewardAmount / #participants)
for _, playerId in ipairs(participants) do
local Player = QBCore.Functions.GetPlayer(playerId)
if Player then
if config.reward.type == 'markedbills' then
local billStacks = math.ceil(perPlayer / 5000)
for i = 1, billStacks do
local amount = math.min(5000, perPlayer - ((i - 1) * 5000))
Player.Functions.AddItem('markedbills', 1, nil, {
worth = amount,
source = config.label,
})
end
elseif config.reward.type == 'cash' then
Player.Functions.AddMoney('cash', perPlayer, 'robbery-' .. robberyId)
end
TriggerClientEvent('inventory:client:ItemBox', playerId, QBCore.Shared.Items['markedbills'], 'add')
end
end
-- Set cooldown
state.status = 'cooldown'
state.cooldownUntil = os.time() + config.cooldown
state.participants = {}
-- Log the robbery for admin review
LogRobbery(robberyId, participants, rewardAmount)
end)
Protección contra exploits y gestión de tiempos de espera
Los sistemas de robos atraen a quienes buscan exploits porque generan riqueza. Implementa varias capas de protección contra exploits comunes. Las comprobaciones de distancia del servidor garantizan que los jugadores estén físicamente en la ubicación del robo durante todas las fases. Los límites de frecuencia impiden que los jugadores envíen eventos de inicio de robo repetidamente. El seguimiento de participantes garantiza que solo los participantes registrados puedan avanzar por las capas de seguridad o recibir botín. Los tiempos de espera globales evitan el spam de robos en todo el servidor al limitar cuántos pueden estar activos simultáneamente. Los tiempos de espera por jugador impiden que una sola persona robe varias ubicaciones seguidas. Registra cada intento de robo con marcas de tiempo, identificadores de participantes, estado de éxito o fracaso y la recompensa distribuida para que los administradores puedan investigar patrones sospechosos. Vigila los tiempos de finalización imposibles, en los que un jugador supera todas las capas de seguridad más rápido de lo que sería físicamente posible. Implementa un sistema de reputación en el que una actividad delictiva excesiva atraiga mayor atención policial y consecuencias más severas. Esto crea un límite natural que anima a los jugadores a espaciar sus actividades criminales.
Rutas de escape y eventos dinámicos
La fase de escape es cuando los robos se vuelven realmente impredecibles. Una vez asegurado el botín, los criminales deben escapar de la zona mientras cargan pesadas bolsas de lona que limitan su movimiento. Diseña el sistema para admitir varios métodos de escape: vehículos terrestres estacionados afuera, barcos en muelles cercanos para bancos junto al agua o extracciones en helicóptero desde azoteas para golpes de alto nivel. Añade eventos dinámicos durante la fase de escape, como bandas de clavos que la policía pueda desplegar, avisos por radio que revelen la descripción del vehículo sospechoso y controles de carretera aleatorios en intersecciones clave. El servidor debe registrar si los criminales logran salir de la zona del robo, definida mediante un radio configurable alrededor del banco, y marcar el robo como completado solo entonces. Si todos los participantes mueren o son arrestados antes de salir de la zona, la policía debe poder recuperar el botín como prueba. Esto genera una experiencia significativa después del robo para ambos bandos y garantiza que escapar sea tan desafiante y gratificante como el propio golpe.