El Libro·Capítulo 1/12·9 min

Cómo hacer scripts en FiveM

De cero a tu primer comando funcional: recursos, fxmanifest, Lua básico, el modelo cliente-servidor y los eventos de red que lo conectan todo.

Hacer scripts en FiveM es más sencillo de lo que parece desde fuera. Si entiendes cuatro ideas (qué es un recurso, cómo se declara, lo justo de Lua y quién manda entre cliente y servidor) ya puedes escribir cosas que funcionan de verdad. Vamos a montar tu primer script desde cero y, de paso, a fijar los conceptos que vas a repetir el resto de tu vida como dev de FiveM.

Todo es un recurso

En FiveM no escribes «un script suelto»: escribes un recurso. Un recurso es simplemente una carpeta dentro de resources/ que contiene un archivo especial llamado fxmanifest.lua. Ese manifiesto es el índice que le dice al servidor qué archivos cargar y en qué lado se ejecutan (cliente, servidor o ambos).

text
resources/
└── mi_primer_script/
    ├── fxmanifest.lua
    ├── config.lua
    ├── client.lua
    └── server.lua

Estructura típica de un recurso

El fxmanifest.lua, pieza por pieza

Si el manifiesto está mal, el recurso ni arranca. Por suerte es corto y casi siempre se parece a esto. Léelo con calma: cada línea tiene un porqué.

lua
fx_version 'cerulean'   -- versión del manifiesto (usa siempre 'cerulean')
game 'gta5'             -- el juego objetivo

author 'TuNombre'
description 'Mi primer recurso'
version '1.0.0'

-- Compartido: se carga en cliente Y servidor (ideal para config)
shared_scripts {
  'config.lua'
}

-- Cliente: corre en el PC de cada jugador. NO es de confianza.
client_scripts {
  'client.lua'
}

-- Servidor: corre en tu máquina. Es la autoridad.
server_scripts {
  'server.lua'
}

fxmanifest.lua

  • fx_version 'cerulean' y game 'gta5' son obligatorios: sin ellos el recurso no carga.
  • shared_scripts: se cargan en ambos lados. Perfecto para tu config.lua y datos comunes.
  • client_scripts: se ejecutan en el PC de cada jugador. Nunca confíes en lo que venga de aquí.
  • server_scripts: se ejecutan en tu servidor. Son la fuente de verdad y quien valida.

Para activar tu recurso añade ensure mi_primer_script en server.cfg. Para recargarlo sin reiniciar el servidor, escribe en consola: refresh y luego ensure mi_primer_script.

Lo justo de Lua

FiveM usa Lua. No necesitas dominarlo entero: con variables, funciones, tablas, condicionales y bucles vas sobrado para empezar. Y hay dos funciones que verás en casi todos los scripts: CreateThread (para procesos que se repiten) y Wait (para no quemar la CPU).

lua
-- Variables: usa SIEMPRE local
local nombre = 'Crxative'
local vida = 100

-- Funciones
local function curar(cantidad)
  vida = vida + cantidad
  return vida
end

-- Tablas (listas y diccionarios)
local armas = { 'pistola', 'rifle' }      -- los índices empiezan en 1
local jugador = { nombre = 'Ana', dinero = 500 }

-- Condicionales
if jugador.dinero >= 100 then
  print('puede pagar')
end

-- Un hilo que se repite cada segundo sin colgar el juego
CreateThread(function()
  while true do
    Wait(1000)         -- pausa 1000 ms (¡imprescindible!)
    print('sigo vivo')
  end
end)

Lua esencial para FiveM

Un bucle while true sin Wait CONGELA el cliente o el servidor. La regla de oro: todo bucle infinito lleva al menos un Wait dentro.

Cliente vs servidor: el modelo mental

Esta es la idea más importante de todo FiveM. El CLIENTE corre en el ordenador de cada jugador: dibuja la interfaz, lee teclas, muestra el mundo, sabe dónde está el jugador. Pero es manipulable: un tramposo puede modificarlo. El SERVIDOR corre en tu máquina, lo controlas tú, y es la única autoridad: aquí guardas el dinero, validas las acciones y decides qué es real.

  • El cliente PIDE; el servidor DECIDE.
  • Lógica visual y de input → cliente. Dinero, inventario, permisos, base de datos → servidor.
  • Regla sagrada: nunca confíes en datos que llegan del cliente. Valídalos siempre en el servidor.

