Los scripts .txt definen qué existe. El Lua define qué pasa: el loot, un menú nuevo con clic derecho, una acción con animación, una luz que sigue al jugador, un cañón de agua en un camión.
El Lua de Project Zomboid es Lua 5.1
El juego usa Kahlua, una versión de Lua 5.1 que corre dentro de Java. Si aprendiste Lua en otro lado, ojo con estas diferencias:
- No existe
gotoni etiquetas::algo::. Para saltar un paso dentro de unfor, usa unif. - No existe la división entera
//. Usamath.floor(a / b). - No existen
&,|,~,<<ni>>. - Las palabras reservadas (
end,function,repeat...) no pueden ser claves sueltas: escribet["end"], not.end. - Cuando llamas un método que no existe, el error es un críptico
tried to call nil, y aparece al cargar la partida, no al cargar el mod.
Cliente, servidor y shared
media/lua/
├── client/ <- solo en la PC del jugador: menús, interfaz, efectos visuales
├── server/ <- solo en el servidor: loot, reglas, lo que "manda"
└── shared/ <- en los dos: datos, utilidades y acciones con tiempoEn un jugador tu PC hace de cliente y de servidor a la vez, así que todo corre. La diferencia importa en multijugador, y es mejor pensar así desde el principio: el mod funciona en los dos modos sin cambios.
Un espacio de nombres por mod
Todos los mods comparten el mismo Lua global. Para no pisar a otros, mete todo lo tuyo en una sola tabla:
MiMod = MiMod or {}
function MiMod.saludar(player)
player:Say("Weno pa'l leseo")
endNunca dejes variables globales sueltas como contador = 0: tarde o temprano chocan con las de otro mod.
Eventos: cuándo se ejecuta tu código
Tu código se "cuelga" de eventos del juego:
Events.OnGameStart.Add(function()
print("[MiMod] la partida empezó")
end)La lista completa de B42 está en la referencia de eventos de Lua. Algunos que usamos en mods publicados:
| Evento | Para qué lo usamos |
|---|---|
OnPostDistributionMerge | Agregar loot (ver Loot) |
OnTick | Cosas continuas, como una luz que sigue al jugador. Limítalo: corre en cada frame. |
OnClientCommand | El servidor recibe pedidos de los jugadores |
OnSave | Limpiar cosas temporales antes de guardar |
OnPlayerDeath | Limpiar lo que tenía el jugador |
OnWeaponSwing | Reaccionar al golpe de un arma (la pistola de gravedad) |
Para que OnTick no pese, haz el trabajo cada cierto tiempo:
local ultimo = 0
Events.OnTick.Add(function()
local ahora = getTimestampMs()
if ahora - ultimo < 1000 then return end -- una vez por segundo
ultimo = ahora
-- trabajo pesado aquí
end)Multijugador: el cliente pide, el servidor decide
Si un jugador hace algo que cambia el mundo, el cliente no lo aplica directamente: le pide al servidor que lo haga. El servidor revisa que tenga sentido y lo aplica para todos. Así funciona el cañón de agua de Wanaco:
-- client/: el jugador aprieta el botón
sendClientCommand(player, "Wanaco", "toggleCannon", { on = true })-- server/: el servidor revisa y aplica
Events.OnClientCommand.Add(function(module, command, player, args)
if module ~= "Wanaco" or command ~= "toggleCannon" then return end
local vehicle = player:getVehicle()
if not vehicle then return end -- nunca confíes en el cliente
-- ... encender el cañón y avisar a todos
end)- El primer texto (
"Wanaco") es el canal de tu mod. - El servidor vuelve a validar todo. Un cliente modificado podría mandar cualquier cosa.
- Esto también funciona en un jugador, así que no necesitas dos versiones del código.
Acciones con tiempo (timed actions)
Las acciones con barra de progreso y animación (comer, construir, bailar) son timed actions. Se crean derivando de ISBaseTimedAction:
require "TimedActions/ISBaseTimedAction"
MiMod.BailarAction = ISBaseTimedAction:derive("MiModBailarAction")
function MiMod.BailarAction:isValid()
return self.character:getVehicle() == nil
end
function MiMod.BailarAction:start()
self:setActionAnim("MiBaile")
endTrampas de API que encontramos en B42
getCell():getVehicles()ahora es un Set, sin:get(i). Para recorrer objetos usagetCell():getObjectListForLua().- Las propiedades de tiles se revisan con
:has(IsoFlagType.water), en minúscula.:Is(...)no existe y datried to call nil. HaloTextHelper.addText(player, texto, color)no existe. UsaaddGoodText(verde),addBadText(rojo) oaddText(player, texto).