FOnline Engine
Current master GitHub
Документация Docs/ru/how-to/content/map-format.md

Формат карт FOnline

Это руководство определяет переиспользуемый контракт движка для авторских файлов .fomap. Оно описывает исходный синтаксис, идентичность карт и размещений, переопределения свойств, владение предметами, цикл загрузки и сохранения в Mapper, раздельное запекание для сторон и начальное создание runtime-сущностей.

Полный контракт, привязанный к ревизии, приведён в сгенерированном справочнике формата карт:

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

Решение о размещениях

Назначайте каждому авторскому размещению Critter и Item явный положительный $Id и сохраняйте эти идентификаторы уникальными среди секций Critter и Item всей карты. Каждому размещению также нужен $Proto: он выбирает прототип криттера или предмета и не является идентификатором размещения.

Выражайте владение явными ссылками. Предметы CritterInventory содержат идентификатор владеющего размещения в CritterId, а предметы ItemContainer содержат идентификатор родительского предмета в ContainerId. Не выводите владение из порядка секций или близости координат.

Минимальная карта

[ProtoMap]
$Name = SmallRoom
Size = 80 80
WorkHex = 40 40

[$Name/Critter]
$Id = 1
$Proto = Guard
Hex = 38 40
Dir = 3

[$Name/Item]
$Id = 2
$Proto = MetalDoor
Hex = 42 40

Настроенный контейнер карт содержит один или несколько якорей [ProtoMap]. Секции размещений адресуют якорь как [$Name/Critter] и [$Name/Item] либо используют вместо $Name явный ID карты из якоря. Данные перед первым якорем, неизвестный адрес и любые другие формы секций отклоняются. В частности, текущий загрузчик отклоняет секции без адреса [Critter] и [Item]. Они, как и более старые формы [Header], [Tiles] и [Objects], могут встречаться в унаследованных проектах, но не являются допустимым вводом для текущего движка.

Общий конфигурационный парсер предоставляет key = value, повторяющиеся секции, комментарии #, продолжение строки обратной косой чертой и синтаксис добавления key += value. Типы свойств по-прежнему определяют допустимые текстовые значения и операции добавления.

Идентичность карты

Каждая секция [ProtoMap] начинает определение карты. $Name задаёт ID прототипа Map, адрес секций размещений и базовое имя обоих запечённых ресурсов:

SmallRoom.fomap-bin-server
SmallRoom.fomap-bin-client

Если контейнер содержит один безымянный якорь, используется имя исходного файла без .fomap. В контейнере с несколькими картами каждый якорь обязан иметь уникальное явное $Name; размещения используют это имя, например [VaultEntrance/Critter]. Для обычных файлов с одной картой держите имя исходного файла и $Name одинаковыми. Несовпадение допустимо и полезно для намеренного канонического переименования, но делает целевое запекание и поиск исходника менее очевидными.

$Parent использует обычное наследование прототипов Map при запекании прототипов. Это небезопасная для Mapper конструкция исходника: загрузка исходника в Mapper не разрешает цепочку родителей, а сохранение записывает текущее полное состояние свойств карты без $Parent. Для карт, редактируемых в Mapper, предпочитайте явные свойства [ProtoMap]. Если наследование необходимо, проверяйте и запечённый runtime-результат, и каждый цикл загрузки и сохранения в Mapper.

Поля $Text <language> относятся к запеканию текста прототипов. Mapper явно сохраняет их как дополнительные данные [ProtoMap]. Другие неизвестные директивы $ при сохранении в Mapper не сохраняются.

Идентичность размещения

Каждое адресованное размещение Critter и Item требует $Proto:

[$Name/Item]
$Id = 20
$Proto = Locker
Hex = 45 42

Сначала разрешается прототип. Остальные ключи применяются как переопределения свойств отдельного размещения к копии свойств этого прототипа.

