Перейти к основному содержимому

Уровни и карты EternalTD

«Уровень» в EternalTD — это YAML-конфигурация в plugins/EternalTD/levels/ в паре с папкой шаблона мира в plugins/EternalTD/worlds/. Когда игрок присоединяется, EternalTD клонирует шаблонный мир в контейнер миров сервера и запускает сессию в этой клонированной копии.

Поля конфигурации уровня​

ПолеТипПо умолчаниюПримечания
isEnabledbooltrueОтключённые уровни пропускаются при загрузке
levelNamestringnullОтображаемое имя, показываемое в сообщениях, таблицах и меню NPC
levelDescriptionсписок строк[]Строки, показываемые в меню NPC. Поддерживает плейсхолдеры $highscoreWave и $highscorePlayer
worldNamestringnullИмя папки шаблона в plugins/EternalTD/worlds/
startLocationсписок строкnullСписок сериализованных локаций, где спавнятся мобы
endLocationсписок строкnullСписок сериализованных локаций, к которым идут мобы («красные» плитки)
levelLocationsсписок строкnullВсе проходимые клетки уровня. Пакеты содержат этот список; для своей карты его записывает /etd register <level> по выделению пола, либо его можно заполнить вручную
wavesConfigFilestringобязательноИмя файла связанной конфигурации волны (waves/<name>.yml)
highscoreWaveint0Лучшая волна, достигнутая на этом уровне
highscorePlayerNamestring"No one"Отображаемое имя игрока, установившего рекорд
environmentenumNORMALОкружение мира, используемое при загрузке клонированного мира

Размер сетки, используемый повсюду, — 3 блока на логический квадрат (константа GRID_SIZE в коде).

waveCount не используется. Этот ключ в старом файле не задаёт количество волн: EternalTD использует связанную конфигурацию волн.

Формат строки локации​

Локации в EternalTD сериализуются как строки, разделённые запятыми, вида:

worldName,x,y,z,pitch,yaw

Также принимается формат из четырёх частей worldName,x,y,z с нулевыми pitch и yaw и формат из пяти частей x,y,z,pitch,yaw без мира. Для сетки, стартов и выходов сессии указанный мир заменяется миром инстанса. Для хаба и NPC указанный в координатах мир должен быть загружен.

Загруженные пакеты обычно содержат эти значения. Для своей карты levelLocations содержит по одной целочисленной координате центрального блока пола для каждой игровой клетки 3×3, с шагом в три блока. Загрузчик прибавляет 0.5 к X и Z, получая центр в мире. /etd register <level> записывает этот список по выделению пола (см. Рабочий процесс создания карт); его также можно заполнить вручную.

Жизненный цикл мира​

Когда игрок присоединяется к уровню:

  1. EternalTD ищет worldName среди непосредственных подпапок plugins/EternalTD/worlds/. Имена с любой точкой . исключаются. Отсутствующий шаблон, недоступная конфигурация волн или небезопасное имя приводят к отказу. Также отклоняются уровни, у которых levelLocations, startLocation или endLocation отсутствует, пуст или содержит запись, которую не удаётся разобрать: «This level has missing or invalid floor, entrance, or exit coordinates. Nothing was started.»
  2. Повторный запрос подготовки мира отклоняется, пока первый запрос игрока ещё выполняется; отклоняются и игроки, которые уже участвуют в матче, в том числе в матче другого плагина Nightbreak («You are already in a match!»). Ожидающий запрос резервирует игрока в общей системе матчей, поэтому матчи других плагинов не принимают его, пока запрос не завершится. Отключение игрока или 10 минут без готового мира отменяют запрос и удаляют частичную копию.
  3. Он резервирует следующий свободный числовой суффикс (<worldName>_0, <worldName>_1, ...). Резервирование синхронизировано и запоминает имена, которые зарезервированы, но ещё не появились на диске, поэтому два игрока, входящих одновременно, не могут получить одно и то же имя инстанса.
  4. Шаблон копируется в контейнер миров сервера вне основного потока. Загрузка мира и инициализация сессии по-прежнему выполняются в основном потоке.
  5. Клонированный мир загружается как временный пустой мир через TemporaryWorldManager из MagmaCore, поэтому миграция Paper 26.1+ изолируется, а отсутствующие чанки возвращаются как пустота, а не как заново сгенерированный рельеф.
  6. EternalTD проверяет сетку (см. Проверка запуска сессии), а InstanceProtector применяет правила защиты EternalTD. Затем система матчей принимает игрока: сохраняет его запись восстановления, на следующем тике телепортирует его на точку появления нового мира и выдаёт предметы матча. Если другой плагин отклоняет эту телепортацию, игрок покидает матч, и забег завершается. Перед началом дождитесь сообщения «Created your world! We're ready to go.». Более раннее «Joined level!» само по себе не подтверждает успешный запуск сессии.

