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

Формат прототипов

Прототипы FOnline представляют собой именованные наборы свойств, основанные на метаданных и отдельно запекаемые для сервера, клиента и Mapper. Они задают переиспользуемые значения сущностей по умолчанию и фиксированные определения проекта, но не являются runtime-записями сохранения, записями размещения на карте или игровой таксономией контента.

Это руководство описывает модель авторинга, наследование, ссылки, миграции и процесс валидации. Точные объявления текущей ревизии приведены в сгенерированных справочниках синтаксиса, встроенных свойств, правил валидации и канонической JSON-модели.

Состояние контракта

Контракт формата прототипов имеет статус experimental и привязан к ревизии. Движок владеет:

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

Встраивающий проект владеет:

  • дополнительными расширениями прототипов и структурой каталогов контента;
  • проектными объявлениями сущностей и FixedType, свойствами, enum и script callbacks;
  • конкретными ID, сочетаниями полей, игровой семантикой, балансом и локализацией;
  • миграциями проектного контента и сохраняемых данных проекта;
  • семантическими валидаторами, тестами, политикой релизов и публичными примерами.

Сгенерированный каталог свойств показывает, способен ли parser движка загрузить ключ. Он не утверждает, что присваивать этот ключ в прототипе осмысленно или безопасно для конкретной игровой системы. Такие семантические ограничения должны задавать документация и тесты проекта.

Проверенные пути исходников

  • BuildTools/PrototypeFormatInterface.json
  • Source/Common/Settings.inc
  • Source/Common/ConfigFile.cpp
  • Source/Common/Properties.h
  • Source/Common/Properties.cpp
  • Source/Common/PropertiesSerializer.cpp
  • Source/Common/EntityProperties.h
  • Source/Common/EntityProtos.cpp
  • Source/Common/ProtoManager.cpp
  • Source/Common/ScriptSystem.h
  • Source/Server/EntityManager.cpp
  • Source/Tools/Baker.cpp
  • Source/Tools/ProtoBaker.cpp
  • Source/Tools/ProtoTextBaker.cpp
  • Source/Scripting/ServerCritterScriptMethods.cpp
  • Source/Scripting/ServerItemScriptMethods.cpp
  • Source/Scripting/ServerLocationScriptMethods.cpp
  • Source/Scripting/ServerMapScriptMethods.cpp
  • Source/Tests/Test_ConfigFile.cpp
  • Source/Tests/Test_Properties.cpp
  • Source/Tests/Test_EntityProtos.cpp
  • Source/Tests/Test_ProtoManager.cpp
  • Source/Tests/Test_ProtoBaker.cpp
  • Source/Tests/Test_ServerMapOperations.cpp

От исходного файла до запечённого прототипа

ProtoBaker получает полный список файлов resource pack и оставляет те, чьё расширение входит в Baking.ProtoFileExtensions. Значение движка по умолчанию — fopro; проект может добавить fomap для верхнеуровневых прототипов карт и другие расширения, ориентированные на типы авторинга.

Расширение не выбирает тип прототипа. Его выбирает каждая секция:

  • [Proto<Type>] разрешается в метаданные сущности с именем <Type> и требует HasProtos;
  • [<FixedType>] разрешается в метаданные, объявленные через ///@ FixedType;
  • каждая верхнеуровневая секция [ProtoMap] в контейнере карты разрешается в Map.

Вложенные секции [$Name/Critter] / [$Name/Item] не попадают на этот этап. Они принадлежат map baker и отдельному контракту формата карт. Устаревшая структура [Header] / [Tiles] / [Objects] не является допустимым форматом авторинга.

Сначала baker собирает все объявления и регистрирует пустой прототип для каждого разрешённого типа/ID. Регистрация завершается до применения текста свойств, поэтому свойство может ссылаться на прототип, объявленный позже или в другом файле того же входа pack. Порядок исходников не является механизмом зависимостей.

Затем один и тот же набор исходников применяется к метаданным сервера, клиента и Mapper и записывается как <pack>.fopro-bin-<side>.

Синтаксис конфигурации

Текст прототипов использует общий parser конфигурации:

# A configuration comment
[ProtoItem]
$Name = BaseContainer
NoBlock = true

[ProtoItem]
$Name = SecureContainer
$Parent = BaseContainer
NoBlock = false

Используйте key = value для замены, а key += value только там, где добавление текста входит в документированное представление свойства. Конечный обратный слеш продолжает логическую строку только тогда, когда перед ним стоит пробел или табуляция; parser обрезает обе физические строки и соединяет их одним пробелом. # начинает комментарий вне кавычек и неэкранированного содержимого.

Переиспользуемый Engine больше не определяет Item.Count, Item.Stackable, overloads move/add/destroy с частичным count и built-in event изменения stack. Игра, которой нужны взаимозаменяемые item stacks, должна объявить собственные count/stackability properties и владеть merge, split, transfer, destruction, synchronization и notifications в project scripts. Не возвращайте удалённые поля в .fopro как custom-looking keys, если проект явно не объявил соответствующую metadata.

