7,62 Hard Life

7,62 Hard Life

7.62custom for HLA 1.0
MaxP  [开发者] 9 月 8 日 上午 6:40
Моддинг
Если кто захочет далее экспериментировать с моддингом через lua, делюсь тем, что удалось накопать из крупиц общедоступной информации (подразумевается, что читающий это человек хотя бы на базовом уровне знаком с каким-то из языков высокого уровня).

1. Специфика lua (для начинающих знакомство с ним):
- Для конкатенации строк используются две точки: ..
- Внимательно следите за регистром
- Единственная пользовательская структура данных lua по умолчанию - таблицы (table), соответственно, все операции с таблицами - итерацией по ним с помощью функции pairs или ipairs
- Будьте готовы писать свой инструментарий, так как имеющийся крайне ограничен, а на добавленные модом фичи никто не удосужился написать документацию
- Для доступа к большинству методов/атрибутов объектов используется иерархическая структура запросов, т.к. функции часто возвращают таблицу с функциями. Например, чтобы получить скорость пули из магазина оружия в руках выбранного персонажа:
lua print(GetCurrentMerc():GetHands():GetClip():GetBullet():GetInfo():GetSpecificInfo()["Speed"])
- Обратите внимание, что далеко не для всех объектов присутствует корректная текстовая интерпретация, так что проверяйте вывод и через print, и через Log.
- Логическая интерпретация значений "" или 0 в Lua - истина, имейте в виду :)

2. Доступные через lua обработчики событий:
- Player takes %s - срабатывает, когда игрок берёт (помещает в инвентарь) какой-то предмет передаваемого класса.
- Player loses %s - когда теряет предмет (то есть когда мы просто хватаем объект курсором)
- Player took on - срабатывает, когда любой юнит игрока надевает униформу (например, при загрузке игры все "надевают"). В качестве передаваемого параметра - класс униформы.
- Merc took on - то же самое, но когда другие персонажи надевают униформу
- Sector loaded - при загрузке секторе, без параметров
- Before sector unload - при выгрузке сектора (но НЕ срабатывает, когда отряд покидает сектор, получив маршрутную точку на карте - особенность игры в том, что какой-то сектор должен быть "предзагружен", и при покидании сектора он загружен до входа в другой сектор, а при загрузке на глобальную карту загружен случайный сектор). Без параметров.
- ShopOk - при закрытии магазина, без параметров.
- BattleEnd/BattleStart - тут всё понятно, так же без параметров.
- Car destroyed/Car destroyed finally - не проверял, присутствуют у VehManager
Вышеуказанные обработчики событий добавляются командой AddEventListener, например
AddEventListener("Player takes " .. "Gold_ore", foo)

Другие обработчики:
AddLoadGameHandler(foo)
AddNewGameHandler(foo)
AddSaveGameHandler(foo)

Ну и конечно OnItemContextMenu.

100%, что есть ещё - желающие могут поискать в exe. Признаки, очевидно - либо третья форма глагола либо в названии, либо признак действия, как с takes. Хотя название может быть почти любым - см. BattleEnd и ShopOk.

Кто-то из знакомых с похожим функционалом других приложений может возопить: "А какого %s в обработчик не передаются параметры?!".
Ну вот так сделали :) Там столько всего странного, что мне даже перечислять лень. Всё это, конечно, касается только вывода в lua - видимо, поддержка скриптовых команд делалась на остаточном принципе, а разработчики HLA, решая лишь свои конкретные задачи, ещё добавили сверху (ни в коем разе не хочу никого зацепить, они и так проделали огромную работу). Так что входящему сюда программисту нужно быть готовым к тому, что многие его [несомненно гениальные] идеи разобьются об острые камни острова "Ну вот так сделали".

Но почти 100%, что обработчика события стрельбы, либо чего-то похожего, в lua нет - в большинстве "возрастных" игр это де-факто стандарт, т.к. при неумелых действиях можно вызвать серьёзные "тормоза" при стрельбе. Что, естественным образом, делает невозможным доработку таких потенциально интересных механик, как износ глушителя при стрельбе сверхзвуковыми боеприпасами, повышенную вероятность заклинивания при набитом до предела магазине (см. магазины М-16) и т.д.

