О, «Герои»? Дайте две! Как я писал очередной браузерный клон легендарной стратегии, в который уже почти* можно играть

2rfv5rdpralxok9zyltejp_0ufa.png

TL; DR для тех, кому некогда читать™:



Итак, началось всёс хорошо продуманного плана… Подождите, это из другой вселенной. В нашей вселенной всё началось с 2020 года, когда смартфоны ещё продавались официально, но на работу ходить уже было нельзя. В какой-то момент я понял, что нужно переключить свою голову с логической оценки происходящего и проблемы четырёх стен на любую — какую угодно! — задачу трёх тел (главное, чтобы не буквально). Или, перефразируя отца ядерной физики, идея должна была быть достаточно безумной, чтобы за неё взяться. Идея нашлась — и винить в этом следует популярнейшую статью 2018 года «Герои Меча и Магии» в браузере: долго, сложно и невыносимо интересно — написать «клон» третьих «Героев». За месяц.

77mj_ftxbc-ugoy7fgyamm6esmy.jpeg

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


(1) День первый

NB: В этой статье я использую две параллельные группы заголовков: одна временна́я, другая смысловая. Чтобы облегчить восприятие, номера дней я указываю так, как будто бы работа шла без перерыва каждый день. Однако дни не нормализованы — в один день я мог провести за проектом 1 час, а в иной — 11 (мой личный рекорд).

Начал я со сбора информации о существующих проектах. Сходу нашёл две очень старые и поныне живущие русские пошаговые MMO (heroeswm с заявленным онлайном в 5 000 игроков и heroesland с онлайном поменьше), русскую же экономическую инкарнацию в виде mlgame, а также heroes-online.com от Ubisoft, аккурат в 2020 благополучно почившую в бозе. Из открытых движков, помимо всем известного VCMI, был относительно новый и очень активный fheroes2 (в отличие от первого, воссоздаёт не третьих «Героев», а вторых). Этим список живых проектов исчерпывался, если не считать многочисленных модов на основе оригинальных «Героев», кучи мобильных игр, мимикрирующих под оригинал, и появившегося в 2020 FreeHeroes от Владимира Смирнова (mapron).

Зато сайтов по тематике игры — великое множество:


  • heroesworld.ru — большой русский форум
  • heroescommunity.com — огромный английский форум
  • celestialheavens.com — английский форум и новости
  • df2.ru — не очень активный ныне форум по MUD Dangerous Fantasy, где постов по третьим «Героям» больше, чем во всех остальных разделах, вместе взятых
  • h3.gg — турниры
  • Форум на GOG
  • heroes35.net — официальный WoG-овский форум
  • Discord-сервер HotA с онлайном в 2 000 и сервер мододелов WoG и ERA с 600
  • «Физмиг» aka «Физика мира Героев» и схожий проект handbookhmm — справочники по механике игры
  • Тысячи карт на maps4heroes.com и hommdb.com, библиотека идей для новых городов, и прочая, и прочая, и прочая, и прочая, и прочая, и прочая, и прочая
  • Полезности на польском, ещё на польском, а вот на чешском и даже на китайском (китайский фандом «Героев» — второй по размеру после русского)

Даже на прогрессивном Хабре астрологи регулярно объявляют месяц статей о «Героях» — в прошлый раз это было в феврале (что, впрочем, не удивительно).

Поясню для тех, кто не в курсе нынешней кухни «Героев». Самым старым модом считается WoG (In the Wake of Gods) — в прошлом году была даже статья автора на 20-летие проекта. В середине нулевых WoG перестал развиваться, но на его основе появился скриптовый движок ERA, встраивающийся внутрь процесса «Героев» и творящий всевозможные безобразия нестандартные вещи (с точки зрения оригинала).

На базе ERA делается много мелких модификаций, но более масштабные обычно используют свою платформу. В первую очередь это HotA (Horn of the Abyss), воспринимаемая многими как «современные Герои 3», в особенности по части PvP. (ИЧЗХ, и WoG, и HotA имеют русские корни.)

Совершенно невероятно, но, и об этом говорилось уже много раз: спустя 24 года, в экосистеме «Героев» каждый год появляется что-то принципиально новое.

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

Ситуация вокруг «Героев» сложилась любопытная. В одном рейтинге игр для ПК «Герои 3» стоят на 4-м месте, обгоняя Warcraft 3 и Mass Effect —, но даже исповедуя здоровый скептицизм, нельзя не признать, что игра до сих пор популярна.


uo-xtxh7lxnuqswc71mubiczj_m.png