ProtoBaker интерпретирует $Name и $Parent, а применение свойств пропускает любой ключ с префиксом $. $Text ... принадлежит отдельному контракту ProtoTextBaker; не считайте произвольный $-ключ осмысленным только потому, что загрузка свойств его игнорирует. Ключи с префиксом _ также пропускаются при применении свойств и должны быть зарезервированы для проектных инструментов с явным проектным контрактом. Любой другой ключ обязан разрешаться в метаданные.

Идентичность

$Name = <PrototypeId> задаёт ID. Без него используется имя исходного файла без расширения.

ID не может содержать / или $: оба символа зарезервированы для адресации вложенных секций и отклоняются до хеширования и проверки дубликатов.

Идентичность ограничена разрешённым типом. Item/Foo и Critter/Foo могут сосуществовать, но два объявления Item/Foo в любом месте одного входа pack приводят к ошибке. ID хешируются и проходят через правила миграции Proto до проверки дубликатов.

Предпочитайте один основной прототип на файл и сохраняйте имя файла равным ID. Это сохраняет полезное значение по умолчанию и делает поиск по исходникам, review, diff миграций и сгенерированные примеры предсказуемыми. Всегда задавайте $Name явно, если файл содержит несколько секций или его имя не является нужным ID.

Каталоги и расширения служат только организации и обнаружению. Никогда не выводите из них тип или игровое поведение.

Наследование

$Parent = ParentA ParentB перечисляет через пробел родителей того же разрешённого типа. Родители могут находиться в других файлах, но обязаны присутствовать в текущем входе pack.

Baker объединяет:

  1. предков в глубину;
  2. прямых родителей слева направо;
  3. дочерний прототип последним.

Поздние значения заменяют ранние. Управляющие ключи с префиксом $ не копируются как свойства. Среди прямых родителей самый правый выигрывает при совпадении key, а child выигрывает у всех parents. Ancestor, достигнутый по нескольким paths, добавляет поля только при первом достижении; Baking.AllowRepeatedProtoParents с default true пропускает повтор, а false отклоняет inheritance diamond. ProtoBaker и ProtoTextBaker используют один контракт обхода, поэтому properties и $Text не расходятся.

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

Граф родителей должен быть ациклическим. Оба prototype baker отслеживают active parent path и отклоняют self-cycle, цикл из двух узлов и более длинные циклы при любом значении repeated-parent setting. Project-side structural validation сохраняет более быстрый feedback, но authoritative rejection выполняет baking.

Применимость свойств

Загрузка свойств учитывает runtime-сторону:

Проверка неизвестного свойства безусловна и выполняется до учёта применимости по сторонам. Пропуск для конкретной стороны применяется только после успешного разрешения metadata property и никогда не делает неизвестное свойство допустимым.

  • неизвестное свойство приводит к ошибке;
  • отключённое на текущей стороне свойство приводит к ошибке, кроме client-only свойства в server output и server-only свойства в client/mapper output — они пропускаются;
  • virtual-свойство приводит к ошибке;
  • temporary-свойство приводит к ошибке;
  • допустимое свойство разбирается согласно своему типу метаданных.

Свойство является temporary, если оно mutable или принадлежит core и при этом не persistent. Сгенерированный каталог свойств применяет именно это правило к текущим встроенным метаданным и перечисляет активные стороны.

Пропуск server-only property в client records не удаляет его авторские строки из client hash dictionary: ProtoBaker собирает также строки server pack, включая FixedType values. Доступ к property остаётся server-only. Даже client-only rebuild требует server metadata для этого сбора; исходники актуального server pack разбираются без повторной script validation и перезаписи pack. Границы output и receiving pool описаны в Запекании и сетевом контракте.

Проектные метаданные отсутствуют в каталоге одного движка. Production-проект должен сгенерировать сопутствующий каталог из объединённых метаданных движка и проекта и опубликовать его в проектной документации.

Применимость в parser — лишь первый барьер. Проект должен отдельно классифицировать поля как:

  • авторские значения прототипов по умолчанию;
  • runtime-состояние, создаваемое или изменяемое gameplay;
  • структурные ссылки под управлением другого инструмента авторинга;
  • производное/cache-состояние, которое нельзя задавать вручную;
  • обязательные проектные поля и допустимые сочетания полей.

Текстовые значения и ссылки

Авторитетным источником для значений служит PropertiesSerializer. Он отклоняет неверные коллекции, переполнение целых чисел, недопустимые значения enum, неконечные числа с плавающей точкой и неразрешённые ссылки. Boolean принимает объявленную текстовую форму или числовые 0/1; нельзя судить о допустимости чисел и enum по более свободному parser конфигурации.

Свойства FixedType и ссылок на прототипы разрешаются через метаданные и правила миграции. Ссылка обязана называть существующую цель, кроме nullable-свойства с пустым авторским значением.

