Начните с одной мысли#
Код мода - это инструкции и данные, которые X4 читает в подходящий момент. В моде нет обязательного единственного файла main, через который проходят все события. Данные корабля загружаются как ресурсы, MD ждёт условия, AI выполняет поведение, а Lua обрабатывает интерфейс.
Движок - исполняемая часть игры: он создаёт объекты, считает движение, хранит мир и выполняет доступные скриптам операции. Полные исходники движка не лежат в папке мода. Автор расширения работает через предоставленные форматы и API. API - набор доступных обращений к системе: например, прочитать имя объекта или послать событие.
Почему здесь несколько языков#
Разным задачам нужны разные способы описания. Прочность корпуса удобно хранить как число в документе. Реакцию на событие - как обработчик. Кнопку - как элемент окна с функцией нажатия.
| Слой | Где обычно находится | Что делает | Пример задачи |
|---|---|---|---|
| XML-данные | assets/, libraries/, t/ |
Описывают свойства и контент | Прочность, товар, перевод |
| XML diff | Файл по пути изменяемого ресурса | Меняет выбранную часть документа | Заменить один атрибут |
| Mission Director, MD | md/*.xml |
Реагирует на события и выполняет действия мира | Получить запрос и переименовать объект |
| AI-скрипты | aiscripts/*.xml |
Описывают поведение исполнителя | Движение и выполнение приказа |
| Lua UI | ui/*.lua, подключение в ui.xml |
Строит окна и обрабатывает ввод | Кнопка, таблица, выбранная строка |
| Нативный код | Движок; у некоторых модов отдельная DLL | Работает с более низким уровнем | Специализированная интеграция |
MD и AI используют XML, но у них свои наборы элементов и правила исполнения. Файл с расширением .xml ещё не говорит, что именно он делает. Посмотрите на корневой элемент и каталог.
Python и C# могут встречаться в инструментах и сложных проектах. Они не становятся от этого встроенными языками MD. Для первого XML-патча достаточно описания расширения и самого патча.
Что происходит при загрузке расширения#
Упрощённая схема, полезная для чтения проекта:
extensions/имя_мода/content.xml
→ игра узнаёт расширение и его зависимости
→ получает ресурсы с учётом расширений и патчей
→ загружает подходящие скрипты и UI
→ события и callbacks вызывают обработчики
→ движок меняет состояние мираЭто схема ответственности, а не обещание точного порядка загрузки каждого файла. На результат влияют версия X4, DLC, зависимости, другие моды и состояние сохранения.
content.xml - паспорт расширения. В нём важны уникальный id, версия и зависимости. ui.xml перечисляет Lua-файлы для конкретного UI-окружения. Число в version манифеста и версия, написанная для человека, имеют разное представление; не переносите произвольную строку 0.1.0 в числовое поле без изучения формата.
Пример 1: XML хранит свойство#
Вот сокращённая иллюстрация, не полный macro для установки:
<properties>
<hull max="1800" />
</properties>Здесь hull - элемент, max - его атрибут, 1800 - записанное значение. Сам XML не запускает цикл и ничего не вычисляет. Игра знает, как интерпретировать это поле в соответствующем ресурсе.
Чтобы изменить поле, используют отдельный diff по тому же пути ресурса:
<diff>
<replace sel="/macros/macro[@name='ship_arg_s_fighter_02_a_macro']/properties/hull/@max">3600</replace>
</diff>Разберём sel:
| Часть | Как прочитать |
|---|---|
/macros/macro |
Найти элемент macro внутри macros |
[@name='ship_arg_s_fighter_02_a_macro'] |
Оставить macro с указанным именем |
/properties/hull |
Перейти к свойствам корпуса |
/@max |
Выбрать атрибут max |
Это XPath-селектор: он выбирает место в XML-документе. Он не является условием MD и не ищет живой корабль в секторе. Изменение ресурса также не заменяет проверку уже существующего корабля в сейве. Полный путь установки и проверка описаны в первом патче.
Пример 2: MD выполняет действия#
Внутри actions можно встретить такие инструкции:
<set_value name="$Count" exact="3" />
<do_if value="$Count gt 0">
<debug_text text="'Есть элементы для обработки'" />
</do_if>Читается так: записать 3 в переменную $Count; если значение больше нуля, вывести диагностическое сообщение. Фрагмент предполагает, что вы уже находитесь в подходящем MD cue. Это не самостоятельный файл расширения.
Важная разница:
<set_value name="$Count" exact="3" />
<set_value name="$Label" exact="'Три'" />Внешние двойные кавычки относятся к XML. Внутри exact находится выражение MD. 3 - число; 'Три' - строка. Одинарные кавычки здесь имеют смысл для языка выражений.
Полный MD-скрипт содержит mdscript, cues, отдельные cue, их условия и действия. Cue - узел логики со своим состоянием: он может ждать, стать активным и завершиться. Это полезнее представлять как «когда произойдёт событие, выполнить действия», чем как программу, которая непрерывно перечитывает весь файл сверху вниз.
В нашем коде встречаются обработчики instantiate="true" namespace="this": повторные события получают экземпляры, а локальные переменные разрешаются в их собственном контексте. Подробности и оговорки - в cues и переменных MD. Общий принцип MD описан в руководстве Egosoft; доступность конкретного действия нужно сверять с X4 своей версии.
Пример 3: Lua вызывается из интерфейса#
Учебный Lua-фрагмент, не использующий API игры:
local function makeLabel(name)
if type(name) ~= "string" or name == "" then
return "Без имени"
end
return "Корабль: " .. name
end
local label = makeLabel("Север")local создаёт локальное имя. function объявляет функцию. Аргумент name получает входное значение. return возвращает результат. .. соединяет строки. В итоге label содержит Корабль: Север.
Когда функция назначена обработчиком кнопки, её вызывает UI при нажатии. Такой обработчик называют callback. Назначить функцию и вызвать её прямо сейчас - разные действия:
local callback = makeLabel -- сохранить саму функцию
local result = makeLabel("Север") -- вызвать и сохранить ответКонкретную кнопку и регистрацию меню создают средствами UI X4. Здесь показан только принцип функций, без вымышленного API кнопок.
Как связаны окно и мир#
В игре есть Lua API для чтения данных и некоторые операции движка. MD предоставляет другой набор действий и свойств. Поэтому выбор слоя определяется доступной операцией и её жизненным циклом; не всякий игровой эффект обязательно проходит через MD.
Один распространённый путь обмена выглядит так:
Lua: AddUITriggeredEvent(screen, control, payload)
→ MD: event_ui_triggered
→ данные запроса: event.param3
→ действия и чтение результата
→ MD: raise_lua_event
→ Lua: RegisterEvent(name, callback)AddUITriggeredEvent и RegisterEvent - функции среды X4, их нет в обычном интерпретаторе Lua. set_value и raise_lua_event - элементы MD, их нельзя вызвать как Lua-функции. Готовый парный пример есть в статье об обмене событиями.
Почему после изменения файла результат бывает прежним#
Проверьте, какой слой вы меняли. Патч мог выбрать другой macro или получить другое итоговое значение из-за совместимости модов. Lua-файл мог не попасть в ui.xml. Cue мог уже завершиться и восстановиться таким из сейва. Ссылка на объект могла устареть после загрузки.
Сохранение хранит состояние мира и MD, а не просто «запускает все файлы заново». Некоторые изменения требуют новой инициализации или миграции старых данных. Поэтому отдельно проверяются новая игра и загрузка подходящего сейва.
Полезно задавать три отдельных вопроса:
- Файл загрузился без ошибки?
- Нужный обработчик действительно вызвался?
- Нужное свойство настоящего объекта изменилось и сохранилось?
Сообщение в журнале может подтвердить второй пункт, но само по себе не подтверждает третий.
Что читать дальше#
Следующий шаг - операторы и основные функции с примерами. Затем выберите патч параметра, первый MD-скрипт или своё окно. Официальная отправная точка для поиска форматов и инструментов - Modding Support.