Требования к шаблону​

EternalTD проверяет корень перед созданием клона, а вложенные элементы — во время копирования. Недопустимый вложенный элемент вызывает откат частичной копии. Шаблон отклоняется, если:

  • это не каталог или это символическая ссылка
  • в его корне нет level.dat
  • он содержит символическую ссылку где-либо внутри
  • он содержит файл с именем .eternaltd-instance (это имя зарезервировано, см. ниже)

Сбой копирования запускает откат и выводит сообщение об ошибке. Если сама очистка не удалась, проверьте консоль: сообщение не доказывает, что все файлы удалены.

Маркеры владения инстансом​

Каждый клон получает файл .eternaltd-instance с именами шаблона и инстанса. EternalTD проверяет этот маркер перед удалением устаревшего инстанса и перед удалением скопированного мира, не прошедшего проверку. Частичная копия удаляется сразу после сбоя копирования, а мир созданной сессии удаляет система матчей при завершении сессии (см. ниже).

Устаревшие инстансы, оставшиеся после падения, вычищаются при загрузке уровней, и даже тогда только те папки, которые несут корректный маркер и не загружены в данный момент.

Проверка запуска сессии​

Сессия становится доступной для игры только при выполнении всех условий ниже:

  • levelLocations, startLocation и endLocation присутствуют и непусты — иначе «This level doesn't have a complete play area, start, and end configuration!»
  • сама игровая зона разбирается в корректную сетку — иначе «This level's play area is invalid!»
  • у каждой стартовой плитки есть и наземный, и воздушный путь к какой-либо конечной плитке — иначе «This level does not have complete land and air paths from every start to an end!»

Эти проверки выполняются после загрузки скопированного мира и до того, как игрок будет принят в матч, поэтому при сбое собственное состояние игрока не затрагивается. Затем EternalTD удаляет временный мир и сообщает «Failed to start the level. Your player and the temporary world were rolled back.» Если сбой происходит при самом приёме игрока, уже после сохранения записи восстановления, эта запись сразу восстанавливается.

Когда сессия заканчивается:

  • Все оставшиеся башни разбираются (башня, выбросившая исключение при удалении, логируется и пропускается, а не блокирует остальные).
  • Запись восстановления игрока восстанавливается, и он возвращается туда, где находился непосредственно перед входом в уровень, или на точку появления основного мира, если тот мир больше не загружен.
  • Правила защиты мира снимаются. Все остальные, кто ещё находится в клонированном мире, например персонал с eternaltd.match.bypass, отправляются на точку появления хаба из config.yml или на точку появления основного мира, если хаб не настроен.
  • Билеты принудительной загрузки чанков игровой области освобождаются (см. Проверка пути).
  • Очистка запрашивает выгрузку и удаление в обоих поддерживаемых расположениях. При отказе в выгрузке удаление пропускается. Пока плагин включён, удаление выполняется асинхронно через 5 секунд после выгрузки. Мир, снова загруженный до очистки, тоже пропускается; неполное удаление записывается в журнал.
  • Завершение идемпотентно — повторный end() (например, /etd quit сразу за которым /etd hub) ничего не делает.