На уровне загрузчика $Id необязателен. Отсутствующие, неположительные и повторяющиеся значения заменяются следующим свободным положительным ID. Это восстановление позволяет загружать несовершенный контент, но не подходит как авторский контракт: после исправления ссылка владения может незаметно указывать на другую сущность.

Для производственных карт:

  1. задавайте каждому размещению явный положительный $Id;
  2. обеспечивайте уникальность ID совместно для секций Critter и Item, а не только внутри одного типа;
  3. сохраняйте ID стабильным между правками, если на размещение ссылается другое размещение;
  4. используйте аудит или gate запекания, отклоняющий случайные дубликаты до ревью.

Текстовое чередование не гарантирует порядок выполнения. Загрузчик сначала обрабатывает все секции Critter, затем все секции Item. Сохранение в Mapper также нормализует порядок: криттеры и предметы их инвентаря идут перед предметами карты и их непосредственными дочерними предметами.

Переопределения свойств

Свойства секции используют получателя, выбранного формой секции:

Секция Получатель Типичные свойства движка
[ProtoMap] Map Size, WorkHex, DayTime, DayColor
[$Name/Critter] или [MapId/Critter] Critter Hex, Dir, Condition, InitScript
[$Name/Item] или [MapId/Item] Item Hex, Ownership, Static, Hidden, Count, PicMap

Сгенерированный каталог свойств является авторитетным источником встроенных метаданных для закреплённой ревизии. В нём указаны тип, runtime-стороны, флаги, источник и допустимость текстового авторинга. Метаданные проекта могут добавлять свойства; документируйте их в репозитории проекта, не расширяя это руководство движка декларациями одной игры.

Неизвестные, виртуальные, временные, неправильно записанные или недопустимые для текущей стороны свойства приводят к ошибке при загрузке или валидации. Свойства конкретной стороны могут не попасть в противоположный output. Свойства-ресурсы проверяет MapBaker, поэтому синтаксически корректная карта всё равно может завершиться ошибкой из-за отсутствующего изображения, модели, скрипта, звука или прототипа.

Владение предметами

Ownership определяет, где материализуется авторский предмет:

Владение Ссылка или позиция Поддерживаемое применение на карте
MapHex Hex Статический объект или создаваемый нестатический предмет карты
CritterInventory CritterId Непосредственный предмет инвентаря размещённого криттера
ItemContainer ContainerId Непосредственный дочерний предмет размещённого нестатического предмета карты
Nowhere нет Не поддерживается для авторского размещения на карте

Пример предмета инвентаря:

[$Name/Critter]
$Id = 100
$Proto = Guard
Hex = 30 30

[$Name/Item]
$Id = 101
$Proto = Rifle
Ownership = CritterInventory
CritterId = 100

Пример контейнера и его непосредственного дочернего предмета:

[$Name/Item]
$Id = 200
$Proto = LootCrate
Hex = 35 30
Static = false

[$Name/Item]
$Id = 201
$Proto = Ammo
Ownership = ItemContainer
ContainerId = 200

Сервер сначала создаёт криттеров и нестатические предметы карты, записывает соответствие авторских и runtime-ID, а затем присоединяет дочерние предметы. Дочерний предмет, для ID владельца которого нет runtime-соответствия, пропускается. Статические предметы не являются обычными создаваемыми владельцами, а цепочки дочерних предметов глубже одного уровня не материализуются текущим однопроходным сопоставлением ID. Ограничивайте авторскую вложенность одним уровнем и покрывайте её runtime-тестом, если от неё зависит игровой процесс.

Статические и динамические предметы

Статический предмет обязан использовать владение MapHex. При серверной загрузке карты он становится неизменяемой записью статической сетки. Его Hex, геометрия multihex, NoBlock, ShootThru, флаги триггеров и статические скрипты участвуют в состоянии коллизий и взаимодействий карты.