Все четыре официальных продолжения провалились. Новых мало кто ждёт. Реинкарнации от сторонних разработчиков появляются регулярно, но славы оригинала никто не стяжал даже близко. Весь фандом сосредоточен исключительно на модах к оригинальной игре, что накладывает свои ограничения (только Windows, неадаптивный UI, жёсткие рамки оригинального геймплея, крайняя трудоёмкость разработки).

Получается, миру нужны современные оригинальные «Герои» с широкими возможностями для моддинга, а также с чем-то, чего нет ни у VCMI с его 15-летней историей и 3 057 звёздами на GitHub, ни у очень вкусного fheroes2 с 1 971 звездой. Почему народ не спешит портировать туда свои модификации? Что я могу предложить нового? И есть ли шанс в одиночку довести дело хотя бы до альфа-версии?

Ответ напрашивался сам собой…


rfaic14dtzufocg0v6ltbddxxfo.jpeg

Если вы хотите расширяемости, то это не про C++. Это даже не очень про Java (см. Xposed). Но это очень даже про JavaScript, особенно если использовать Sqimitive. Вместо API — вся кодовая база; вызывай что хочешь, перекрывай как знаешь. Кто-то спросит — разве можно так писать? Да, если трактористы — женщины! ведь не забудьте: у нас уже есть альтернативы на C++, написанные по всем правилам. Значит, будем писать не по правилам!

Минутка самопиара. Я очень люблю универсальные технологии. Фреймворк Sqimitive — на котором построено всё здание HeroWO и несколько моих проектов поменьше — основан на идее, что любой метод любого объекта есть событие, а на события можно подписываться (с заданным приоритетом), перекрывать имеющиеся обработчики, откладывать их вызов и даже пакетировать (batch). Это позволяет решать множество прикладных задач через единый механизм: например, наследование класса есть просто изменение списка слушателей в подклассе, а множественное наследование — серия таких изменений, и делать их можно после объявления класса (aka mix-ins). Размер всего фреймворка — порядка 1 500 строк, зато документация, описывающая возможности применения — порядка 200 страниц.

Интерпретируемый язык сам по себе сократит время разработки и объём кода, но ведь мы говорим о JavaScript в браузере —, а современные браузеры специально предназначены для презентации (очень) сложного контента. HTML и CSS для не очень динамичной игры вроде «Героев» — это что-то уровня червоточины в пространстве: с их помощью я смогу перепрыгнуть через целые пласты игровых подсистем. А проект, может быть, даже сможет выжить.

Продолжая мысль, если расширяемость ставить во главу угла, то ядро движка должно быть гибким, иначе мы получим тех же «Героев» или VCMI, только в профиль. И чем более гибким, тем больше шанс, что в будущем «Герои» в форме HeroWO выйдут-таки из стазиса и станут современной игрой с хардкорным олдскульным нутром, а может даже вберут в себя существующие модификации, а то и целиком старые RTS вроде Disciples!


nbelhytqmrwuhjj2apwxml7abvc.jpeg

Картинка получалось захватывающая. Решив, что корованы того стоят, я взялся за дело.


(2) День второй

Как-то так получилось, что имея пример великолепной статьи lekzd перед глазами, я изначально документировал весь процесс разработки, так что восстановить последовательность событий не составляет труда.

Бо́льшую часть второго дня я провёл за безуспешными попытками скомпилировать h3m-map-convertor и его зависимость homm3tools. «Герои» хранят игровые карты в файлах с расширением .h3m («Heroes 3 Map»), и, насколько мне известно, общедоступных парсеров этого формата существует ровно один — h3mlib от homm3tools (в 2020 ещё появился FreeHeroes со своим парсером). Качество кода h3mlib удручает: ручной разбор данных на Си — занятие неблагодарное, но когда это Си с макросами, то призыв четырёх пони апокалипсиса становится вопросом времени. Впрочем, главная проблема была в том, что даже после танцев с бубном и успешной компиляции библиотека отказывалась читать официальные карты, причём ошибка возникала где-то внутрях Zlib. В процессе всего этого акта меня не покидало ощущение, что h3m-map-convertor, автором которого, собственно, и является lekzd, базировался на какой-то подпольной более новой версии homm3tools, которая не была доступна простым смертным вроде меня.

В конце концов, я оставил попытки собрать собственный конвертер .h3m в JSON, решив, что 11-ти уже сконвертированных карт, которые я смог вытащить с демо-сайта lekzd, мне вполне хватит для начала работы. Забегая вперёд, скажу: мне хватило лишь одной, той самой — «Adventures of Jared Haret» (можете запустить её в HeroWO), а на 105-й день я написал свой парсер, с рулеткой и мета-данными, сотнями проверок, компилятором и прочими шплюшками.


