Objetivo: entender cómo un objeto Java llega a Lua, qué puedes hacer con él y qué no, y cómo averiguar la API real del motor sin depender de wikis. Este es el módulo que más te va a servir cuando "no hay documentación".
cp ~/Library/Application\ Support/Steam/steamapps/common/ProjectZomboid/Project\ Zomboid.app/Contents/Java/projectzomboid.jar ~/Zomboid/Workshop/_juego/1. El modelo mental
┌──────────── JVM (projectzomboid.jar) ────────────┐
│ IsoPlayer, IsoZombie, InventoryItem, ... │
│ LuaManager ── expone clases y funciones ──┐ │
│ ▼ │
│ Kahlua (intérprete Lua escrito en Java) │
│ tu Lua ── llama métodos por reflexión ──► │
└────────────────────────────────────────────────────┘- Kahlua es Java. Un objeto Java no se "copia" a Lua: Lua recibe una referencia y Kahlua traduce cada llamada
obj:metodo(args)a una llamada Java por reflexión. - Solo es visible lo que el motor expone. Lo hace
zombie.Lua.LuaManager$Exposer: su métodoexposeAll()llama asetExposed(Clase)unas 1.000 veces, una por cada clase que Lua puede usar. Lo que no está en esa lista no existe para Lua, aunque esté en el.jar(por ejemploGameClientyGameServer, las clases de red, no están). - Las funciones globales (
getPlayer,getText,instanceof...) son métodos estáticos dezombie.Lua.LuaManager$GlobalObjectmarcados con la anotación@LuaMethod(name = "...", global = true). Hay 759.
2. Lo que puedes hacer con un objeto Java desde Lua
| Quieres… | Se escribe | Ejemplo real del juego |
|---|---|---|
| Llamar un método | obj:metodo(args) (con dos puntos) | getPlayer():getInventory() |
| Crear un objeto | Clase.new(args) | ArrayList.new() (53 usos), IsoThumpable.new(...) (38), ColorInfo.new() (100) |
| Llamar un método estático | Clase.metodo(args) | ScriptManager.instance:getItem("Base.Apple") (campo estático instance + método) |
| Usar un enum | Enum.VALOR | ItemTag.MAGNIFIER, CharacterTrait.ILLITERATE, Keyboard.KEY_F11 |
| Saber la clase | instanceof(obj, "IsoZombie") | 221 usos. En Java es GlobalObject.instof: busca la clase expuesta con ese nombre y usa Class.isInstance, así que también responde true con una clase padre (instanceof(zombi, "IsoGameCharacter")) |
| Guardar datos propios en el objeto | obj:getModData() → tabla Lua | módulo 14 |
Y lo que no puedes:
- Leer o escribir campos (
obj.x): Kahlua solo expone métodos. Si un dato no tiene getter, no se lee desde Lua. La única puerta es la reflexión que usa el visor de objetos (getNumClassFields,getClassField,getClassFieldVal), y está cerrada fuera de debug:getClassFieldValllama primero aLuaManager.validateReflectionAccess, que lanzaIllegalStateException("Not in debug")si el juego no está en modo debug. Sirve para inspeccionar, nunca para un mod publicado. - Agregar campos a un objeto Java (
getPlayer().miCosa = 1da error): para eso estágetModData(). - Heredar de una clase Java desde Lua. Lo que el juego deja extender (UI, acciones) está escrito en Lua justamente por eso.
3. Traducción de tipos
| Java | En Lua | Ojo con |
|---|---|---|
int, float, double, long | number (double) | Al llamar un método que pide int, el convertidor (zombie.Lua.KahluaNumberConverter) usa Double.intValue(): 3.7 llega como 3 y -3.7 como -3 (trunca, no redondea) |
boolean | boolean | |
String | string | |
null | nil | Un método que devuelve null te da nil: revisa antes de encadenar (getPlayer():getVehicle():getSpeed() revienta si no estás en un auto) |
ArrayList, List | objeto Java | Índices desde 0: list:get(0); tamaño list:size() (1.206 usos) |
KahluaTable | table | Es la tabla Lua vista desde Java: así llegan las tablas que pasas a métodos que las aceptan |
El bucle que vas a escribir mil veces
local items = getPlayer():getInventory():getItems() -- ArrayList de Java
for i = 0, items:size() - 1 do -- desde 0 hasta size-1
local it = items:get(i)
print(it:getFullType())
endEl juego tiene 565 :get(i) y 328 :get(i-1): los segundos recorren for i = 1, list:size() y restan 1. Ambos están bien; mezclar los dos es el bug.
Sobrecarga de métodos
Java permite varios métodos con el mismo nombre y distintos parámetros (AddItem(String), AddItem(InventoryItem)). Kahlua elige según la cantidad y el tipo de los argumentos que pasas. Si llamas con un tipo que no calza con ninguno, el error es confuso ("no such method" aunque el método exista): revisa los tipos.
4. Cómo averiguar la API real (el método)
De más rápido a más profundo:
- ¿Lo usa el Lua del juego?
rg -n "getVehicle\(\)" _juego/lua— si el juego lo usa, existe y ves cómo se llama. Es la fuente más rápida y la más confiable para la forma de uso. - ¿Qué tiene este objeto? En la consola, con el inspector del ejercicio 6 o el visor de objetos de debug (
client/DebugUIs/ObjectViewer.lua), mira un objeto vivo. - ¿Qué métodos tiene la clase?
javapsobre la clase en el.jar(sección 5). Da la lista exacta de métodos públicos con sus tipos. - ¿Qué hace el método? Decompilarlo (módulo 08).
En nuestros mods usamos este mismo método, con una regla que conviene adoptar: cuando algo se contradice, manda lo que dice el juego instalado.
5. javap en tres comandos
mkdir -p /tmp/pzjar && cd /tmp/pzjar && unzip -oq ~/Zomboid/Workshop/_juego/projectzomboid.jar
javap -cp /tmp/pzjar -public zombie.characters.IsoPlayer | less # métodos públicos y firmas
javap -cp /tmp/pzjar -p -c zombie.scripting.objects.Item \
| awk '/public void DoParam\(java.lang.String, java.lang.String\)/,/^$/' \
| grep -oE '// String [A-Za-z]+' | sort -u # las claves que acepta el parser de ítemsEl segundo truco es oro: el parser de cada tipo de script es una clase Java, y sus cadenas literales son exactamente las claves que reconoce. En Build 42 (versión de octubre de 2026), cada línea Clave = valor de un ítem pasa por Item.DoParam(String, String), que tiene 407 cadenas distintas y compara con equalsIgnoreCase 440 veces: por eso las claves de los ítems no distinguen mayúsculas. (En una versión anterior contamos 404 en Item.Load: el código cambia entre parches, y por eso se verifica en el .jar que tienes.)
Ejercicios
Ejercicio 7.1 · Encadenar sin reventar
Escribe CursoLab.velocidad() que devuelva la velocidad del vehículo del jugador o 0 si va a pie, sin que nunca lance error. Pruébala a pie, de copiloto y manejando.
Ejercicio 7.2 · Recorrer colecciones de Java
Escribe CursoLab.inventarioPorCategoria() que devuelva una tabla Lua { Food = 3, Tool = 5, ... } con el inventario del jugador, incluyendo lo que va dentro de mochilas (pista: un ítem contenedor tiene su propio getInventory(); busca cómo recorre el juego inventarios anidados en luautils.countItemsRecursive).
Listo cuando: el conteo coincide con lo que ves en la interfaz, con y sin mochila.
Ejercicio 7.3 · Leer la API con javap
Con el .jar descomprimido:
- Lista los métodos públicos de
IsoPlayerque empiezan congety devuelvenboolean. ¿Cuántos son? - Busca un método de
IsoGameCharacterque diga si el personaje está en el interior. Pruébalo en la consola. - Saca la lista de claves que acepta
Item.DoParamy compárala con las claves que usaste en el módulo 02. ¿Hay alguna que usaste y no esté? (Si la hay, el juego la ignora en silencio.)
Ejercicio 7.4 · ¿Qué está expuesto?
Encuentra en el .jar la clase que registra las clases visibles para Lua (pista: está en el paquete zombie.Lua; busca setExposed o exposeAll).
- Saca la lista completa de clases expuestas a
notas/expuestas.txt(pista: enexposeAll, cadaldc ... // classseguido desetExposed). ¿EstáIsoZombie? ¿YVehicleManager? Comprueba con la lista queGameClientno está. - Elige una clase no expuesta y comprueba en la consola que Lua no la ve.
Listo cuando: puedes responder con evidencia "¿puedo usar la clase X desde Lua?" para cualquier X.
Ejercicio 7.5 · Las funciones globales
Las funciones como getPlayer() están en una clase de zombie.Lua marcadas con una anotación. Encuéntrala, saca la lista completa de funciones globales y guárdala en notas/globales.txt. Marca 10 que no conocías y prueba 3.
Desafío 7
Escribe pzapi.py: recibe un nombre de clase (IsoPlayer) y genera un archivo de tipos para el Lua Language Server (anotaciones ---@class y ---@field/---@return) a partir de javap. Ponlo en .vscode y comprueba que VS Code autocompleta getPlayer(): con métodos reales.
Anterior: 06 · Lua en PZ · Siguiente: 08 · Leer el código de Java