Нестатический предмет MapHex является заготовкой: сервер создаёт новый runtime-предмет для каждого экземпляра карты и переназначает его авторский ID. Размещённые криттеры используют ту же модель создания на экземпляр. Предметы инвентаря и контейнеров создаются после их владельцев.

Выбирайте осознанно:

  • используйте статические предметы для неизменной геометрии карты, декораций, блокеров и триггеров, которым не нужен обычный жизненный цикл предмета;
  • используйте нестатические предметы для добычи, контейнеров, перемещаемых или изменяемых объектов и корней владения;
  • не делайте статическим предмет инвентаря или контейнера;
  • не полагайтесь на авторский ID статического предмета как на runtime-владельца ItemContainer.

Серверный и клиентский output

MapBaker создаёт связанную пару:

  • серверный бинарный файл содержит хеши строк карты, размещённых криттеров и все предметы;
  • клиентский бинарный файл содержит хеши строк и записи видимых статических предметов;
  • динамические сущности синхронизируются обычным runtime-механизмом сущностей, а не встраиваются в статический слой клиента.

Скрытые статические предметы не записываются как клиентские предметы, но строки их клиентских свойств всё равно добавляются в словарь хешей. Благодаря этому серверная статическая логика сохраняет идентификаторы, нужные клиентскому resolver хешей, не раскрывая видимую сущность карты.

Client dictionary также содержит все строки, собранные для server map blob: авторские значения Server properties и overrides криттеров/dynamic items. Добавляются только строки, не server entities или property records. В client pool они попадают лишь при загрузке этой карты; для более ранней передачи map-only hash нужен иной объявленный receiving source. См. сетевой контракт.

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

Координаты и границы

ProtoMap.Size задаёт допустимые координаты карты. Каждый размещённый криттер и каждый предмет MapHex должны иметь Hex внутри этого размера. Серверная загрузка отклоняет позиции за границами.

Некоторые операции редактирования Mapper ограничивают перемещаемые сущности и координаты multihex границами карты. Это удобство редактора, а не разрешение коммитить неверный исходник. Валидатор проекта должен разбирать и отклонять выходящие за границы авторские координаты до runtime-упаковки.

MultihexMesh представляет собой последовательность пар координат x/y. Сохранение в Mapper нормализует свойство в пары с продолжением обратной косой чертой:

MultihexMesh = \
 40 40 \
 41 40 \
 42 40

Нечётное число значений приводит к ошибке сериализации. Загрузка в Mapper также может объединять подходящие предметы в multihex-сетки согласно прототипному MultihexGeneration; сохранение после такой операции способно существенно переписать размещения. Проверяйте такие diff как семантические изменения карты.

MultihexLines задаёт принадлежащую прототипу directional geometry, разворачиваемую вокруг placement anchor. Server materialization применяет эти line cells к static и dynamic map items, а также разворачивает линии вокруг каждого допустимого cell из MultihexMesh. Все получившиеся cells указывают на один item и участвуют в cached blocking/interaction flags. Поэтому runtime placement считает anchor, mesh cells и их line expansions единым footprint; project validator должен проверять весь expanded footprint, а не только anchor и явные пары mesh.

Цикл Mapper

Сохранение в Mapper является детерминированной нормализацией выбранной карты, а не побайтовой сериализацией этой карты. Оно:

  • сначала записывает [ProtoMap] и сохраняет выбранную карту в $Name;
  • сериализует текущее полное состояние свойств Map в Mapper;
  • сохраняет дополнительные поля $Text*;
  • исключает управляющую директиву $Parent;
  • записывает размещения как [$Name/Critter] и [$Name/Item];
  • записывает явные $Id и $Proto размещений;
  • группирует всех криттеров перед всеми предметами карты;
  • размещает непосредственные предметы инвентаря и контейнера сразу после группы владельца;
  • нормализует порядок свойств и форматирование MultihexMesh;
  • перед сохранением может объединять предметы согласно MultihexGeneration.