(4) День четвёртый

Поскольку я не собирался делать клон движка lekzd, то рассматривал его JSON-ы как промежуточный формат. В этот день я написал конвертер «lekzd-json» в формат HeroWO, где сделал самое простое преобразование: пересчёт координат объектов с указания правой нижней точки (как в оригинальных «Героях» и во многих других старых играх) на указание левой верхней точки.


xgi9pcujldoizjeb8f22tgw11y8.png

В этот же день я изучил файлы, задающие параметры различных игровых механик. Вкратце, «Герои» хранят данные в архивах с расширением .lod (а также идентичных .snd и .vid). Таких архивов имеется четыре (открываются через MMArchive):


nxkjvruqomhw_1nnn92t38ecwuk.png


  • H3bitmap.lod — статические изображения (например, портреты героев) и текстовые файлы с константами. Текстовики можно открыть либо в Excel, либо в TxtEdit. Рисунки — 8-битные PCX (это такой BMP эпохи DOS). Да, в «Героях» используется всего 256 цветов! Сразу и не скажешь, правда?


cp78vr0and-nf4pd9kveix_aeo8.png


  • H3sprite.lod — анимированные изображения с расширением .def. По сути, 8-битная графика с разными способами сжатия. DefPreview умеет их показывать и экспортировать в BMP, а DefTool умеет их собирать.


3hchq09zgsxokdl3sey548rce1c.gif


  • Heroes3.snd — игровые звуки (музыка находится в стандартных MP3-шках в самой папке игры); стандартный WAV, но в модуляции DVI-ADPCM — браузеры и многий софт (включая oggenc) её не понимают. Получить «обычный» WAV можно с помощью моей утилитки adpcm2pcm или Audacity или SoX.
  • VIDEO.VID — видеоролики в ныне канувшем в лету (купленном Epic), а на рубеже веков чрезвычайно популярном формате Bink Video. Официальные RAD Game Tools до сих пор запускаются на Windows 10 и позволяют экспортировать кадры и звук.

В репозитории HeroWO есть папка databank с текстовыми файлами — в них я детально описываю данные «Героев», в том числе содержимое архивов и типы используемых файлов и назначение графических и звуковых файлов. В Сети до сих пор нет единого места с такой информацией, поэтому если вы можете что-то дополнить или исправить — пожалуйста, присылайте PR! Обещаю, никто не уйдёт обиженным.

Пользуясь случаем, хочу сказать спасибо нашему соотечественнику Сергею Роженко aka grayface, создавшему в нулевые годы десятки утилит для работы с данными «Героев», исходники которых он выложил на GitHub. Сергей, да пребудет с тобой навечно Сила Delphi!

Разобравшись с форматами в начальном приближении, под конец дня я написал самый первый код для клиентской части: 230 строк (под спойлером), отрисовывающих карту «Приключений Жареда Харета» в базовой версии (как на КДПВ). В последующие годы я до того насмотрелся на эту карту, что мог бы по памяти нарисовать стартовый экран, где героя в проходе зажали Троглодиты…, но мы отвлеклись.


Скрытый текст


  
    HeroWO
  
  
    


spvps-_vd56vbyawsgza45f0nok.png


i8xqjkbfnobika1tq7f9xcfgv4o.png

Части этого кода пережили все рефакторинги и их можно найти даже в текущей версии: ObjectStore, Map, DOM.Map.

Отрисовка вскрыла и первые проблемы, вторая из которых не решена до сих пор:


  • Объекты на карте могут выходить за её границы. Это очень часто случается с элементами ландшафта — горы и лес не помещаются целиком в рамки карты и должны частично обрезаться. Реализация обрезания усложнила бы циклы: стало бы недостаточно перебирать все точки объекта — нужны проверки, не находится ли точка за пределами карты, чтобы не выйти за границы массива. Альтернатива в виде создания обрезанных вариаций объектов при конвертации карты тоже имела свои недостатки. Я решил эту проблему в стиле Warcraft 3: добавил невидимую область по краям, которую движок воспринимает как полноценную часть карты, но которая перекрывается пользовательским интерфейсом.


1yrcayyigpk2hjpszgst5q16k_u.png


  • Z-координата (глубина/дальность от глаз пользователя) вычисляется неясным образом. Вначале я думал, что она зависит от порядка следования объектов внутри .h3m и их координат, но после многочасовых экспериментов с редактором карт я оставил попытки разобраться, как же именно она определяется, так что текущая формула выглядит не очень. Кто знает, расскажите!


xb5aixjl6ygn_uxsqmbaz-wt7oo.png

