Llevo unas semanas creando mods para Project Zomboid Build 42. Vengo de backend con PHP y Laravel, asi que meterme en Lua, modding de videojuegos y modelado 3D ha sido un cambio bastante radical. Estos son los aprendizajes mas utiles que me llevo despues de publicar tres mods y tener un cuarto en desarrollo.

La estructura de carpetas importa mas de lo que parece

Build 42 cambio la estructura de los mods respecto a versiones anteriores. Ahora cada mod vive dentro de una carpeta con este formato:

MiMod/
├── workshop.txt
└── Contents/
    └── mods/
        └── MiModId/
            ├── 42/
            │   ├── mod.info
            │   └── poster.png
            └── common/
                └── media/
                    ├── lua/
                    │   ├── client/
                    │   ├── server/
                    │   └── shared/
                    └── scripts/

La carpeta 42/ contiene los metadatos especificos de la build (el mod.info y el poster). La carpeta common/ tiene todo el codigo Lua, scripts de items, texturas y traducciones. Si no sigues esta estructura, el juego simplemente no carga el mod y no te dice por que.

Los symlinks para desarrollo local deben apuntar a ~/Zomboid/Workshop/, no a ~/Zomboid/mods/. La segunda ruta causa errores de resolucion de paths con symlinks.

Kahlua no es Lua 5.3

Project Zomboid usa Kahlua, una implementacion de Lua 5.1 sobre Java. Esto significa que muchas cosas que esperarias de un Lua moderno simplemente no existen. La que mas me pillo fue goto y las etiquetas ::label:: — Kahlua no las soporta. Donde en Lua 5.3 harias un goto continue dentro de un bucle, aqui toca reestructurar con if/else.

Tampoco hay operador ternario, ni operaciones bitwise nativas, ni table.unpack (se usa unpack directamente). Es Lua en su forma mas basica, y cuanto antes lo asumas, menos tiempo pierdes depurando errores cripticos.

Tres tipos de mods, tres problemas distintos

He trabajado en tres tipos de mods muy diferentes, y cada uno tiene sus propios retos:

La API de vehiculos es sorprendentemente flexible

Definir un vehiculo en PZ es un archivo .txt con un DSL propio. Se definen las fisicas (masa, potencia, relaciones de marcha), las ruedas, los asientos, las piezas mecanicas y las areas de interaccion. El sistema de templates permite heredar comportamiento estandar:

template = PassengerSeat4,
template = Door/part/DoorFrontLeft,
template = Tire,
template = Engine,
template = GasTank,

Y luego sobrescribes solo lo que necesitas. Para que un vehiculo aparezca en el mundo, necesitas un script de distribucion en server/ que registre el vehiculo en las zonas de spawn con VehicleDistributions.

VehicleSpawnManager: cuando el mod nace de la frustracion

Mientras probaba el Renault 5, necesitaba ver cuantos vehiculos de cada tipo habia en el mapa y poder controlar sus ratios de spawn. No encontre nada que lo hiciera bien, asi que hice VehicleSpawnManager: un panel in-game (tecla F8) que lista todos los vehiculos — vanilla y de mods —, muestra cuantos hay spawneados, y te deja poner limites o aplicar un multiplicador global.

Lo interesante tecnicamente es que el mod descubre las zonas de spawn y los vehiculos dinamicamente en runtime. No hardcodea nada. Lee VehicleZoneDistribution para obtener zonas, getScriptManager():getAllVehicleScripts() para listar vehiculos, y modifica los spawnChance en caliente. Todo se persiste con ModData y se aplica al cargar la partida.

Sandbox Options: configuracion sin codigo

PZ tiene un sistema de sandbox options que permite exponer configuracion al jugador desde el menu de creacion de partida. Solo necesitas un archivo sandbox-options.txt en common/media/ y acceder a los valores con SandboxVars.TuMod.TuVariable. Es la forma correcta de dar opciones al usuario sin que tenga que tocar archivos Lua.

Modelos 3D: la parte mas dificil no es el codigo

Para el Renault 5 Copa Turbo genere el modelo base con Tripo3D y despues pase horas en Blender separandolo en piezas. PZ necesita que cada parte del vehiculo sea un mesh independiente con un nombre especifico: r5ct_door_fl, r5ct_hood, r5ct_trunk, etc. Ademas, necesitas tres archivos FBX separados: Body (carroceria), Wheels (ruedas) y Objects (piezas sueltas cuando se desmontan).

Los origenes de cada pieza importan — las puertas deben tener el pivot en la bisagra para que la animacion de apertura funcione. Y las texturas siguen un sistema de capas: Shell (pintura), Mask (para skins de color), Rust, Damage, Blood y Shadow. Es mucho trabajo no-codigo que nadie te cuenta hasta que te metes.

Traducciones con Translate/

PZ carga automaticamente los archivos de traduccion desde shared/Translate/EN/IG_UI_EN.txt. El formato es simple — pares clave-valor, uno por linea. Luego en Lua usas getText("IGUI_MiClave") y el juego resuelve el idioma. Es un sistema limpio que vale la pena usar desde el principio aunque solo vayas a soportar ingles.


Resumen

Hacer mods para PZ B42 tiene una curva de entrada real: la documentacion es escasa, el motor usa un Lua limitado, y la estructura de carpetas es inflexible. Pero una vez entiendes las convenciones, el sistema es bastante potente. Puedes hookear casi cualquier evento del juego, manipular el mundo en runtime, crear UI complejas con ISPanel y hasta meter vehiculos completos con fisicas y mecanica.

Si vienes de desarrollo backend como yo, lo que mas te va a costar es la falta de tipos, la depuracion sin stack traces decentes y el workflow de "cambiar archivo, reiniciar juego, probar". Pero tambien te vas a sentir sorprendentemente comodo con la arquitectura: separacion client/server, persistencia con ModData, eventos y hooks — es mas familiar de lo que parece.