Начните с одной мысли#

Код мода - это инструкции и данные, которые 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-патча достаточно описания расширения и самого патча.

Что происходит при загрузке расширения#

Упрощённая схема, полезная для чтения проекта:

TEXT
extensions/имя_мода/content.xml
    → игра узнаёт расширение и его зависимости
    → получает ресурсы с учётом расширений и патчей
    → загружает подходящие скрипты и UI
    → события и callbacks вызывают обработчики
    → движок меняет состояние мира

Это схема ответственности, а не обещание точного порядка загрузки каждого файла. На результат влияют версия X4, DLC, зависимости, другие моды и состояние сохранения.

content.xml - паспорт расширения. В нём важны уникальный id, версия и зависимости. ui.xml перечисляет Lua-файлы для конкретного UI-окружения. Число в version манифеста и версия, написанная для человека, имеют разное представление; не переносите произвольную строку 0.1.0 в числовое поле без изучения формата.

Пример 1: XML хранит свойство#

Вот сокращённая иллюстрация, не полный macro для установки:

XML
<properties>
  <hull max="1800" />
</properties>

Здесь hull - элемент, max - его атрибут, 1800 - записанное значение. Сам XML не запускает цикл и ничего не вычисляет. Игра знает, как интерпретировать это поле в соответствующем ресурсе.

Чтобы изменить поле, используют отдельный diff по тому же пути ресурса:

XML
<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 можно встретить такие инструкции:

XML
<set_value name="$Count" exact="3" />
<do_if value="$Count gt 0">
  <debug_text text="'Есть элементы для обработки'" />
</do_if>

Читается так: записать 3 в переменную $Count; если значение больше нуля, вывести диагностическое сообщение. Фрагмент предполагает, что вы уже находитесь в подходящем MD cue. Это не самостоятельный файл расширения.

Важная разница:

XML
<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 игры:

LUA
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. Назначить функцию и вызвать её прямо сейчас - разные действия:

LUA
local callback = makeLabel        -- сохранить саму функцию
local result = makeLabel("Север") -- вызвать и сохранить ответ

Конкретную кнопку и регистрацию меню создают средствами UI X4. Здесь показан только принцип функций, без вымышленного API кнопок.

Как связаны окно и мир#

В игре есть Lua API для чтения данных и некоторые операции движка. MD предоставляет другой набор действий и свойств. Поэтому выбор слоя определяется доступной операцией и её жизненным циклом; не всякий игровой эффект обязательно проходит через MD.

Один распространённый путь обмена выглядит так:

TEXT
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, а не просто «запускает все файлы заново». Некоторые изменения требуют новой инициализации или миграции старых данных. Поэтому отдельно проверяются новая игра и загрузка подходящего сейва.

Полезно задавать три отдельных вопроса:

  1. Файл загрузился без ошибки?
  2. Нужный обработчик действительно вызвался?
  3. Нужное свойство настоящего объекта изменилось и сохранилось?

Сообщение в журнале может подтвердить второй пункт, но само по себе не подтверждает третий.

Что читать дальше#

Следующий шаг - операторы и основные функции с примерами. Затем выберите патч параметра, первый MD-скрипт или своё окно. Официальная отправная точка для поиска форматов и инструментов - Modding Support.