В этот день было принято первое судьбоносное решение, которое легло в основу движка и, как говорят за океаном, драматично упростившее ряд вещей в дальнейшем. Решение это мне подсказала всё та же статья (раздел «Хранение данных»). Я приведу оттуда слайд, отлично иллюстрирующий концепцию:


gs7ome7iesjlzgr53dfsx9dvys8.jpeg

Я уже говорил, что люблю универсальные технологии? Так вот, в HeroWO вообще все данные хранятся в виде пары десятков огромных массивов внутри объектов класса докObjectStore. Например, objects.json — хранилище объектов на карте приключений:


z9axbdakawpy31jvccdygju7gxa.png

У каждого хранилища есть своя схема (таблица с именами полей для каждого индекса), размеры и массив слоёв, в каждом из которых «россыпью» хранятся данные отдельных объектов. На скриншоте выше показано начало объекта-героя «Жареда Харета»: первая выделенная строка — значение для свойства actionable (список клеток в границах объекта, с которыми можно взаимодействовать), вторая — actionableFromTop (флаг, допускающий взаимодействие с объектом с меньшим Y) и так далее.

В «Приключениях» всего 3 161 объект (включая тайлы земли); размер схемы (число элементов в массиве на один объект) — 45; итого, длина layers равняется 142 245 элементам. Второе и последнее большое хранилище — effects.json (о нём позже), там 3 046 объектов и 231 496 элементов. Если в Chrome сравнить потребление памяти с массивом из объектов (вида {actionable: "000010", actionableFromTop: true, ...}), то увидим выгоду в 32%: 1 205 004 байта против 822 840 у докObjectStore.

Правда, в текущей версии хранение плохо оптимизировано (например, для самых многочисленных объектов — тайлов — схема в два раза короче, и остальное забивается null-ами), но доработать это сравнительно легко.

Инкапсуляция всех данных в структуре одного типа позволяет делать разные полезные штуки. Например, через месяц после начала работы над проектом я добавил поддержку вложенных хранилищ (массивы слоёв с собственной схемой) — на скриншоте это поле garrison сразу под выделением, где 7 есть creature, а 10 — count. Дальше, в конце схемы (стрелка влево) видно, что два поля имеют одинаковый индекс — это объединение (union) для опциональных полей, которые не могут использоваться одновременно и потому хранятся в общей ячейке (в нашем случае, message существует только у квестовых объектов, а available — только у городов, которые ими не являются). Объединения экономят место и создаются автоматически путём анализа пересечений использования свойств в схеме; например (слева — тип значения, справа — разъединяющая характеристика объектов):

$message:
    array  *str    - quest
            str    - quest
    array  *str    - treasure
            str    - treasure
    array  *str    - event
            str    - event
    array  *str    - monster
            str    - monster
$available:
            non-layered 1D sub-store - town
            non-layered 1D sub-store - dwelling
            non-layered 1D sub-store - hero

В своём движке lekzd использовал такую систему вынужденно ради скорости; я же с самого начала положил её в основу проекта, полагая, что она радикально облегчит сериализацию игрового мира. Для примера, конвертер карт оригинальных «Героев» (h3m2json.php) занимает 4 475 строчек, плюс 602 строки с комментариями. В то же время, сохранение и загрузка карты в формате HeroWO — это просто череда вызовов JSON.stringify()/JSON.parse() без какой-либо подготовки данных. Подкупало и то, что наличие единой точки доступа к данным (в лице докObjectStore) должно было сильно упростить синхронизацию клиентов в многопользовательской игре — и действительно, сейчас там порядка 650 строк (сервер, клиент), что в разы меньше, чем один только парсер карт в homm3tools.

Оглядываясь назад, я вижу, что это решение было одним из краеугольных камней, благодаря которому получилось довести дело до выпуска. Я постоянно находил новые выгоды от него — например, хранение данных в виде плоского массива числовых значений потенциально позволяет использовать его напрямую в WebAssemly, что открывает дорогу для оптимизации другого фундаментального механизма HeroWO: калькуляторов игровых эффектов, о которых мы поговорим значительно позже.

Прошло всего 4 дня, а карта уже, считай, готова. График, вроде, выдерживаем, ещё недельку — и ка-ак зарелизим! Казалось бы, что могло пойти не так… od5f8skdoqzr1js9-k9h-z562iw.gif


На днях выйдет вторая часть. Если вам понравилось — пожалуйста, подпишитесь на блог компании, где будут публиковаться все остальные части! Ну, и жду бурления конструктивной полемики в комментариях!

herowo.game • Форум • Discord • YouTube • GitHub

© Habrahabr.ru