Правила защиты инстанса​

Пока уровень активен, в клонированном мире применяются следующие правила:

  • Взрывы отключены
  • Течение жидкостей отключено
  • Элитры отключены
  • Переключение полёта запрещено
  • Дружественный огонь запрещён
  • Стандартный спавн мобов запрещён

Система матчей также охраняет мир: игрок в забеге не может телепортироваться в него, из него или внутри него, кроме как через собственные перемещения EternalTD, а другие игроки без eternaltd.match.bypass не могут в него телепортироваться. См. Правила матча.

Рабочий процесс создания карт​

Редактируйте шаблон как исходную карту и записывайте сетку в YAML уровня:

  1. Отредактируйте карту в отдельном мире или одиночной игре, сохраните её и поместите шаблон в plugins/EternalTD/worlds/<worldName>/. Изменения клона матча не обновляют шаблон.
  2. Создайте YAML уровня, укажите загруженную конфигурацию волн и заполните levelLocations, startLocation и endLocation. Для сетки используйте центры блоков пола, для стартов и выходов — координаты, соответствующие этим клеткам.
  3. Для игровых помощников в мире редактирования выполните /etd reload, затем /etd selectfloor. Левая кнопка задаёт угол 1, правая — угол 2. Углы должны находиться в одном мире на одинаковой высоте пола Y; длины по X/Z с учётом обоих концов должны быть кратны 3.
  4. Команда /etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2> задаёт углы в текущем мире и вычитает 1 из каждого переданного Y. Указывайте высоту пола плюс один.
  5. /etd showselection <level> показывает над каждой клеткой пола, которую зарегистрировало бы выделение, одну частицу END_ROD, видимую только вам. Команда ничего не сохраняет.
  6. Сохраняйте выделение активным при выполнении /etd register <level> start или end. Команды добавляют целочисленные координаты блока игрока и сразу сохраняют их, не вычитая 1 из Y и не проверяя принадлежность сетке. Предварительно инициализируйте оба списка. Их также можно отредактировать напрямую в YAML.
  7. После регистрации стартов и выходов выполните /etd register <level>. Команда записывает levelLocations по выделению, заменяя существующий список, сохраняет файл уровня и очищает выделение. Выключение режима выделения также сбрасывает его.
  8. Сохраните отредактированный шаблон, перезагрузите конфигурацию и войдите через меню NPC или /etd join <level>. Дождитесь финального сообщения о готовности мира и проверьте карту в игре.

Проверка выделения вычисляет размеры по следующей формуле с усечением до целого:

size = (abs(corner1 - corner2) + 1) / 3

Порядок углов не важен. Проверяются центральные блоки: начиная с меньшего угла по X/Z плюс один, затем с шагом в три блока, на высоте Y угла 1 в текущем мире игрока. Клетка пропускается, если центральный блок пола проходим или блок над ним непроходим. Проверка не охватывает все блоки каждой клетки 3×3.

Проверка пути​

EternalTD кеширует для каждой стартовой клетки самый дешёвый путь A* к одной из конечных клеток — наземный и воздушный. При нескольких выходах сравниваются стоимости готовых путей и выбирается кратчайший, поэтому враги идут к ближайшему достижимому выходу.

Наземный кеш перестраивается при запуске сессии, установке и продаже башни. Воздушный кеш вычисляется только один раз, при первом перестроении: воздушные пути полностью игнорируют башни, поэтому постройки игрока их не нарушают.

Воздушные враги используют отдельный воздушный путь, который полностью игнорирует блокировку башнями и вместо этого следует воздушному смещению (4 блока над настроенным путём).

