Граница Lua и Java: исследовать API Build 42
Граница между Lua и Java в Build 42
В руководстве объясняется модель объектов Java, доступных Lua, описываются поддерживаемые операции и преобразования, а также показано, как исследовать локальный API с помощью javap. Автор утверждает, что сведения о движке проверены на .jar Build 42 за октябрь 2026 года; эта проверка и точная версия в данной работе не повторялись.
Модель взаимодействия
На диаграмме статьи projectzomboid.jar работает в JVM; LuaManager открывает классы и функции интерпретатору Kahlua (Lua на Java), а вызовы методов Java из Lua проходят через reflection.
┌──────────── 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 не копирует объект Java в Lua: Lua получает ссылку, а каждый вызов obj:metodo(args) преобразуется в вызов Java через reflection.
- Из Lua доступны только классы, которые открыл движок. По статье, этим занимается zombie.Lua.LuaManager$Exposer: exposeAll() вызывает setExposed(Clase) примерно 1 000 раз — для каждого доступного Lua класса. Если класса нет в этом списке, он недоступен Lua, даже если есть в .jar. Примеры автора — сетевые классы GameClient и GameServer.
- Глобальные функции вроде getPlayer, getText и instanceof описаны как статические методы zombie.Lua.LuaManager$GlobalObject с аннотацией @LuaMethod(name = "...", global = true). В источнике указано 759 таких функций.
Что можно делать с объектом Java из Lua
| Операция | Запись | Пример и замечание источника |
|---|---|---|
| Вызвать метод | obj:metodo(args) (двоеточие) | getPlayer():getInventory() |
| Создать объект | Clase.new(args) | ArrayList.new() (53 применения), IsoThumpable.new(...) (38), ColorInfo.new() (100) |
| Вызвать статический метод | Clase.metodo(args) | ScriptManager.instance:getItem("Base.Apple"): статическое поле instance и метод |
| Использовать enum | Enum.VALOR | ItemTag.MAGNIFIER, CharacterTrait.ILLITERATE, Keyboard.KEY_F11 |
| Проверить класс | instanceof(obj, "IsoZombie") | Статья сообщает 221 применение. Функция Java GlobalObject.instof ищет открытый класс и вызывает Class.isInstance; поэтому подходит и родительский класс, например IsoGameCharacter. |
| Хранить пользовательские данные в объекте | obj:getModData() → таблица Lua | Модуль 14 курса. |
Ограничения, перечисленные в статье: Kahlua открывает методы, но не произвольные поля наподобие obj.x. По утверждению автора для рассматриваемой версии, без подходящих методов Lua не может читать или изменять произвольное поле объекта Java. Reflection, используемый инспектором объектов (getNumClassFields, getClassField, getClassFieldVal), по словам автора, закрыт вне debug-режима: getClassFieldVal сначала вызывает LuaManager.validateReflectionAccess, которое выбрасывает IllegalStateException("Not in debug"). Источник предупреждает, что это средство инспекции, не для опубликованного мода.
- Нельзя добавлять поля в объект Java из Lua; руководство предлагает использовать getModData().
- Нельзя наследовать Java-класс из Lua. Автор объясняет, что расширяемые части игры — интерфейс и действия — написаны на Lua.
Преобразование типов
| Java | В Lua по руководству | На что обратить внимание |
|---|---|---|
| int, float, double, long | number (double) | Для параметра int конвертер zombie.Lua.KahluaNumberConverter применяет Double.intValue(): 3.7 превращается в 3, а -3.7 в -3; значение усекается к нулю, а не округляется. |
| boolean | boolean | |
| String | string | |
| null | nil | Перед цепочкой вызовов проверяйте nil; getPlayer():getVehicle():getSpeed() завершится ошибкой, если персонаж не в машине. |
| ArrayList, List | Объект Java | Индексы начинаются с 0: list:get(0); размер — list:size(). В статье указано 1 206 вызовов size(). |
| KahluaTable | table | Представление таблицы Lua в Java; так приходят таблицы, переданные в методы, которые их принимают. |
Пример обхода Java ArrayList из источника сохранён буквально:
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())
end
Автор насчитал в игре 565 вызовов :get(i) и 328 вызовов :get(i-1). Второй вариант обходит индексы for i = 1, list:size() и вычитает 1. По статье, оба способа допустимы, но их смешивание приводит к ошибке.
В Java могут быть перегруженные методы с одним именем и разными параметрами, например AddItem(String) и AddItem(InventoryItem). Согласно руководству, Kahlua выбирает вариант по числу и типам аргументов. Несовпадение типов может дать непонятную ошибку «no such method», хотя такой метод существует.
Как выяснить реальный API
- Найти, как метод использует Lua игры. В статье приведён пример rg -n "getVehicle\(\)" _juego/lua.
- Посмотреть живой объект в консоли с помощью инспектора из предыдущего упражнения или debug-окна client/DebugUIs/ObjectViewer.lua.
- Проверить через javap публичные методы класса и их типы в .jar. Публичная сигнатура сама по себе не доказывает, что класс доступен Lua.
- Декомпилировать метод, чтобы понять его поведение (модуль 08).
Когда разные источники противоречат друг другу, автор предлагает исходить из установленной игры.
Использовать javap
Для javap нужен JDK: встроенной в игру Java недостаточно. Скопируйте projectzomboid.jar из собственной установки игры в _juego и обновляйте копию после обновления игры; приведённый путь — пример для macOS. Руководство предлагает распаковать .jar и запросить публичные методы IsoPlayer. Последняя команда исследует Item.DoParam, чтобы извлечь строки с ключами парсера предметов. Примеры команд сохранены без изменений и не запускались:
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 ítems
Пояснение автора: парсер каждого типа скрипта — класс Java, а его строковые константы показывают поддерживаемые ключи. Для Build 42 за октябрь 2026 автор утверждает, что каждая строка «Ключ = значение» предмета обрабатывается Item.DoParam(String, String), содержащим 407 различных строк и 440 сравнений equalsIgnoreCase; поэтому регистр ключей не важен. В более ранней версии автор насчитал 404 строки в Item.Load и отмечает, что код меняется между патчами. Эти числа не проверялись в данной работе.
Упражнения
Упражнение 7.1 · Безопасная цепочка вызовов
Напишите CursoLab.velocidad(), которая возвращает скорость автомобиля игрока или 0, если он идёт пешком, и никогда не вызывает ошибку. Проверьте пешком, на пассажирском месте и за рулём.
Упражнение 7.2 · Обход коллекций Java
Реализуйте CursoLab.inventarioPorCategoria(), возвращающую таблицу Lua вида { Food = 3, Tool = 5, ... } для инвентаря игрока, включая предметы внутри рюкзаков. Подсказка: у предмета-контейнера есть собственный getInventory(); ищите в luautils.countItemsRecursive обход вложенных инвентарей. Критерий — совпадение с интерфейсом с рюкзаком и без него.
Упражнение 7.3 · Изучить API через javap
- На распакованном .jar перечислите публичные методы IsoPlayer, которые начинаются с get и возвращают boolean; посчитайте их.
- Найдите в IsoGameCharacter метод, сообщающий, находится ли персонаж в помещении, и проверьте его в консоли.
- Извлеките ключи Item.DoParam и сравните с ключами модуля 02; найдите те, которые игра молча игнорирует.
Упражнение 7.4 · Что открыто Lua
Найдите в .jar класс zombie.Lua, который регистрирует видимые Lua классы; ищите setExposed или exposeAll. Сохраните полный список в notas/expuestas.txt, проверьте IsoZombie и VehicleManager, а также отсутствие GameClient. Затем выберите закрытый класс и проверьте, что Lua его не видит. Цель — обосновать по свидетельствам, доступен ли Lua конкретный класс.
Упражнение 7.5 · Глобальные функции
Найдите аннотацию, которой в классе zombie.Lua помечены функции вроде getPlayer(). Выгрузите список глобальных функций в notas/globales.txt, отметьте десять незнакомых и проверьте три.
Задача со звёздочкой 7
Напишите pzapi.py: скрипт получает имя класса, например IsoPlayer, и на основе javap создаёт файл типов Lua Language Server с аннотациями ---@class, ---@field и ---@return. Поместите его в .vscode и проверьте, дополняет ли VS Code методы getPlayer():.