Objetivo: saber de dónde sale cada cosa que ves en el juego, y poder encontrarla en los archivos en menos de un minuto. Esta habilidad vale más que cualquier guía: cuando no sepas hacer algo, vas a buscar cómo lo hace el juego.
1. Dos mundos: Java y media/
Project Zomboid tiene dos capas:
- El motor, en Java (
projectzomboid.jar): render, física, zombis, red, guardado, el parser de scripts, la máquina virtual de Lua. Todo lo que tiene que ser rápido. - El contenido, en
media/: datos (scripts.txt, traducciones.json, texturas, modelos, sonidos) y lógica de juego en Lua (casi toda la interfaz, los menús contextuales, las acciones, el crafteo de alto nivel).
Regla práctica: si algo se ve en un menú o es una acción del jugador, casi seguro está en Lua y lo puedes leer y cambiar. Si es cómo se mueve un zombi o cómo se calcula el daño, está en Java: se lee (módulo 08) pero se cambia con más límites (módulo 09).
2. Qué hay en media/
| Carpeta | Contenido | Formato |
|---|---|---|
scripts/ | Ítems, recetas, modelos, vehículos, sonidos, entidades | .txt con sintaxis propia (módulo 02) |
lua/shared/ | Código que corre en cliente y servidor; traducciones en Translate/<IDIOMA>/ | .lua, .json |
lua/client/ | Interfaz, menús, input, todo lo que ve un jugador | .lua |
lua/server/ | Lógica del mundo: loot (Items/), comandos de servidor | .lua |
textures/ | Íconos (Item_<Nombre>.png) y texturas de modelos | .png |
models_X/ | Modelos 3D | .fbx, .x |
clothing/ | Definición de prendas | .xml |
fileGuidTable.xml | Tabla que une los GUID de la ropa con su archivo | .xml |
sound/, ui/, maps/, anims_X/, AnimSets/ | Sonidos, imágenes de UI, mapas, animaciones | varios |
Además, muchas texturas del juego base vienen empaquetadas en .pack (atlas): por eso no encuentras Item_Apple.png suelto. Lo vemos en el módulo 04.
3. Cómo se juntan los mods con el juego
El juego arma un sistema de archivos virtual: primero media/ del juego y encima la carpeta media/ de cada mod activo, en el orden de carga.
Consecuencias que tienes que tener claras:
- Mismo camino = reemplazo. Si tu mod trae
media/lua/client/ISUI/ISInventoryPaneContextMenu.lua, reemplaza el archivo del juego entero. Funciona, pero rompe con cada parche y con cualquier otro mod que haga lo mismo. Casi nunca es la respuesta: en el módulo 09 vemos cómo parchear funciones sin reemplazar archivos. - Nombres únicos. Pon el nombre del mod en tus archivos (
CursoLab_Core.lua, noCore.lua) para no pisar archivos de otros mods sin querer. - Los scripts
.txtse suman, no se reemplazan por archivo. Todos los.txtdescripts/(del juego y de los mods) se leen y se juntan pormodule. Si dos definen el mismoitem, gana el último en cargar.
4. Orden de carga del Lua
Dentro de cada mod y del juego:
shared/, despuésclient/, despuésserver/(en una partida local se cargan los tres; en un servidor dedicado el servidor no cargaclient/, y los clientes no ejecutanserver/).- Dentro de cada carpeta, por orden alfabético de la ruta. Por eso el juego tiene archivos llamados
a_requires.lua: para que carguen primero. require "Carpeta/Archivo"fuerza a cargar otro archivo antes (sin.lua, relativo ashared/,client/oserver/). Se ejecuta una sola vez aunque lo pidas muchas.
Todo esto pasa al arrancar el juego, no al cargar la partida. Por eso un print suelto en tu archivo sale en el menú principal. Lo que dependa del mundo (el jugador, el mapa) tiene que esperar a un evento.
La línea de tiempo (eventos principales)
arranque del juego
├─ se cargan los .lua (shared → client → server)
├─ OnGameBoot ← el juego terminó de cargar el Lua
└─ OnMainMenuEnter ← estás en el menú
cargar una partida
├─ OnInitGlobalModData ← los datos guardados de los mods están disponibles
├─ OnPreDistributionMerge / OnDistributionMerge / OnPostDistributionMerge ← loot
├─ OnInitWorld, OnLoad
├─ OnCreatePlayer ← existe el jugador (uno por jugador local)
├─ OnGameStart ← la partida empezó
└─ OnTick, OnPlayerUpdate, EveryOneMinute, EveryTenMinutes... ← mientras juegasLa lista completa de eventos (252 en B42) está en la referencia del sitio: wenopal.work/referencia/eventos.html, con ejemplos de dónde los usa el juego.
5. Nombres completos e identificadores
- Un ítem se llama por módulo + nombre:
Base.Apple,CompleteItaliano.CompletoItaliano(Tocomples Food, uno de nuestros mods, usa el móduloCompleteItaliano). - Su nombre visible no está en el script sino en la traducción:
Translate/ES/ItemName.json→"Base.Apple": "Manzana". - Los textos de la interfaz son claves (
ContextMenu_Eat,IGUI_...,Tooltip_...) que se resuelven congetText("Clave").
Con esto, todo texto que ves en el juego se puede rastrear hacia atrás: texto → clave en un .json → getText("clave") en un .lua → la función que lo muestra.
Ejercicios
Para todos usa rg (ripgrep) o la búsqueda de VS Code sobre ~/Zomboid/Workshop/_juego/.
Ejercicio 1.1 · Rastrear un ítem
Para la lupa (Loupe), encuentra:
- el archivo y la línea donde se define el ítem,
- su nombre en español y portugués,
- todos los archivos
.luaque hacen algo con lo que la lupa es (pista: mira susTags).
Listo cuando: puedes explicar en una frase para qué sirve la lupa según el código, no según una wiki.
Respuesta
scripts/generated/items/normal.txt,item Loupe, conTags = base:hasmetal;base:magnifier.Translate/ES/ItemName.jsonyTranslate/PTBR/ItemName.json, claveBase.Loupe.rg -n "MAGNIFIER" lua/→shared/TimedActions/ISResearchRecipe.lua(investigar recetas es 10 % más rápido con una lupa en la mano) yclient/Farming/ISUI/ISFarmingMenu.lua+ISFarmingInfo.lua(suma 2 niveles de agricultura para ver plagas). En Lua el tag aparece comoItemTag.MAGNIFIER, no como"base:magnifier": es un enum que viene de Java.
Ejercicio 1.2 · Rastrear un texto de menú
Haz clic derecho sobre una comida en el inventario: aparece "Comer" (o Eat). Encuentra:
- la clave de traducción de ese texto,
- el archivo Lua y la función que agrega esa opción al menú,
- la acción (
ISxxxAction) que se ejecuta al elegirla.
Listo cuando: tienes las tres respuestas con ruta y número de línea.
Ejercicio 1.3 · Orden de carga en la práctica
En CursoLab, crea tres archivos con un CursoLab.log(...) cada uno: shared/CursoLab_A.lua, client/CursoLab_B.lua, server/CursoLab_C.lua. Agrega Events.OnGameBoot.Add(...) en uno de ellos.
- Antes de abrir el juego, escribe en qué orden crees que van a salir los mensajes.
- Ábrelo y compara con
console.txt. - Renombra
CursoLab_A.luaaZZ_CursoLab_A.lua. ¿Cambia algo? ¿Por qué no rompe nada queclient/useCursoLab.logsiCursoLab_Core.luaestá enshared/?
Ejercicio 1.4 · Reemplazar un archivo (y ver por qué no se hace)
- Copia
_juego/lua/client/ISUI/ISInventoryPaneContextMenu.luaa CursoLab (misma ruta) y cambia una línea para que el texto "Comer" sea "Comer ya". - Comprueba que funciona.
- Ahora borra la copia y escribe en tus notas: ¿qué pasaría con este mod después del próximo parche del juego? ¿Y si otro mod hace lo mismo con el mismo archivo?
El módulo 09 resuelve esto bien.
Desafío 1
Escribe un script (Python o shell) pzfind que reciba un texto visible del juego en español (por ejemplo "Investigar") y muestre: la clave de traducción, los .lua que usan esa clave con número de línea, y la función donde aparece cada uso. Lo vas a usar en todo el curso.
Anterior: 00 · Entorno · Siguiente: 02 · Scripts: ítems