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






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


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


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


А жив ли мальчик? Расчехляем OSINT


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


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


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



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


Поясню для тех, кто не в курсе нынешней кухни «Героев». Самым старым модом считается 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 — но даже исповедуя здоровый скептицизм, нельзя не признать, что игра до сих пор популярна.



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


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


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



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


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


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


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



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


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


Игровые архивы и форматы данных


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


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


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


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


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



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



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


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


  • 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 строк (под спойлером), отрисовывающих карту «Приключений Жареда Харета» в базовой версии (как на КДПВ). В последующие годы я до того насмотрелся на эту карту, что мог бы по памяти нарисовать стартовый экран, где героя в проходе зажали Троглодиты… но мы отвлеклись.


Скрытый текст
<!DOCTYPE html>
<html>
  <head>
    <title>HeroWO</title>
  </head>
  <body>
    <div id="herowo"></div>

    <script src="nodash.min.js"></script>
    <script src="sqimitive.min.js"></script>

    <script>
      var ObjectStore = Sqimitive.Base.extend({
        _schema: {},
        _schemaLength: 0,
        _layers: [],
        _layerLength: 0,
        _maxLayer: 0,
        _strideX: 0,
        _strideY: 0,
        _strideXY: 0,
        _maxZ: 0,

        events: {
          // opt:
          //> schema    {prop1: 0, prop2: 1, prop3: 1}
          //> layers    [ [o1p1, o1p2, o2p1, o2p2, ...], [o3p1, o3p2, null, null] ]
          //> strideX   set to 1 if not using that coord (1D array)
          //> strideY   set to 1 if not using that coord (2D array)
          init: function (opt) {
            // Passed arrays are not cloned for performance. Clone them before
            // passing if planning to change.
            this._schema = opt.schema
            this._schemaLength = _.max(_.filter(this._schema, function (v, k) {
              return k.indexOf('.') == -1
            })) + 1
            this._layers = opt.layers
            this._layerLength = this._layers[0].length
            this._maxLayer = this._layers.length - 1
            this._strideX = opt.strideX
            this._strideY = opt.strideY
            this._strideXY = opt.strideX * opt.strideY
            this._maxZ = this._layerLength / this._strideXY / this._schemaLength - 1
            if (Math.floor(this._maxZ) !== this._maxZ) {
              throw new Error('Stride parameters do not match the number of members.')
            }
          },
        },

        // prop - either resolved to integer or name of the outermost prop
        // (not 'foo.bar'). It's used in other methods; numeric argument works
        // faster so pre-resolve property index when doing heavy calculations.
        // Doesn't check if prop exists in the schema.
        // There's no "propertyByIndex()" because multiple properties may live
        // on one index ("union").
        propertyIndex: function (prop) {
          return typeof prop == 'number' ? prop : this._schema[prop]
        },

        // Unlike advance(), doesn't check if x/y/z are within the boundaries.
        toContiguous: function (x, y, z, prop) {
          return (z * this._strideXY + y * this._strideX + x)
                  * this._schemaLength + this.propertyIndex(prop)
        },

        fromContiguous: function (n) {
          var prop = n % this._schemaLength
          n = (n - prop) / this._schemaLength
          var x = n % this._strideY
          n = (n - x) / this._strideY
          var y = n % this._strideX
          n = (n - y) / this._strideX
          return {z: n, y: y, x: x, prop: prop}
        },

        // Wraps around. Stop when returns negative.
        //
        // for (var n = toContiguous(1, 2, 3, 'foo'); n >= 0; n = advance(n, -2))
        //   for (var fooValue, l = 0; null != (fooValue = atContiguous(n, l)); l++)
        //     alert(fooValue)
        advance: function (n, by) {
          n += by * this._schemaLength
          return n >= this._layerLength ? -1 : n
        },

        // If need to retrieve multiple properties of the same object, give
        // prop = 0 and use the passed n:
        // var prop = propertyIndex('foo')
        // findWithin(..., 0, function (..., l, n) { atContiguous(n + prop, l) })
        findWithin: function (sx, sy, sz, ex, ey, ez, prop, func, cx) {
          cx = cx || this
          for (var n = this.toContiguous(sx, sy, sz, prop);
               n >= 0 && (sx != ex || sy != ey || sz != ez);
               n = this.advance(n, +1)) {
            for (var value, l = 0; null != (value = this.atContiguous(n, l)); l++) {
              value = func.call(cx, value, sx, sy, sz, l, n)
              if (value) { return value }
            }
            if (this._strideX <= ++sx) {
              sx = 0
              if (this._strideY <= ++sy) {
                sy = 0
                sz++
              }
            }
          }
        },

        find: function (prop, func, cx) {
          return this.findWithin(0, 0, 0, Infinity, Infinity, Infinity, prop, func, cx)
        },

        // Convention: x/y/z - coords, l - layer (depth), prop - property index
        // or name, n - contiguous index of x/y/z/prop (but not l).
        atCoords: function (x, y, z, prop, l) {
          return this.atContiguous(this.toContiguous(x, y, z, prop), l)
        },

        // Returns == null when there are no more objects at l and below.
        // n must be within boundaries.
        atContiguous: function (n, l) {
          return l > this._maxLayer ? null : this._layers[l][n]
        },
      })

      var HMap = Sqimitive.Base.extend({
        objects: null,    // ObjectStore; do not set

        _opt: {
          state: 'created',   // created, loading, loaded
          url: '',
          format: 0,
          origin: '',
          width: 0,
          height: 0,
          levels: 0,
          difficulty: 0,
          title: 0,
          description: 0,
        },

        fetch: function (url) {
          if (this.get('state') != 'created') {
            throw new Error('Must fetch() only on a new Map.')
          }

          this.set('url', url)
          this.set('state', 'loading')
          var async = new Sqimitive.Async.Fetch({dataType: 'json', url: url + 'map.json'})

          return async
            .whenSuccess(function () {
              this.assignResp(async.response)
              this._fetchObjects()
            }, this)
        },

        _fetchObjects: function () {
          var async = new Sqimitive.Async
          async.nest('o', new Sqimitive.Async.Fetch({dataType: 'json', url: this.get('url') + 'objects.json'}))
          //async.nest('c', new Sqimitive.Async.Fetch({dataType: 'json', url: this.get('url') + 'classes.json'}))

          return async
            .whenSuccess(function () {
              var objects = async.nested('o').response
              this.objects = new ObjectStore(objects)
              this.set('state', 'loaded')
            }, this)
        },
      })

      var el = $('#herowo')
      $('body').css('background', 'cyan')
      var map = new HMap

      map.on('change_state', function (now) {
        if (now == 'loaded') {
          var oclass = this.objects.propertyIndex('class')
          var osubclass = this.objects.propertyIndex('subclass')
          var otexture = this.objects.propertyIndex('texture')
          var owidth = this.objects.propertyIndex('width')
          var oheight = this.objects.propertyIndex('height')
          var ox = this.objects.propertyIndex('x')
          var oy = this.objects.propertyIndex('y')
          var oz = this.objects.propertyIndex('z')
          var oabove = this.objects.propertyIndex('isAbove')
          var omirrorX = this.objects.propertyIndex('mirrorX')
          var omirrorY = this.objects.propertyIndex('mirrorY')
          window.o = this.objects
          this.objects.find(0, function (val, x, y, z, l, n) {
            z = this.atContiguous(n + oz, l)
            if (z == 1) {
              var tn = 0
              var c = this.atContiguous(n + oclass, l)
              var s = this.atContiguous(n + osubclass, l)
              if (c >= 256) {
                tn = s
              }
              x = this.atContiguous(n + ox, l)
              y = this.atContiguous(n + oy, l)
              $('<div>')
                .css({
                  position: 'absolute',
                  left: x * 32,
                  top: y * 32,
                  width: this.atContiguous(n + owidth, l) * 32,
                  height: this.atContiguous(n + oheight, l) * 32,
                  zIndex: this.atContiguous(n + oabove, l) * 10000 + ( (y + this.atContiguous(n + oheight, l)) * 10 + l + 1 ),
                  background: 'rgba(255,0,0,.0) url(../../def-png/' + this.atContiguous(n + otexture, l) + '/0-' + tn + '.png)',
                  //outline: '1px dashed rgba(255, 0, 0, .2)',
                  transform:
                    'scale(' +
                    (this.atContiguous(n + omirrorX, l) ? -1 : +1) +
                    ', ' +
                    (this.atContiguous(n + omirrorY, l) ? -1 : +1) +
                    ')',
                })
                .attr({
                  title: x + ':' + y + ' ' + this.atContiguous(n + oabove, l) + ' ' + this.atContiguous(n + otexture, l)
                })
                .appendTo(el)
            }
          })
        }
      })

      map.fetch('maps/converted/')
    </script>
  </body>