При загрузке игровой области принудительно загружаются все чанки с клетками сетки, чтобы длительный матч не остановился из-за незагруженного чанка. Билеты загрузки освобождаются при завершении сессии перед удалением мира инстанса.

Кеш также решает, играбелен ли уровень вообще: если у какой-либо стартовой плитки отсутствует наземный или воздушный путь, сессия отклоняется на этапе входа, вместо того чтобы начинать забег, который враги не смогут закончить. Та же проверка выполняется при установке башни, поэтому башня, которая перекрыла бы стартовую плитку, отклоняется, и золото не тратится.

Поиск клеток старта и выхода использует X/Z, не требует точного совпадения с центром и не сравнивает Y. Запись, которой не соответствует клетка сетки, пропускается с предупреждением. Хотя предупреждение указывает на возможное несовпадение с центром, проверьте также наличие нужной клетки в levelLocations.

На всех найденных клетках старта и выхода строительство запрещено, поэтому игрок не может перекрыть их башней. Подсветка размещения выделяет эти клетки голубым.

NPC и меню уровней​

Конфигурации в plugins/EternalTD/npcs/ связывают жителей с уровнями. Правая кнопка открывает меню из 9 слотов с записями в слотах 0, 2, 4, 6 и 8. Рассматриваются только первые пять настроенных ID уровней: отсутствующие ID оставляют пробелы, последующие пропускаются с предупреждением. NPC без единого загруженного уровня из своего списка пропускается целиком. Нажмите левой кнопкой на зелёную стеклянную панель, чтобы запросить вход в уровень.

ПолеТипПо умолчаниюПримечания
isEnabledbooltrueОтключённые NPC пропускаются
levelIDsсписок строкобязательноИмена файлов уровней, предлагаемых этим NPC
locationstringnullЛокация спавна в стандартном формате worldName,x,y,z,pitch,yaw
namestringnull для собственной конфигурацииИмя NPC; готовое определение может задавать его
difficultystringnull для собственной конфигурацииМетка над NPC; задайте явно
disguisestringnullДескриптор LibsDisguises (например, custom:etd_tutorial_npc)
customDisguiseDatastringnullДополнительные данные команды LibsDisguises — обычно длинная строка скина игрока

Появление NPC планируется через четыре секунды после создания его конфигурации и требует загруженного мира назначения. Житель неуязвим, имеет отключённый AI, сохраняется постоянно и получает NPC-метку EternalTD. Если LibsDisguises включён и оба поля маскировки заданы, EternalTD пытается применить маскировку. Недопустимый дескриптор может оставить обычного жителя.

Плавающая стойка для брони с меткой difficulty спавнится в 2.3 блоках над NPC.

Поведение точки появления​

DefaultConfig управляет тем, как игроки обрабатываются в мире-хабе:

  • setupDone — флаг, отслеживающий, было ли завершено руководство первоначальной настройки (по умолчанию false).
  • spawnLocations — по умолчанию etd_spawn,0,65,0,0,0, всегда записывается в конфигурацию. Разрешается и используется только при наличии мира etd_spawn.
  • manageSpawn — по умолчанию true. Если включено и мир назначения загружен, EternalTD запрашивает телепортацию в точку появления через 1 тик после входа.
  • playerGuide — текст игрового руководства.
  • nightbreak.autoDownloadPluginUpdates — общая настройка MagmaCore (по умолчанию false). Когда включено, обновления плагина и контента загружаются автоматически при запуске.
  • nightbreak.applyPluginUpdatesWithoutRestart (общая настройка MagmaCore, по умолчанию never; также when-empty или immediately) определяет, когда загруженное обновление плагина применяется без перезапуска. См. Настройка.

По завершении инициализации EternalTD запрашивает ту же телепортацию для уже подключённых игроков, кроме находящихся в мире назначения. Для этого также нужны включённый manageSpawn и загруженный мир назначения. Ни вход, ни этот обход игроков не проверяют результат телепортации, поэтому другой плагин может помешать прибытию.