Si dejas que el cliente diga «dame 1.000.000$», te lo van a dar a sí mismos. El servidor debe comprobar SIEMPRE: ¿este jugador puede hacer esto de verdad?

Eventos de red: cómo hablan los dos lados

Cliente y servidor no comparten variables: se comunican con eventos. Un lado dispara un evento, el otro lo escucha. Las piezas que necesitas conocer son pocas.

  • RegisterNetEvent('nombre'): registra un evento que puede venir por la red (necesario antes de escucharlo).
  • AddEventHandler('nombre', fn) o el handler inline: define qué hacer cuando llega el evento.
  • TriggerServerEvent('nombre', ...): el cliente envía un evento al servidor.
  • TriggerClientEvent('nombre', source, ...): el servidor envía un evento a un cliente concreto (source = id del jugador).

En un evento de servidor, la variable global source contiene el ID del jugador que lo disparó. Es tu única fuente fiable de QUIÉN está actuando: úsala, no te fíes de un id que mande el cliente dentro de los datos.

Tu primer script: el comando /saludo

Vamos a juntarlo todo. El cliente registra un comando /saludo; al usarlo, avisa al servidor. El servidor valida (aquí, algo simple) y responde al cliente, que muestra una notificación en pantalla. Fíjate cómo el flujo es siempre cliente → servidor → cliente.

lua
-- client.lua
RegisterCommand('saludo', function()
  -- El cliente solo PIDE: avisa al servidor de que quiere saludar
  TriggerServerEvent('miscript:saludar')
end, false)

-- Escuchamos la respuesta del servidor
RegisterNetEvent('miscript:respuesta')
AddEventHandler('miscript:respuesta', function(mensaje)
  -- Notificación nativa de GTA en pantalla
  BeginTextCommandThefeedPost('STRING')
  AddTextComponentSubstringPlayerName(mensaje)
  EndTextCommandThefeedPostTicker(false, true)
end)

client.lua

lua
-- server.lua
RegisterNetEvent('miscript:saludar')
AddEventHandler('miscript:saludar', function()
  local src = source           -- ID del jugador que disparó el evento

  -- El servidor VALIDA: ¿existe ese jugador de verdad?
  local nombre = GetPlayerName(src)
  if not nombre then return end

  print(('El jugador %s (id %d) ha usado /saludo'):format(nombre, src))

  -- Respondemos SOLO a ese cliente
  TriggerClientEvent('miscript:respuesta', src, 'Hola, ' .. nombre .. ' 👋')
end)

server.lua

Eso es todo: un comando real, con ida y vuelta por la red, donde el servidor manda. A partir de aquí, dar dinero, abrir un menú o guardar en base de datos es exactamente el mismo patrón, solo que con más lógica de validación en medio.

Buenas prácticas desde el día uno

  • Saca los valores ajustables (precios, coordenadas, textos) a config.lua. Nunca los hardcodees repartidos por el código.
  • Usa local en todas tus variables salvo que necesites una global a propósito.
  • Nombra tus eventos con prefijo de recurso ('miscript:saludar') para no chocar con otros.
  • Valida en el servidor todo lo que pueda dar ventaja a un jugador (dinero, ítems, permisos).
  • Lee la consola: los errores de Lua te dicen archivo y línea. Son tu mejor amigo.

Si entiendes este capítulo de verdad, ya sabes el 80% de cómo se construye cualquier recurso de FiveM. Lo demás es repetir este patrón con más oficio. Crea tu carpeta, copia el comando /saludo, pruébalo en tu servidor y haz que te salude por tu nombre: ese primer «funciona» engancha.

¿Una duda sobre esto? El chat de la IA lo sabe todo y te responde con código.

Pregunta a la IA
Cómo hacer scripts en FiveM (Lua, cliente-servidor)