</html>



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


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


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


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


ObjectStore: универсальное хранилище данных HeroWO


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



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



У каждого хранилища есть своя схема (таблица с именами полей для каждого индекса), размеры и массив слоёв, в каждом из которых «россыпью» хранятся данные отдельных объектов. На скриншоте выше показано начало объекта-героя «Жареда Харета»: первая выделенная строка — значение для свойства 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 дня, а карта уже, считай, готова. График, вроде, выдерживаем, ещё недельку — и ка-ак зарелизим! Казалось бы, что могло пойти не так…




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


herowo.gameФорумDiscordYouTubeGitHub

Комментарии (69)


  1. celen
    18.04.2023 13:36
    +17

    Безумству храбрых поем мы песню (с).

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


    1. ProgerXP Автор
      18.04.2023 13:36

      Конечно, игра жутчайше, адски глючит и лагает

      Со всей ответственностью заявляю, что там буквально две главные причины всех проблем (canvas и effects), поэтому если есть кто-то с прямыми руками и свободным временем — из движка можно сделать "конфетку" сравнительно малой кровью.


      1. realWeRT
        18.04.2023 13:36

        а что если на юнити запилить движок , ну да переписать придется зато компилить хоть под чо?


        1. AgentFire
          18.04.2023 13:36

          Я как-то мечтал о подобном, но юнити еще тогда не знал, и хотел на WPF. Ведь в игре много интерфейсов! Такая классная технология и так жаль, что не кроссплатформенная.


      1. domix32
        18.04.2023 13:36

        canvas в смысле внутреигровой или который html canvas?


        1. ProgerXP Автор
          18.04.2023 13:36

          Который HTML <canvas>. В движке сейчас используются только DOM-узлы для представления всей графики.


      1. pythonist1234
        18.04.2023 13:36

        А мб стоит переписать на WebGL (или сразу на Three.js :-))?


        1. ProgerXP Автор
          18.04.2023 13:36

          Конечно, можно, только что значит "переписать"? Если имеется в виду переписать движок, то в этом нет необходимости — за графику там отвечает хорошо если 15% всего кода.


          1. pythonist1234
            18.04.2023 13:36

            Ну да, графику я и имел в виду.


        1. domix32
          18.04.2023 13:36
          +1

          Тогда проще взять какой-нибудь готовый движок а ля Phaser и сделать для битмапов/звуков обертки для него.


          1. ProgerXP Автор
            18.04.2023 13:36

            Я работал с Phaser. Это целый комбайн размером с сам HeroWO. Например, у него есть физический движок (расчет столкновений и прочее). Тащить его только ради рисования текстур как-то не камильфо (для звуков уже есть своя вполне адекватная подсистема) — это можно делать и без всяких библиотек.


            1. domix32
              18.04.2023 13:36
              +1

              Только у вас миллион нод и проблемы с клампингом, а у phaser 60 fps и прочая инфраструктура для game dev. Дело-то конечно ваше.


  1. appet1te
    18.04.2023 13:36

    Славяне могут вдыхать жизнь в героев до бесконечности. Восхищает и удивляет.

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

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


    1. ProgerXP Автор
      18.04.2023 13:36
      +2

      Команда HotA - закрытая донельзя и базируется на оригинальных "Героях", так что "официальным" такой проект никогда не станет и пределы роста у него весьма ощутимые. Реальнее было бы вкладываться в VCMI или fheroes2, но там совсем другой стек и свои тараканы, о чем неплохо написано тут.


      1. Wallhead
        18.04.2023 13:36
        +1

        Большой плюс хоты, что там уже сделан большой, хороший ребаланс и много клевых фич встроено. И в нее уже играет большое количество людей, мне кажется уже ни один новый проект по героям не выстрелит, как хота. Вам удачи желаю, конечно)


        1. ProgerXP Автор
          18.04.2023 13:36

          Нужно действовать поэтапно. HotA — это надстройка над SoD. Когда будет полная поддержка SoD, то добавить "ребаланс и фичи" не составит труда.


      1. Mangojuss
        18.04.2023 13:36
        +1

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

        Все "свистелки" гораздо проще делать уже на качественно сделанном продукте, чем после малейшего изменения ловить сотни багов или переписывать половину движка. Вы пока не много нового можете заметить в fheroes2 лишь потому, что разработчики не гонятся за сумбурным добавлением всего и вся, что придет а голову, а просто пытаются сделать базу, готовую игру, которая бы стала отправной точкой для будущих модификаций геймплея.

        И в целом, по моему личному мнению, излишне открытый проект, в котором изменения в геймплей вносятся нецентрализованно, никогда не станет популярным, оставаясь уделом нескольких гиков, которые любят поковырять игру на предмет "что если...". К тому же, отсутствие какого либо порядка в разработке приведет к бесконечным конфликтам любых новых реализаций. И в конечном счёте такие проекты так никогда и не дойдут до играбельного состояния.

        Чем глобальнее изменения, тем меньше шансов, что предвзятое геройское сообщество примет мод. В этом мне импонирует подход команды HotA: разработка осуществляется слаженной командой единомышленников со своим видением, но с оглядкой на настроения сообщества. Зайдите на любой форум и предложите людям реализовать любое в этой игре. И вам тут же накидают не одну тысячу желаний, от мелких правок, до самых бредовых. И грянут бесконечные споры о том, на сколько единиц изменить ту или иную величину, или на какое расстояние можно телепортировать город новым "заклинанием". Поэтому хорошие проекты, по моему мнению, не должны быть слишком открыты, чтобы любой желающий начинал переворачивать всё с ног на голову. К тому же, само качество и проработка нового контента у таких одиночек-энтузиастов вызывает сомнения.

        Гораздо больше шансов у проекта сыскать себе поклонников, если добавлять в игру что-то новое будет одна команда с оглядкой на баланс, лор игры и просто одну концепцию. Я не вижу смысла в добавлении каких-то новых фишек, наподобие боёв 3х3 героев на одном поле битвы просто потому, что мы можем. Если же душа желает столь глобальных изменений, может проще сделать уже новую игру, чем выслушивать потом мириады упрёков, дескать, что это не канон и вы испортили игру? Геройщики - люд консервативный, навряд ли многие захотят играть в такое. А если играть будут единицы, есть ли смысл в потраченном времени...?

        Безусловно, развитие игры невозможно без подобных проектов и в принципе без энтузиастов. Однако в первую очередь должна быть отправная точка, готовая игра, которая работает стабильно и даёт возможность что-то изменять. Моды в этом плане имеют преимущество, ведь строятся на готовой игре, но имеют трудности с внесением изменений и некоторые ограничения из-за оригинальной архитектуры. А открытые движки в первую очередь должны хотя бы нормально работать. Смысл добавлять столько всего нового, если сама игра нормально не работает? Даже если вы просто обкатываете новые механики, кто будет делать всю остальную игру, в которой эти механики должны в перспективе работать? В ней же огромная куча логики и анимаций, на правильную реализацию которой уйдет не меньше уже потраченного времени.


        1. ProgerXP Автор
          18.04.2023 13:36

          Смысл добавлять столько всего нового, если сама игра нормально не работает?

          Вы ошибочно считаете, что я специально тратил время на добавление нестандартных для "Героев" функций. Это не так. Проект писался с нуля, а потому очень многие функции получились сами собой, просто потому, что они были заложены при проектировании. С точки зрения кода разница между картами с 2 уровнями или 22 — никакой. То же самое с битвой — хоть два героя, хоть десять, хоть поле 15×11, хоть 30×20. Да, на данный момент нет подходящей графики и баланса, но их легко добавить, если в движке нет фундаментальных ограничений.

          Я упоминаю о таких функциях только для того, чтобы показать отличия от существующих проектов. Жаль, если получается так, что за "багами не видно леса".


          1. Mangojuss
            18.04.2023 13:36
            +2

            Простите, если показался излишне резок.

            Просто как показал печальный опыт упомянутых выше проектов, пока сама основа движка не будет сделана на совесть, эти продукты никогда не станут восприниматься игроками, как нечто стоящее. Free Heroes 2 был заложен ещё до 2010 года. И после ~10 лет разработки он так и не приблизился к вменяемому состоянию. И был заброшен в середине 2010-ых. Как думаете, почему? И дело здесь не только в угасании интереса разработчиков. Просто проект стал тонуть в бесконечных багах, которые разработчики не могли исправить. На той стадии в движке нормально не работало ничего. От интерфейса и логики игры, до рендеринга и ИИ.

            В начале 2020-ых проект взялись делать другие люди, и делают до сих пор в рамках проекта fheroes2. За три с лишним года было переделано всё. И вовсе не в одиночку. И только сейчас проект, можно сказать, представляет из себя что-то стоящее, т.к. разработчики скрупулёзно воспроизводили всё, как было в оригинале, все анимации, всю логику сражений. Логику отрисовки карты и проходимость по этой самой карте. Без этого всего игра представляет интерес только лишь для казуальных игроков. А игроки в героев, как Вы сами понимаете, большей частью игроки тех времён. И они сразу почувствуют все недоделки.

            А насчёт фундаментальных ограничений... Их нет ни в fheroes2, ни в VCMI. Тут больше история о том, насколько код легко адаптировать под что-то новое.

            У VCMI, к слову, такая же длинная история. И проект сейчас тоже активнее стал развиваться, однако, проблем, которые видны игрокам невооружённым глазом, там ещё ох как предостаточно.

            Ваши старания благородны. Но, я боюсь, история начинается ровно так же, как и у схожих проектов: есть базовая реализация, но чтобы довести всё до ума понадобится настоящее чудо. (Ну или лет 10-15, а на столько едва ли у вас хватит энтузиазма, при всем моем уважении. )

            По мне так более ценным было бы, если хотя бы одна единственная часть игрового процесса была реализована на все 100, с правильной логикой, оптимизацией, отрисовкой и прочим. Нежели полностью вся игра работала "как придётся". Впрочем, подобные проекты, без финансирования, создаются годами... Надеюсь, вам хватит терпения и выдержки довести проект до рабочей стадии.


  1. appet1te
    18.04.2023 13:36
    -1

    Собрать молодых надо. Подключить мощности энтузиастов и соорудить сообщество разрабов по типу комьюнити линукса.


  1. Yuribtr
    18.04.2023 13:36

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

    Вот только один вопрос - что будет в качестве AI? Может сразу интеграцию с OpenAI запилить? )))


    1. mister_m0j0
      18.04.2023 13:36

      https://github.com/ihhub/fheroes2 есть вот такая реализация движка


  1. celen
    18.04.2023 13:36
    +9

    Встает вопрос - сколько изменений можно внести в оригинальный геймплей и баланс, прежде чем третьи герои перестанут ими быть?

    У вас есть фича - возможность битвы 3х1 на большом поле. В оригинале такой возможности нет. При этом мы все согласимся, что и интерфейсно, и гемплейно, и по балансу оригинальные homm3 очень далеки от совершенства и существует масса интереснейших механик, которые бы им пошли на пользу, но которых в оригинале нет. Однако, если в достаточной мере увлечься внедрением новых механик, получится игра, которая должна будет называться не "герои меча и магии 3 в браузере" а "herowo". В русской транслитирации звучит не слишком то благозвучно!

    В какой степени вы видите должным балансировать между подражанием классике и внесением новизны?


    1. ProgerXP Автор
      18.04.2023 13:36
      +6

      Встает вопрос - сколько изменений можно внести в оригинальный геймплей и баланс, прежде чем третьи герои перестанут ими быть?

      Ответа на этот вопрос нет, но вся соль в том, что HeroWO на него и не пытается ответить и не делает за вас этот выбор. В текущей версии все улучшения — косметические, но даже их можно убрать магическим переключателем "Classic" (https://herowo.game/?classic) и игра станет неотличимой от SoD, вплоть до криво расставленных элементов UI.

      Задача HeroWO — дать простор для творчества, а не использовать его. Согласитесь, не использовать имеющийся простор проще, чем использовать неимеющийся :)

      Однако, если в достаточной мере увлечься внедрением новых механик, получится игра, которая должна будет называться не "герои меча и магии 3 в браузере" а "herowo".

      Функционал геймплея предоставляется модулями, как то H3.js ("Heroes 3"). В H3 сейчас нет возможности запустить битву с 3+ участниками, но модуль может сделать это элементарно. Вы при запуске сами решаете, в какую модификацию хотите играть, никто вам "улучшенных Героев" навязывать не будет.

      В русской транслитирации звучит не слишком то благозвучно!

      Это такой тонкий троллинг от автора и отражение (скептического) отношения к собственным трудам.


  1. WASD1
    18.04.2023 13:36

    Круто, серьёзно.

    ПС
    Одно замечание: текущее ограничение по одновременности хода (пока игроки не увидели друг-друга впервые) - это не техническое ограничение, а ограничение ТЗ.
    Вы когда его отменяете - у вас получается пусть немного, но уже другая игра с другим балансом (кто быстрее нажал).


    1. im_last
      18.04.2023 13:36
      +1

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


      1. iuabtw
        18.04.2023 13:36
        +1

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

        Насколько помню, в текущей ХОТА решили этот вопрос откатом последнего хода. То есть, если во время одновременных ходов один игрок нападет на другого, то одновременные ходы отключаются и ход начинается сначала.


        1. im_last
          18.04.2023 13:36

          Ну так тут либо шашечки, либо ехать.


        1. AgentFire
          18.04.2023 13:36

          то одновременные ходы отключаются и ход начинается сначала.

          А это не тоже ли самое? Только еще и с машиной времени.


          1. Tsimur_S
            18.04.2023 13:36

            А это не тоже ли самое? Только еще и с машиной времени.

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

            То есть отключение одновременных ходов никакой информации вам не добавляет. Но дает пространство для абьюза машины времени.

            Что из этого хуже или лучше это уже другой вопрос.


            1. Aldrog
              18.04.2023 13:36

              Откат хода это гораздо более сильный мапхак, ведь вы перед ним успеваете разведать часть карты. И положение противника знаете более точно, чем просто «не больше, чем в двух ходах».

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

              Автоматическое отключение по расстоянию на самом деле звучит как интересная альтернатива откату хода как такому себе последнему рубежу.


        1. Tarakanator
          18.04.2023 13:36

          Мне кажется можно откатывать менее чем на ход.
          Ну как минимум если 3 игрок увидел 5, то ходы 1,2 игроков можно не трогать.


    1. ProgerXP Автор
      18.04.2023 13:36

      Вы когда его отменяете - у вас получается пусть немного, но уже другая игра с другим балансом (кто быстрее нажал).

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


  1. killerqueen
    18.04.2023 13:36

    Ждем Warcraft или Diablo)



  1. kekekeks
    18.04.2023 13:36

    Z-координата (глубина/дальность от глаз пользователя) вычисляется неясным образом.

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


    1. ProgerXP Автор
      18.04.2023 13:36

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


  1. alexander222
    18.04.2023 13:36
    +1

    Очень круто! завидую белой завистью. В 2016 году тоже начинал подобный проект, но пошел другим путем. Делал игру без серверной части. У меня была цель сделать максимально аутентично. Для того что бы поиграть нужно было иметь оригинальный файл с ресурсами, который нужно было загрузить в браузер. Там он парсился(локально, без отправки на сервер). Для отрисовки использовал WebGl- во первых это работало намного быстрее, во вторых позволяло использовать все оригинальные графические решения, например вода анимировалась честным colour cycling, цвета флагов подставлялись динамически, и так далее. К сожалению потом стало намного меньше свободного времени и проект забросил. успел сделать только до состояния что отображается карта, по которой можно походить героем и пособирать ресурсы. Как раз недавно откопал на битбакете исходники, думал не опубликовать-ли их, но почитав понял что они слишком ужасны что бы кому-то показывать. Раздумываю над тем не написать-ли статью про "анатомию" героев- в каком формате хранятся какие ресурсы, как их можно распаковать и как отрисовать в webgl.


    1. ProgerXP Автор
      18.04.2023 13:36
      +3

      Для того что бы поиграть нужно было иметь оригинальный файл с ресурсами, который нужно было загрузить в браузер.

      Это тупиковый подход, если планируешь хоть немного расширять оригинал (а такое желание возникнет рано или поздно, инфа 100%). Например, в картинки нельзя добавить 8-битный альфа-канал и 24-битный цвет. А уж парсить бинарные данные на JavaScript...

      Для отрисовки использовал WebGl- во первых это работало намного быстрее, во вторых позволяло использовать все оригинальные графические решения, например вода анимировалась честным colour cycling, цвета флагов подставлялись динамически, и так далее.

      Ни в коей мере никого не осуждаю, но, КМК, ошибка многих подобных проектов в том, что работа над ними проходила в режиме бесконечного времени. В этом случае, естественно, нужно сразу писать хорошую графическую подсистему и оптимальные алгоритмы. В реальном мире, увы, упускается очень важный факт: если есть готовый проект (пусть он и медленный, и глючный, и без слез в него не сыграешь), то его можно оптимизировать; если же половина не сделана вообще — то любые оптимизации не приблизят проект к готовности.

      Поэтому, как я не хотел бы анимировать воду через палитру, суровая реальность такова: либо у меня будет красивая вода, и кому-то придется дописывать все остальное, либо будет все остальное, и кому-то придется дописывать красивую воду.

      Вам я предлагаю дописать красивую воду — или, для начала, отрисовать мини-карту :)

      Раздумываю над тем не написать-ли статью про "анатомию" героев- в каком формате хранятся какие ресурсы, как их можно распаковать и как отрисовать в webgl.

      Что за вопрос, это же Хабр! Больше статей о "Героях", хороших и разных!


      1. alexander222
        18.04.2023 13:36

        Это тупиковый подход, если планируешь хоть немного расширять оригинал (а такое желание возникнет рано или поздно, инфа 100%).

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

        А уж парсить бинарные данные на JavaScript...

        Не все так плохо, там есть ArrayBuffer в котором это делать достаточно комфортно.

        В этом случае, естественно, нужно сразу писать хорошую графическую подсистему и оптимальные алгоритмы

        В то время мне очень интересна была технология webgl, плюс я никогда не работал с графикой, одна из целей была понять как вообще рендерится графикп и научитьсч писать шедеры. Не могу сказать что после мне это пригодилось, но цель была достигнута. Да и на самом деле все не так сложно, на пусть и коривую/не оптимальную, но вполне рабочую графическкю ситему ушло, на сколько я помню, 2-3 календарных месяца(занимался по немногу, в основном в электричке по дороге на работу и обратно)

        для начала, отрисовать мини-карту :)

        Спасибо за предложение. Пока ничего не обещаю, но постараьсь выкроить время, очень заманчиво.


  1. FanatPHP
    18.04.2023 13:36
    +4

    Очень круто.
    Но немного печалит TLDR. А именно сочетание "Неограниченное число всего на свете" и "багов полно и все тормозит".
    Напоминает анекдот "а теперь мы попробуем со всей этой фигней взлететь".
    Мне кажется, что прототип, не столь богатый функционально, но рабочий, было бы проще разработать и в дальнейшем развивать.


    У нас был тренер по аджайлу, который очень любил рассказывать историю про Скрепыша. Что Микрософт вбухала в этого анимированного помощника кучу сил, научила его разговаривать разными голосами, делать смешные ужимки, использовать кучу разных персонажей… Но в итоге он оказался никому не нужным. "Если бы" — заключал тренер — "Микрософт выкатила концепт с гораздо меньшим количеством кул фич, и увидела что не взлетело, то сэкономила бы себе кучу ресурсов".


    1. ProgerXP Автор
      18.04.2023 13:36

      Напоминает анекдот "а теперь мы попробуем со всей этой фигней взлететь".

      Ну... вы смотрели код в репозитории? На ваш взгляд, его качество действительно можно охарактеризовать как "фигня"?

      Как я уже отвечал выше, не нужно считать, что я специально тратил время на добавление нестандартных для "Героев" функций. Текущий вариант — это и есть минимальный прототип.


      1. FanatPHP
        18.04.2023 13:36
        +3

        Ну… вы смотрели код в репозитории? На ваш взгляд, его качество действительно можно охарактеризовать как "фигня"?

        Нет, не смотрел. Мой комментарий был вообще не про код. Слово, которое вас так обидело, относится не к коду, а к анекдоту. Который вы, судя по всему, не знаете, хотя у него борода длиннее экватора.


        Но спасибо что спросили, я сходил, посмотрел. Ответ на ваш вопрос — да, можно. И нужно. Классическая лапша, из палаты мер и весов. Нет, я все понимаю — это прототип, собранный на коленке. Но ведь не я же вас за язык тянул на предмет кода, правда же?


        Как я уже отвечал выше, не нужно считать, что я специально тратил время на добавление нестандартных для "Героев" функций. Текущий вариант — это и есть минимальный прототип.

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


  1. Urvdmih
    18.04.2023 13:36
    +1

    "Все четыре официальных продолжения провалились."

    Пятая часть же хорошая была... И вроде по деньгам в плюс вышла.


    1. ednersky
      18.04.2023 13:36
      +3

      а снова и снова переигрывают только в третью часть


      1. ivorrus
        18.04.2023 13:36

        Четвертые и пятые тоже имеют своих поклонников. На упомянутом в статье forum.heroesworld.ru раздел по 5ым вполне себе живой.


        1. ednersky
          18.04.2023 13:36

          там по комменту раз в три месяца, можно считать что вообще все форумы там мертвы


  1. Dovgaluk
    18.04.2023 13:36

    Интерпретируемый язык сам по себе сократит время разработки

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


    1. ProgerXP Автор
      18.04.2023 13:36

      Частично соглашусь, однако с интерпретируемым языком есть степень свободы, при которой опытом/навыками/инструментами можно частично компенсировать необходимость в тестах и отлове багов, и тогда может получиться быстрее. А может и нет. "Геройских" проектов на Си существует достаточно, для меня смысла начинать еще один было мало.


    1. FanatPHP
      18.04.2023 13:36

      Вы что-то путаете. Тесты пишут для программ на любых языках.
      Если вы про статические анализаторы ("работу компилятора"), то они уже есть, их писать не надо. "Баги в продакшене" опять же есть у программ на любом языке. Компилятор — это не волшебный удалитель багов.


      1. Dovgaluk
        18.04.2023 13:36

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

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


        1. FanatPHP
          18.04.2023 13:36

          Как я писал выше, никакие тесты для этого писать не надо, достаточно запустить статический анализатор. Они сейчас есть для всех языков. И вы тоже узнаете об ошибке сразу.


          1. Dovgaluk
            18.04.2023 13:36
            -1

            Статический анализатор может находить только самые простые проблемы. Он же не знает, что именно будет передано в качестве параметра или что будет находиться в каком-нибудь списке.


            1. FanatPHP
              18.04.2023 13:36

              Вы так старательно пытаетесь натянуть сову на глобус, что это даже умиляет :)


              Прекрасно статический анализатор отлавливает ошибки с передачей параметра, если функция, из которой он берется, может вернуть не то.
              А ошибки рантайма, когда список готовится той же программой, и компилятор не увидит.


              Давайте, придумайте еще что-нибудь, чтобы оправдать вашу нелюбовь к недоязыкам. Это нас всех развлечёт.


            1. SagePtr
              18.04.2023 13:36
              +2

              Он же не знает, что именно будет передано в качестве параметра

              Ну и компиляция на этом этапе тоже ничего не обнаружит, так что в этой ситуации компилируемый язык у интерпретируемого не выиграет. Разве что проблема может быть с типом передаваемого параметра, но здесь ещё на этапе разработки IDE подчеркнёт.


          1. KvanTTT
            18.04.2023 13:36
            -1

            Зачем нужен статический анализатор, когда может быть доступен сразу компилятор?


            1. FanatPHP
              18.04.2023 13:36
              +1

              Я даже не знаю, как вам ответить, все такое вкусное. Вам в контексте обсуждаемого вопроса или без него?


  1. stanislavskijvlad
    18.04.2023 13:36
    +1

    Вудуш одобряет :) Вам бы в команду HotA Crew.


    1. mapron
      18.04.2023 13:36

      Вы походу переоцениваете открытость команды HotA Crew)


  1. ris58h
    18.04.2023 13:36

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


    1. ProgerXP Автор
      18.04.2023 13:36

      Да, ПКМ должен работать. Мака не имею, проверить не могу, увы.


  1. HiLander
    18.04.2023 13:36

    Похоже, я что-то не так делаю, но в "Классическом" режиме дальше выбора карт продвинуться так и не удалось...

    А так низкий поклон за монументальный объем работ по любимой стратегии.

    П.С. Интересно, кто-то соберется сделать аналог по не менее гениальному но куда менее известному M.A.X.? Я бы поучаствовал...


    1. mapron
      18.04.2023 13:36

      https://www.maxr.org/docs.php?id=9&s=3c45973c4ffa81da9794a62967d89d50

      https://github.com/dkargin/maxr

      я пару лет назад устанавливал. играбельно.


  1. domix32
    18.04.2023 13:36
    +2

    А почему баги не вести в том же github, а не на форуме? Шаблоны для issue повторяют то что тегирует форум


    1. mapron
      18.04.2023 13:36

      Ну я создавал автору issue, он отвечает, так что видимо это приемлемая для него альтернатива.


    1. ProgerXP Автор
      18.04.2023 13:36

      Я не против GitHub. На форуме для многих нехабровчан должно быть проще, только и всего.


  1. acordell
    18.04.2023 13:36
    +2

    О, я тут случайно застукал своего ребенка играющего в HMM 3. 19 лет ребенку. Игра старше его. Так что даже молодежь играет!


    1. mapron
      18.04.2023 13:36

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


  1. eog25by
    18.04.2023 13:36

    Есть вариант, что z-координата ни что иное как обозначение подземки. 0/1 или четное/нечетное. можно там поиграться попробовать