3. Интроспекция осуществляется с помощью функции dbg.
Например, посмотреть глобальные (!) функции/структуры данных можно следующей командой:
lua Log(dbg(_G)), должно получиться что-то типа этого:
https://gist.github.com/anonymous/9156685
Понятно, что это только вершина айсберга - многое может быть не-глобальным, либо просто методами объектов отдельных классов, либо самих классов.
Получить все методы в таблице можно следующим образом:
lua Log(dbg(GetCurrentMerc():GetHands():GetClip())) - в данном случае в лог запишется структура возвращаемой таблицы методов магазина для оружия в руках.
Узнать структуру тел функций невозможно, только имена. Так что в большинстве случаев Вам придётся угадывать, что принимает функция в качестве параметров.
Пример: Вы хотите добавить патроны в магазин, и знаете, что в методах магазина есть SetBullet, но не знаете, что в него передать.
Начните с поиска соответствующего getter'а - в данном случае это GetBullet, который возвращает userdata класса Item. Userdata это чаще всего C-структуры (в данном случае - игровой объект), отображаемые в lua, а доступные для lua методы/атрибуты хранятся в т.н. метатаблице - metatable.
Так что если интерпретатор ругается на то, что Вы пытаетесь в dbg передать userdata, а ожидалась table, сделайте следующее:
- сохраните объект в переменную (для удобства):
lua bullet = GetCurrentMerc():GetHands():GetClip():GetBullet()
- получите список доступных методов/атрибутов:
lua Log(dbg(getmetatable(bullet)))
Обратите внимание на разницу классов: GetHands(), как и GetClip(), возвращает объект ItemList, в то время как GetBullet() возвращает объект Item - очевидно, что ItemList может состоять состоит из нескольких элементов. Там есть разница в структуре, которую нужно учитывать, когда будете доступаться к методам/атрибутам, но я рекомендую изучить иерархию самостоятельно, т.к. эти наборы одинаковы для всех объектов одного мета-класса (Item либо ItemList).
Таким образом, возвращаясь к созданию патрона в магазине, мы пришли к тому, что нужно создать объект Item - это делается отдельной функцией CreateItem, которая принимает имя класса ammo.
IemList-объекты создаются методом персонажа AddItem, либо глобальной функцией AddItem (рекомендуется как более безопасный).


4. Добавленные полезные функции:

- in_string(text, pattern, lower=false) -> boolean - ищет совпадения подстроки в строке, используется строковый метод string.find.
Пример: in_string("The Earth without art is just Eh...", "ear", true) -> true

- multiple_find(text, pattern) -> boolean - проверяет на совпадение с text строк переданных в таблице pattern (игнорируя регистр), например:
multiple_find("desert camo uniform", {"MILITARY GARB","Desert camo uniform","Autodefensas uniform"}) -> true

- indexOf(array, value) -> int - ищет элемент в таблице, возвращая его индекс либо -1, если он не найден.

- tablelength(table) -> int - возвращает количество элементов в таблице. Использование для этой цели оператора #, как сделано в HLA - неправильно, возвращаемые результаты могут быть некорректными!

- list_iter(table) - итератор по элементам таблицы (НЕ по значениям), удобно для имитации одномерных структур данных.

Для примера, как выглядит итерация по стандартной таблице:
example_table = {
["element_1"] = true,
["element_2"] = false,
}
Итерация по элементам таблицы:
а.) С помощью pairs:
for element, value in pairs(example_table) do
print(string.format("Element: %s, value: %s",element,value))
end
б.) С помощью ipairs (почти аналог enumerate в питоне)
for index, element in ipairs(example_table) do
print(string.format("Index: %s, element: %s",index,element))
end

Но не всегда удобно задавать таблицы, порой проще итерировать одномерную структуру, типа {element1,element2}
По ней тоже можно проходиться тем же ipairs или даже pairs (в данном случае только не нужно создавать переменную со значением):
for element, _ in pairs({element1,element2}) do
..
end

Но проще с помощью list_iter:
for element in list_iter({element1,element2}) do
..
end
最后由 MaxP 编辑于; 9 月 9 日 上午 6:56