Не документируйте предполагаемую пунктуацию массивов и словарей проектного свойства. Прочитайте его сгенерированное объявление типа и докажите конкретное значение через baker или focused parser test.

Init scripts

Встроенные типы Item, Critter, Map и Location предоставляют server-side mutable persistent-свойство InitScript. Непустое авторское значение называет глобальную функцию:

void Function(Item item, bool firstTime)
void Function(Critter critter, bool firstTime)
void Function(Map map, bool firstTime)
void Function(Location location, bool firstTime)

Метаданные ScriptFuncType свойства выбирают точную сигнатуру. Во время server baking BaseBaker::ValidateProperties() разрешает каждый непустой callback и отклоняет отсутствующую функцию или несовпадающую сигнатуру. Атрибут callback не требуется. Делегаты не являются допустимыми сохраняемыми именами.

CallInit помечает сущность инициализированной и вызывает соответствующее событие Game.On*Init до InitScript. Callback, уничтоживший сущность, предотвращает последующие шаги. Имя функции, которое нельзя разрешить в runtime, вызывает жёсткий ScriptException; движок не пропускает его молча. Флаг инициализации уже установлен, поэтому действует документированная гарантия Basic жизненного цикла сущности, а не rollback. Исключение из тела скрипта сообщает ScriptFunc::Call и преобразует в результат false, не выпуская его из тела скрипта.

firstTime равен true для новых сущностей и немедленного вызова из SetupScript / SetupScriptEx; восстановленные сущности мира получают false. Эти runtime-методы сначала вызывают callback и сохраняют его имя только после успешного вызова. Типизированный overload отклоняет делегаты, а оба overload бросают исключение, если функцию нельзя разрешить или вызов сообщил об ошибке.

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

Callback начинается с covered инициализируемой сущностью. Перед доступом к несвязанным сущностям он обязан получить или расширить синхронизацию. Runtime-контракт и правила concurrency описаны в Жизненном цикле скриптов и concurrency.

Миграции

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

Объявляйте:

///@ MigrationRule Proto Item OldContainer NewContainer
///@ MigrationRule Proto Item RemovedContainer __remove__

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

При изменении имени или типа свойства следуйте политике миграции свойств и persistence владеющего объявления метаданных. Миграция прототипа не исправляет несовместимый payload свойства.

Практики авторинга

  1. Начинайте со сгенерированных справочников секций и свойств закреплённой ревизии движка.
  2. Добавляйте проектные метаданные и правила контента в проектную документацию, не копируя внутренности движка.
  3. Делайте ID описательными и стабильными; не кодируйте в них временную структуру каталогов, числа баланса или названия релизов.
  4. Держите цепочки родителей короткими, избегайте пересекающихся множественных родителей и запускайте validator циклов.
  5. Задавайте только семантические значения по умолчанию. Пусть runtime-системы владеют временной позицией, ownership, cache и состоянием связей, если проект явно не документирует обратное.
  6. Осознанно учитывайте поведение отдельных сторон. Пропуск ключа из одного выхода не доказывает, что другая сторона работает без соответствующего проектного контракта.
  7. Используйте ссылки на прототипы вместо дублируемых свободных ID, если метаданные это поддерживают, и проверяйте каждую цель.
  8. Добавляйте миграции в том же изменении, где меняется совместимость ID или свойства.
  9. Запускайте настоящий bake и семантические тесты потребляющей подсистемы; успешный parsing ещё не доказывает пригодность контента.
  10. Для review и инструментов ограничивайте каждое проектное расширение одним семейством контента, помня, что тип выбирают секция и метаданные, а не расширение или каталог.

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

Для изменения движка:

python BuildTools/tests/test_docs_prototype_format.py
python BuildTools/docs_prototype_format.py --check
python BuildTools/docs_contract_diff.py --baseline-git-ref origin/master --allow-missing-baseline --write --enforce

Также запустите focused native tests для изменённой границы parser, метаданных, свойства или baker.

Для изменения проектного контента:

  1. пересоздайте проектный справочник метаданных/свойств;
  2. выполните обычный project resource bake;
  3. запустите структурные валидаторы дубликатов ID, циклов наследования, ссылок, миграций и обязательных полей;
  4. запустите focused tests потребляющей gameplay/editor системы;
  5. проверьте каждую сторону, потребляющую side-specific свойство.

Обновление ревизии движка

Когда встраивающий проект обновляет ревизию Engine:

  1. сравните старую и новую канонические модели формата прототипов;
  2. проверьте изменения ProtoBaker, ConfigFile, сериализации свойств, объявлений метаданных, настроек и тестов во всём диапазоне ревизий;
  3. пересоздайте справочники движка и проекта;
  4. обновите проектную документацию формата и семантики для добавленных, удалённых, переименованных, изменивших тип или сторону свойств;
  5. добавьте миграции и release notes для изменений совместимости;
  6. заново запеките все resource packs и запустите focused runtime/editor tests;
  7. не используйте бинарные прототипы, запечённые на предыдущей ревизии.

См. также

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