Если исходный контейнер содержит несколько карт, Mapper заменяет только последовательность секций выбранной карты и сохраняет каждый невыбранный соседний блок карты побайтно. Это не делает выбранный блок побайтно сохраняемым: он всё равно нормализуется по правилам выше.

Перед работой в Mapper с написанными вручную или сгенерированными картами закоммитьте или иным способом сохраните пригодный для ревью снимок исходника. После сохранения проверьте полный diff выбранного блока, подтвердите записанное $Name, убедитесь, что соседние блоки карт не изменились, и заново запеките оба output.

Генераторам следует сразу выпускать каноническую нормализованную форму. Стабильный порядок и явные ID уменьшают последующие diff Mapper и неоднозначность для людей и ИИ-агентов.

Процесс валидации

Производственный gate встраивающего проекта должен:

  1. разобрать настроенный контейнер и потребовать один или несколько якорей [ProtoMap], причём каждый якорь в файле с несколькими картами должен иметь уникальное явное $Name;
  2. принимать только адреса размещений [$Name/Critter] / [$Name/Item] или эквивалентные адреса с явным ID карты, разрешающиеся в объявленный якорь;
  3. требовать явные положительные ID, уникальные совместно для размещений Critter и Item внутри каждой карты;
  4. разрешить каждый $Proto, значение владения, CritterId и ContainerId;
  5. проверить свойства получателей, доступность на сторонах, ресурсы и границы карты;
  6. запечь серверный и клиентский ресурс каждой объявленной карты;
  7. загрузить представительные карты в сервере и клиенте или Mapper;
  8. выполнить цикл загрузки и сохранения контейнеров с несколькими картами и побайтно проверить нетронутые соседние блоки;
  9. выполнить проектные проверки заданий, точек появления, коллизий, визуальных наборов, скриптов и игровых маршрутов.

Юнит-тесты движка покрывают строгость парсера, исправление ID, применение свойств, каноническое именование output, данные сторон, скрытые статические предметы, нормализацию Mapper и серверную материализацию. Проекту всё равно нужна валидация на реальном контенте, потому что тесты движка не знают его метаданные и правила дизайна карт.

Лучшие практики

  • Для файлов с одной картой согласуйте имя файла, $Name и ID в каталоге проекта; в контейнерах с несколькими картами используйте явные уникальные имена.
  • Помещайте первый [ProtoMap] перед контентом и используйте только адресованные секции размещений.
  • Используйте явные уникальные положительные ID, хотя загрузчик умеет их исправлять.
  • Делайте владение неглубоким, а ссылки очевидными в соседних секциях.
  • Используйте статические предметы только для неизменных объектов MapHex.
  • Не используйте наследование уровня карты в файлах, редактируемых Mapper, если ограничения цикла не обработаны намеренно.
  • Генерируйте каноническое форматирование и проверяйте каждый нормализующий diff Mapper.
  • Проверяйте в CI исходник, обе запечённые стороны и представительные runtime-загрузки.
  • Документируйте проектные соглашения о картах рядом с контентом проекта, ссылаясь сюда за переиспользуемой механикой движка.

Авторитетные источники

Сгенерированная модель отслеживает точные файлы и стабильные ID правил. Основные авторитетные реализации:

  • Source/Common/ConfigFile.cpp для общего текстового синтаксиса;
  • Source/Common/MapLoader.cpp для валидации секций, ID размещений и разрешения прототипов;
  • Source/Tools/ProtoBaker.cpp и Source/Tools/ProtoTextBaker.cpp для обработки прототипов и текста [ProtoMap];
  • Source/Tools/MapBaker.cpp для идентичности output, применения свойств, валидации и данных сторон;
  • Source/Tools/Mapper.cpp и Source/Client/MapView.cpp для нормализации загрузки и сохранения Mapper;
  • Source/Server/MapManager.cpp для статической загрузки и материализации каждого экземпляра;
  • Source/Client/MapView.cpp для загрузки статической карты на клиенте.

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

Введите запрос.