Как стать автором
Обновить

Проклятые Земли. Изменяем движок игры! Подробности + ПРИМЕР реверсинга движка

Время на прочтение21 мин
Количество просмотров15K

Всех приветствую! Наверняка многие играли в легендарную игру от компании Nival Interactive - Проклятые Земли. Я в неё поиграла первый раз, кажется, в 2016 году, и с тех пор у меня есть мечта - сделать аддон к этой замечательной игре. И сделать это одной. С начала и до конца, включая даже редакторы. И часть из этого я расскажу уже сейчас.

Если вам не интересна часть про ассемблер, просто промотайте её. В начале и в конце я рассказываю о том, что вы уже можете пощупать.

Движок ПЗ позволяет добавлять даже колесницы (с оговорками, но всё же реализуемо)
Движок ПЗ позволяет добавлять даже колесницы (с оговорками, но всё же реализуемо)

Предисловие

В своей прошлой статье Проклятые Земли. Освежаем геймплей мне удалось рассказать как про необычные фишки, которые есть в ПЗ, так и продемонстрировать создание и последующую работу некоторых интересных модификаций, которые относительно просто реализовать для Проклятых Земель. Модификаций, как, например, трата запаса сил, зависящая от его количества, или же разблокировка скрытых заклинаний. Такие модификации, которые позволяют так или иначе прочувствовать геймплей этой замечательной игры более свежо, будут актуальны всегда! И чем больше у создателей модов и аддонов к игре возможностей для кастомизации, тем качественнее выйдет мод или аддон. И да, я готовлюсь сделать Standalone Add-on для Проклятых Земель! А SpellAddon в этом поможет. Но пока что показывать нечего.

В этот раз я хочу рассказать об остальных занятных фишках и неочевидных механиках в Проклятых Землях, а заодно максимально наглядно показать как я разбирала движок этой замечательной игры (в том числе и для создания своего аддона!) чтобы помочь каждому начинающему разработчику понять, как с помощью Cheat Engine или Interactive Disassembler (IDA) можно модифицировать движок любимой игры, ну или хотя бы поломать... Погнали!

Почему Проклятые Земли это круто

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

Наверняка каждый игравший в ПЗ помнит про удар со спины в голову. Это такой мощный скрытный удар, который разносит любого противника, который ещё не задетектил героя. Множитель для удара со спины записан в конфиге игры (Проклятые Земли\Config\ai.reg). Узнаёте проект "Геноцид", в котором людоед убивается на лёгкой сложности? Он самый! Удар со спины так хорош, что им можно убивать даже сильнейших врагов (а на лёгком уровне сложности - даже если те имеют резист от оружия).

76 - достаточный для победы урон
76 - достаточный для победы урон

Удар в голову наносит "критический" урон. И это не особенный критический урон, а просто очередной модификатор. Например, по руке гуманоида на самом деле прилетает 102% урона, по телу - 101%, по ноге - 100.5%, по голове - 303%. Уязвимость и здоровье частей тела записаны в базе данных игры (Проклятые Земли\Res\database.res), где находятся параметры прототипов персонажей и характеристики их рас. И только игрок может целенаправленно выбирать куда бить - противники так не умеют (пока что). Но в ПЗ всё гораздо интереснее, чем просто возможность ударить в голову или не в голову.

Для начала стоит сказать, что у персонажа от 1 до 6 условных частей тела - голова, туловище, левая рука, левая нога, правая рука, правая нога. Нанесение повреждений влияет на скорость передвижения и атаки. Скриншот будет лучше кучи слов.

Зависимость действий от повреждения рук юнита. На 2-й стадии 49 хп, а не 45, потому что эта арбалетчица успела отрегенирировать.
Зависимость действий от повреждения рук юнита. На 2-й стадии 49 хп, а не 45, потому что эта арбалетчица успела отрегенирировать.

На скриншоте видно, как повреждения влияют на скорость атаки юнита. И работает это почти пропорционально! Юнит гуманоидного вида с здоровьем руки меньше 50% будет атаковать медленнее на 8/15 от своей базовой скорости атаки. А уже с полностью отбитой (не обязательно сломанной навсегда) рукой или с 2-мя руками, у которых здоровье меньше 50%, юнит будет атаковать аж в 3 раза медленнее.

С ногами ситуация похожая. И очень даже логичная. Тигры, кабаны, волки, жабы и прочие четырёхлапые юниты атакуют кусая или выплёвывая что-то, потому ранения по передним лапам не оказывают на их атаку замедляющего эффекта. У них вместо 2-х рук - ещё 2 ноги. Если здоровье ноги опускается ниже 50%, то у юнита пропадает возможность бегать и немного замедляется скорость каждого из его типов передвижения. Так что стрельба по ногам очень полезна даже в оригинале, если закрыть глаза на тот факт, что стрелковое оружие почти не наносит урон. Благо, современные редакторы (коих очень много, в том числе свой создала и я) позволяют легко редактировать параметры в базе данных ПЗ.

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

Надпись "Слишком тяжело!" значит что персонаж не сможет бегать. Впрочем, поломка доспехов вполне может это исправить прямо на поле боя.
Надпись "Слишком тяжело!" значит что персонаж не сможет бегать. Впрочем, поломка доспехов вполне может это исправить прямо на поле боя.

А что насчёт абсолютно независимых и очень логично продуманных типов восприятия, которых в игре аж 5 штук?

Скриншот из хекс-редактора HxD. Отслеживание обонянием и выслеживание по следам в ПЗ даже работают, но, к сожалению, у юнитов не прописана реакция. Потому они ничего не делают с тем, что, допустим, почуяли запах Хадора или увидели следы Эйлы.
Скриншот из хекс-редактора HxD. Отслеживание обонянием и выслеживание по следам в ПЗ даже работают, но, к сожалению, у юнитов не прописана реакция. Потому они ничего не делают с тем, что, допустим, почуяли запах Хадора или увидели следы Эйлы.

Слух у юнитов работает отлично! Как я уже говорила в своей прошлой статье, идущий юнит издаёт немного звука, а бегущий так вообще страшно шумит, как тому и положено быть. Но наверняка не все знают, что ползающий персонаж тоже издаёт некоторый шум. То что написано в подсказках - чистая правда. А то что в оригинальных Проклятых Землях ползающего персонажа не слышат, так это не потому, что он бесшумен. Просто противники... Они немного... Глухие...

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

Модификаторы, зависимые от положения персонажа - будь то слышимость или заметность прописаны в движке игры. На кортанах персонаж незаметнее в 1.5 раза, лёжа - в 2. Но в базе данных с персонажами можно прописывать (как показано через HxD) модификаторы для типов восприятия. То есть, если прописать хороший слух для персонажей (допустим, орков, противников героев), то они смогут слышать ползущего главного героя (или какого-либо своего противника), но вот крадущийся персонаж, являющийся для них врагом, не будет для них слышим, хоть и будет замечаем зрением с большего расстояния.

Что касаемо зрения - наверняка настоящие задроты ПЗ знают, что людоедов и троллей видно издалека. И это именно потому, что у них стоит модификатор обнаружаемости зрением равный 1.5.

А обнаружение жизни - это безусловное обнаружение противников, которое не зависит от позы, надетой одежды, и препятствий между двумя юнитами. На него может повлиять только тот же модификатор обнаруживаемости и заклинание Мертвец (Lichdom в базе игры).

Кстати, стоит сказать конкретно про нежить в Проклятых Землях. Зомби, скелеты, огоньки и прочая магическая не совсем живность имеет как зрение, так и обнаружение жизни. Так что Бабуру этот вернувшийся чувак сказал неверную инфу!

Диалог с Бабуром "О Золотой долине"
Диалог с Бабуром "О Золотой долине"

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

Кстати, про магию чувств. Она может быть вполне себе боевой. То есть помогать не "не сдохнуть", а именно что "убить". Есть такие 2 замечательных заклинания: "Невидимость" и "Неслышный шаг". Наложив заклинание "Невидимость" на противника, можно убить его перед глазами другого противника, и в таком случае у второго не активируется GPS, по которому он станет преследовать героя! А "Неслышный шаг" это вообще не очень точное название. Это заклинание с любым эффектом выше 0 делает все действия персонажа неслышными для мобов на карте, в том числе и касты заклинаний. Вот пример с лечением за спиной императорского гвардейца.

Реакция (и её отсутствие) у гвардейца
Реакция (и её отсутствие) у гвардейца

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

А что насчёт сетевой игры? Она ведь тоже есть в Проклятых землях! Забудьте на минуту про скучные квесты оригинальной сетевой игры с похожими друг на друга квестами (которых на всю сетевую игру каких-то 28 штук), кривым балансом для магов-стравливателей-багоюзеров и явно скудным общим наполнением (нет озвучки, мало NPC, скрипт одного квеста можно буквально записать на ладони)!

Скриншот игрока из Classic-Mod
Скриншот игрока из Classic-Mod

Самый популярный, масштабный и востребованный на данный момент мод (из сетевых) Classic-mod исправляет все эти проблемы. Силами фанатов этой замечательной игры в нём есть и озвучка, и даже абсолютно новые механики вроде наёмников-некромантов (с которыми автору Николаю немного помогла и я), сюжетных квестов которые открываются один за другим как в сингл-плеере и переходов на другие локации. Вот только в ПЗ создаваемый для персонажа ник ограничен жалкими 10-ю символами, да и с параметрами всё грустно. "Сила", "Ловкость" и "Разум" при создании персонажа ограничены рамками от 15 до 35, да ещё и общая их сумма не может отличаться от 75. А ПЗ, с каким бы модом она ни была - это не донатная ММОРПГ! Кто хочет челенджа - пускай создаёт слабого персонажа, кто хочет изнеженную Великую Магичку - пусть делает 15 25 45 персонажа. 0 проблем.

Сложность #1. Находим и изменяем видимые глазом числа.

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

Сначала займёмся параметрами. На самом деле, не всегда необходимо реверсить движок для того, чтобы добиться чего-то хорошего. Для начала мы попробуем через HxD на халяву найти границы, в рамках которых можно создавать персонажа для сетевой игры. А почему бы и нет? Вполне возможно, что там где-то записаны заветные 35, 15, 100 и 75 (сумма 3-х характеристик - разума, силы и ловкости, названная "Баланс").

В редакторе HxD есть удобный поиск, которым можно искать даже числа с плавающей точкой. Когда я этим занималась, я уже знала что сила, ловкость, разум и рост - это числа дробные, так что мне было немного проще в процессе их поиска. Сразу стоит сказать - такие вещи обычно записываются рядом. Очень невелика вероятность того, что компилятор или разработчик решат разнести 2 параметра отвечающие за границы одного и того же в разные части файла.

2 далёких параметра, равные нужным нам.
2 далёких параметра, равные нужным нам.

Когда нужные нам числа, например, 15 и 35, находятся так далеко друг от друга (больше чем сотни байт), можно предположить, что и то и другое - совсем не то, что мы ищем...

Параметры, похожие на нужные.
Параметры, похожие на нужные.

А вот когда они так близко - то ручки уже тянутся их поменять на 5 и 45 и посмотреть что же из этого выйдет! И таки не зря...

Всё получилось!
Всё получилось!

Результат есть - поехали дальше. На очереди никнеймы!

Сложность #2. Познаём мощь Cheat Engine

Запускаем Cheat Engine. Не важно какой версии, главное чтобы мы выбрали процесс game.exe (с иконкой золотого дракончика) - это и будут Проклятые Земли. Если они не запущены, то это надо исправить, а затем снова войти в выбор процесса и таки выбрать ПЗ.

Cheat Engine сама показывает куда сначала надо тыкнуть.
Cheat Engine сама показывает куда сначала надо тыкнуть.

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

На всякий случая в Cheat Engine выберем Value Type = All и Scan Type = Unknown initial value. У нас пропадёт строка в которую мы могли писать что-то для поиска и останется активная кнопка First Scan, которую и надо нажать, ведь мы хотим найти длину ника у персонажа.

Игра нам даст миллионы разных значений и это не должно пугать. Пойдём в игру и сменим персонажу ник, а точнее его длину. И после этого снова запустим скан в Cheat Engine, но в этот раз уже нажимая на Next scan - в этом весь смысл.

Извращения с ником персонажа ради изменения его длины
Извращения с ником персонажа ради изменения его длины

Для отсеивания надо выбирать некоторые критерии. Например, если мы уменьшили ник персонажа, рационально будет предположить, что длина его изменилась. И скорее всего даже уменьшилась. Потому выбирать стоит Decreased value, а потом запускать Next scan. Increased value - для снова увеличенного ника. Unchanged value запускать тоже очень важно, ведь есть постоянно меняющиеся значения, которые очень хотелось бы отбрасывать в том числе, и желательно пораньше.

После долгих отсеиваний неизбежно будет найдено число, которое меняется так же, как меняется ник персонажа. На это значение надо тыкнуть и красной стрелкой закрепить его внизу. А затем нажатием на него ПКМ и выбором Find out what accesses this address (хоткей F5) начинаем проверять что считывает длину ника и что соответственно не даёт вводить длинный ник. Опытным путём максимально легко устанавливается, что при вводе символа в поле никнейма персонажа, длину считывают 7 инструкций. Но вот когда никнейм уже составляет 10 символов, 2 инструкции перестают эту длину считывать.

Такая вот картина получилась у меня.
Такая вот картина получилась у меня.

Команда по адресу 0x0065CAE8 расположена раньше, чем другая не выполнившаяся, так что прыгнем к ней. Для удобства можно открыть движок ПЗ game.exe в IDA.

Какая-то проверка выше может обходить этот блок инструкций.
Какая-то проверка выше может обходить этот блок инструкций.

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

Команда прыжка 7D 20
Команда прыжка 7D 20

Заменим команду 7В 20 на 90 90 (90 - это NOP, команда не создающая какой-то новый результат). Таким образом прыжка через код не будет.

И это не сработает, потому что прыжок происходит ещё до этого.

2 прыжка.
2 прыжка.

А их тут всего ровно 2, как можно заметить, если открыть game.exe в IDA. Значит более ранний прыжок, который мы видим в дизассемблере - именно тот, который не даёт вводить символы дальше. Уберём и его. Получится такой код:

.text:0065CA9A                 nop
.text:0065CA9B                 nop
.text:0065CA9C                 mov     edx, [esi+0CCh]
.text:0065CAA2                 lea     eax, [esp+34h+var_24]
.text:0065CAA6                 push    edx             ; int
.text:0065CAA7                 push    ecx             ; lpString
.text:0065CAA8                 mov     ecx, esp
.text:0065CAAA                 mov     [esp+3Ch+arg_0], esp
.text:0065CAAE                 push    eax
.text:0065CAAF                 call    sub_7006E5
.text:0065CAB4                 lea     ecx, [esp+3Ch+var_14]
.text:0065CAB8                 push    ecx             ; int
.text:0065CAB9                 mov     ecx, [esi+4]
.text:0065CABC                 call    sub_5F8340
.text:0065CAC1                 mov     edx, [esi+0BCh]
.text:0065CAC7                 mov     ebp, [esi+0B4h]
.text:0065CACD                 mov     ecx, [eax]
.text:0065CACF                 sub     edx, ebp
.text:0065CAD1                 cmp     ecx, edx
.text:0065CAD3                 nop
.text:0065CAD4                 nop
.text:0065CAD5                 lea     eax, [esp+34h+var_24]
.text:0065CAD9                 mov     ecx, edi
.text:0065CADB                 push    eax
.text:0065CADC                 call    sub_700AA9
.text:0065CAE1                 mov     byte ptr [esi+0E6h], 1
.text:0065CAE8                 mov     eax, [esi+0E0h] ; check
.text:0065CAEE                 inc     eax
.text:0065CAEF                 mov     [esi+0E0h], eax

Старый код:

Hidden text
.text:0065CA9A                 jge     short loc_65CAF5 ; jump
.text:0065CA9C                 mov     edx, [esi+0CCh]
.text:0065CAA2                 lea     eax, [esp+34h+var_24]
.text:0065CAA6                 push    edx             ; int
.text:0065CAA7                 push    ecx             ; lpString
.text:0065CAA8                 mov     ecx, esp
.text:0065CAAA                 mov     [esp+3Ch+arg_0], esp
.text:0065CAAE                 push    eax
.text:0065CAAF                 call    sub_7006E5
.text:0065CAB4                 lea     ecx, [esp+3Ch+var_14]
.text:0065CAB8                 push    ecx             ; int
.text:0065CAB9                 mov     ecx, [esi+4]
.text:0065CABC                 call    sub_5F8340
.text:0065CAC1                 mov     edx, [esi+0BCh]
.text:0065CAC7                 mov     ebp, [esi+0B4h]
.text:0065CACD                 mov     ecx, [eax]
.text:0065CACF                 sub     edx, ebp
.text:0065CAD1                 cmp     ecx, edx
.text:0065CAD3                 jge     short loc_65CAF5 ; jump
.text:0065CAD5                 lea     eax, [esp+34h+var_24]
.text:0065CAD9                 mov     ecx, edi
.text:0065CADB                 push    eax
.text:0065CADC                 call    sub_700AA9
.text:0065CAE1                 mov     byte ptr [esi+0E6h], 1
.text:0065CAE8                 mov     eax, [esi+0E0h] ; check
.text:0065CAEE                 inc     eax
.text:0065CAEF                 mov     [esi+0E0h], eax

Результат:

Заменённые байты через программу Cheat Engine.
Заменённые байты через программу Cheat Engine.

Глупые проверки на специальные символы мы тоже уберём. Зачем они в конце концов там нужны, если нам хочется писать упоротые смайлики в никнейм?

Проверки на разные символы.
Проверки на разные символы.

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

Прыжок если символ был не правилен.
Прыжок если символ был не правилен.

0F 84 3D 01 00 00 тоже занопываем (делаем вместо прыжка - 90 90 90 90 90 90). И ник персонажа становится таким:

Топовый персонаж!
Топовый персонаж!

Конечно, такого улётно крутого пацана я создала себе на память.

PERSONAZH уже пошёл гулять!
PERSONAZH уже пошёл гулять!

И зачем было так сильно урезать возможности для создания никнейма любимому главному герою?.. Даже Хадор-Забияка - уже длиннее чем 10 символов... Всё нормально отображается и прекрасно работает...

Сложность #3. Что находится в самом конце *.mob-файле?

Подзаголовок являтся тем самым вопросом который я задала себе в 16-17 лет, впервые открыв *.mob-файл ПЗ. Ах, до сих пор я помню восторг от того, как у меня получилось буквально прочитать секцию графа проходимости, не прибегая к помощи других людей...

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

Дело было очень давно. Я была ещё совсем малышка и мне было очень интересно: что это за хрень записана в конце каждого основного *.mob-файла? Вернёмся в 2018 год и откроем моб. Используем HxD, конечно же.

Начало *.mob-файла.
Начало *.mob-файла.

В самом начале сигнатура *.mob-файла ПЗ, а затем - адрес графа проходимости. В HxD можно очень удобно туда переместиться. Либо скопировать адрес (UInt32 справа) и затем, нажав Ctrl+G, вставить его в поле, либо же просто нажать на тот go to: и ещё быстрее туда переместиться. Поехали! Для примера берём файл из Проклятых Земель: Затерянных в астрале! Карта - "Город Садрок и его окрестности" (zone35.mob).

Я выделила 16 байт. Это 4 значения по 4 байта. Рассмотрим поподробнее:

  1. Сигнатура графа проходимости.

  2. Размер секции графа проходимости включая и заголовок.

  3. Размер графа проходимости по координате X.

  4. Размер графа проходимости по координате Y.

Ниже расположен сам его величество граф проходимости, который помогает без ущерба для производительности ходить персонажу на 500 метров, чётко огибая все возможные объекты на карте!

Интересный факт: самая длинная траектория в ПЗ - это путь от северо-западного угла до юго-восточного угла на карте "Портал".

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

Куча байтиков.
Куча байтиков.

Видно что "на краю" только FF, а уже повыше - не только. Если попробовать всё забить нулями, то игра вылетает при заходе на Город Садрок и его окрестности. Если же всё забить максимальными значениями (FF), то получится вот это:

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

Таким образом можно сделать вывод, что это действительно граф проходимости. Зная то, что карты в ПЗ сделаны из секций (квадратиков), вывод о том, что граф проходимости состоит тоже из квадратиков напрашивается сам...

Анализировала это дело я долго. Пока ехала куда-то. Пока бухала на улице. И ещё где-то, и ещё как-то. Если внимательно посмотреть на структуру секции с графом проходимости на каждой из карт, то видно, что, граф проходимости собственной персоной состоит из блоков, содержимое которых соотносится как 16 к 1 к 2. На скриншоте выше можно заметить, что "отдел" не забитый нулями имеет размер 1024 байт, а до следующего аналогичного "отдела" - 192 байт. Открывая карты Проклятых Земель (не аддона ПЗ: ЗвА) можно убедиться в том, что секции всё же 3. И нулями забита именно бесполезная информация (скорее всего использовалась для редакторов Нивалом). Если её поменять, то ничего не изменится в поведении кого-либо на карте.

Граф проходимости карты "Портал" из ПЗ.
Граф проходимости карты "Портал" из ПЗ.

Здесь наглядно видно "структуру". И раз 3/19 бесполезны, то мы с ними ничего и не делаем. Далее можно логически подумать, что вокруг 1-й клетки должны быть проходы на окружающие её клетки. 4 или 8. Теперь смотрим максимально внимательно на начало графа проходимости. Для примера всё так же берём Город Садрок и его окрестности.

Граф проходимости карты zone35.mob из аддона ПЗ: ЗвА
Граф проходимости карты zone35.mob из аддона ПЗ: ЗвА

Зная уже что по FF FF ходить нельзя (точнее путь наш не считается валидным), с самого начала напрашивается вывод о том, что это - ничто иное, как угол! Угол карты, который является самым непроходимым (в среднем) местом в ПЗ! Ведь будучи строго на углу, пойти можно только прочь от угла в обратном направлении, или вдоль одной из сторон. Другие 5 сторон - путь в тупик, край карты. По крайней мере это будет так, если всё же от каждой клетки идёт именно по 8 путей. И мне сложно представить какую-то иную ситуацию.

Но давайте не спешить. В заголовке мы видели размер карты. То что это размер карты - сомнений 0, ведь на каждой карте он совпадает с её истинным размером, только в 4 раза меньше. У портала, например, указано 128 на 128. А на деле его размер 512 на 512. А в секциях - 16 на 16. У Города Садрока - 64 на 64, 256 на 256, 8 на 8.

Возьмём размер графа проходимости - 0x98000 или 622592. Разделим это число на 64 и затем ещё раз на 64 (на X и на Y соответственно). Получим 152. 152 как раз делится на 19, что даёт нам возможность предполагать, что всё же с клетки 8 проходов, а не 4. Уж слишком симметрично расположены FF FF без всяких "одиночных" "FF".

Испортив только одну секцию графа проходимости, можно прийти к выводу, что вообще ничего не поменялось. Если испортить 80% графа проходимости, то тоже ничего не поменяется. Но вот если портить его по чуть-чуть с самого начала, то всё же можно наткнуться на то, что часть карты становится непроходимой.

Если внимательно присмотреться к непроходимым байтикам (FF FF), то видно, что у нас сначала 3 непроходимых клетки, потом 3 проходимых, а затем 2 непроходимых. А внимательно протестировав карту с испорченным графом проходимости, вывод можно сделать только один!

Структура клетки - фундаментального элемента графа проходимости ПЗ. В хекс-редакторе показаны 6 клеток от верхнего левого угла.
Структура клетки - фундаментального элемента графа проходимости ПЗ. В хекс-редакторе показаны 6 клеток от верхнего левого угла.

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

Hidden text

Код конвертации графа проходимости в картинку:

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
		char pppp[] = { 0x42, 0x4D, 0x1A, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
		char mainstr[256];
		printf("file name (must be pure AI graph file):\n");
		scanf("%s", mainstr);
		DWORD dwX, dwY;
		DWORD dwTemp, dwTemp2, dwLolKek = 0x31, dwSignature, dwFiles, dwFirSec, dwLasSec;
		char *bNum;
		bNum = (char*)malloc(20);
		HANDLE hFile = CreateFileA(mainstr,GENERIC_READ,4,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
		int a = strlen(mainstr);
		mainstr[a-4] = '-';
		mainstr[a] = '.';
		mainstr[a+1] = 'b';
		mainstr[a+2] = 'm';
		mainstr[a+3] = 'p';
		mainstr[a+4] = 0;
		printf("X & Y size (in *.SECs)\n");
		scanf("%d %d", &dwX, &dwY);
		dwX = dwX*8;
		dwY = dwY*8;
		
		char bFile[dwX*dwY*152], rFile[dwX*3*dwY*3*8*3+54], bTemp[20];
		HANDLE hFile4 = CreateFileA(mainstr,GENERIC_ALL,4,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
		ReadFile(hFile,bFile,dwX*dwY*152,&dwTemp,NULL);
		DWORD dwC1 = 0;
		*((DWORD*)(pppp+18)) = dwX*3;
		*((DWORD*)(pppp+22)) = dwY*3*8;
		WriteFile(hFile4, pppp, 0x36, &dwTemp2, NULL);
		register DWORD dwC3 = 0;

		for (register DWORD dwC = 0; dwC < dwY*8; dwC ++) {
			for (register DWORD dwC2 = dwC1; dwC2 < dwC1+dwX*16; dwC2 += 0x10) {
				*((BYTE*)(rFile+dwC3+3)) = (BYTE)*((BYTE*)(bFile+dwC2+0));
				*((BYTE*)(rFile+dwC3+4)) = (BYTE)*((BYTE*)(bFile+dwC2+0));
				*((BYTE*)(rFile+dwC3+5)) = (BYTE)*((BYTE*)(bFile+dwC2+0));
				
				*((BYTE*)(rFile+dwC3+0)) = (BYTE)*((BYTE*)(bFile+dwC2+2));
				*((BYTE*)(rFile+dwC3+1)) = (BYTE)*((BYTE*)(bFile+dwC2+2));
				*((BYTE*)(rFile+dwC3+2)) = (BYTE)*((BYTE*)(bFile+dwC2+2));
				
				*((BYTE*)(rFile+dwC3+0+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+4));
				*((BYTE*)(rFile+dwC3+1+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+4));
				*((BYTE*)(rFile+dwC3+2+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+4));
				
				*((BYTE*)(rFile+dwC3+0+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+6));
				*((BYTE*)(rFile+dwC3+1+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+6));
				*((BYTE*)(rFile+dwC3+2+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+6));
				
				*((BYTE*)(rFile+dwC3+3+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+8));
				*((BYTE*)(rFile+dwC3+4+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+8));
				*((BYTE*)(rFile+dwC3+5+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+8));
				
				*((BYTE*)(rFile+dwC3+6+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+10));
				*((BYTE*)(rFile+dwC3+7+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+10));
				*((BYTE*)(rFile+dwC3+8+18*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+10));
				
				*((BYTE*)(rFile+dwC3+6+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+12));
				*((BYTE*)(rFile+dwC3+7+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+12));
				*((BYTE*)(rFile+dwC3+8+9*dwX)) = (BYTE)*((BYTE*)(bFile+dwC2+12));
				
				*((BYTE*)(rFile+dwC3+6)) = (BYTE)*((BYTE*)(bFile+dwC2+14));
				*((BYTE*)(rFile+dwC3+7)) = (BYTE)*((BYTE*)(bFile+dwC2+14));
				*((BYTE*)(rFile+dwC3+8)) = (BYTE)*((BYTE*)(bFile+dwC2+14));
				
				*((BYTE*)(rFile+dwC3+3+9*dwX)) = 0;
				*((BYTE*)(rFile+dwC3+4+9*dwX)) = 0;
				*((BYTE*)(rFile+dwC3+5+9*dwX)) = 255;
				dwC3 = dwC3 + 9;
			}
			dwC1 += dwX*19;
			dwC3 += dwX*18;
		}		
		WriteFile(hFile4, rFile, (dwX*dwY*27*19)/2, &dwTemp2, NULL);
		CloseHandle(hFile4);
		CloseHandle(hFile);
	return 0;
}

Код конвертации картинки в граф проходимости:

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main () {
		char pppp[] = { 0x42, 0x4D, 0x1A, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE4, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
		char mainstr[256];
		scanf("%s", mainstr);
		DWORD dwX, dwY;
		DWORD dwTemp, dwTemp2, dwLolKek = 0x31, dwSignature, dwFiles, dwFirSec, dwLasSec;
		char bFile[0x260000], rFile[0x402000], bTemp[20], *bNum;
		bNum = (char*)malloc(20);
		HANDLE hFile = CreateFileA(mainstr,GENERIC_READ,4,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
		int a = strlen(mainstr);
		mainstr[a-4] = '-';
		mainstr[a] = '.';
		mainstr[a+1] = 'A';
		mainstr[a+2] = 'I';
		mainstr[a+3] = 'G';
		mainstr[a+4] = 0;
		HANDLE hFile4 = CreateFileA(mainstr,GENERIC_ALL,4,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
		ReadFile(hFile,rFile,0x402000,&dwTemp,NULL);
		DWORD dwC1 = 0;
		scanf("%d %d", &dwX, &dwY);
		dwX = dwX*8;
		dwY = dwY*8;
		register DWORD dwC3 = 0x36;
		for (register DWORD dwC = 0; dwC < 1024; dwC ++) {
			for (register DWORD dwC2 = dwC1; dwC2 < dwC1+dwY*16; dwC2 += 0x10) {
				*((BYTE*)(bFile+dwC2+0)) = *((BYTE*)(rFile+dwC3+3));
				if ((BYTE)*((BYTE*)(bFile+dwC2+0)) == 0xff) *((BYTE*)(bFile+dwC2+1)) = 0xff; else *((BYTE*)(bFile+dwC2+1)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+2)) = *((BYTE*)(rFile+dwC3+0));
				if ((BYTE)*((BYTE*)(bFile+dwC2+2)) == 0xff) *((BYTE*)(bFile+dwC2+3)) = 0xff; else *((BYTE*)(bFile+dwC2+3)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+4)) = *((BYTE*)(rFile+dwC3+0+dwX*9));
				if ((BYTE)*((BYTE*)(bFile+dwC2+4)) == 0xff) *((BYTE*)(bFile+dwC2+5)) = 0xff; else *((BYTE*)(bFile+dwC2+5)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+6)) = *((BYTE*)(rFile+dwC3+0+dwX*18));
				if ((BYTE)*((BYTE*)(bFile+dwC2+6)) == 0xff) *((BYTE*)(bFile+dwC2+7)) = 0xff; else *((BYTE*)(bFile+dwC2+7)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+8)) = *((BYTE*)(rFile+dwC3+3+dwX*18));
				if ((BYTE)*((BYTE*)(bFile+dwC2+8)) == 0xff) *((BYTE*)(bFile+dwC2+9)) = 0xff; else *((BYTE*)(bFile+dwC2+9)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+10)) = *((BYTE*)(rFile+dwC3+6+dwX*18));
				if ((BYTE)*((BYTE*)(bFile+dwC2+10)) == 0xff) *((BYTE*)(bFile+dwC2+11)) = 0xff; else *((BYTE*)(bFile+dwC2+11)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+12)) = *((BYTE*)(rFile+dwC3+6+dwX*9));
				if ((BYTE)*((BYTE*)(bFile+dwC2+12)) == 0xff) *((BYTE*)(bFile+dwC2+13)) = 0xff; else *((BYTE*)(bFile+dwC2+13)) = 0x00;
				
				*((BYTE*)(bFile+dwC2+14)) = *((BYTE*)(rFile+dwC3+6));
				if ((BYTE)*((BYTE*)(bFile+dwC2+14)) == 0xff) *((BYTE*)(bFile+dwC2+15)) = 0xff; else *((BYTE*)(bFile+dwC2+15)) = 0x00;
				dwC3 = dwC3 + 9;
			}
			dwC1 += 19*dwX;
			dwC3 += 18*dwX;
		}
		WriteFile(hFile4, bFile, 152*dwX*dwY, &dwTemp2, NULL);
		CloseHandle(hFile4);
		CloseHandle(hFile);
	return 0;
}

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

Графы проходимости 3-х карт ПЗ. Угадаете, каких именно?

Hidden text
Карта ПЗ
Карта ПЗ
Карта из ПЗ: ЗвА
Карта из ПЗ: ЗвА
Карта ПЗ
Карта ПЗ

Тогда для меня это было ой как волнительно, но зато какой эпичный результат получился!

Заключение и респект модмейкерам

В последнее время на радость много людей работают ради улучшения прекрасной игры Проклятые Земли, при этом показывая просто отличные результаты!

Мой друг PlayHard_GoPRo, мой знакомый Igles, Павел Шатов, Pyrsus Proskurin и многие другие замечательные мододелы прямо сейчас трудятся над настоящим ремейком Проклятых Земель! Новые территории, HD текстуры, идеально выверенный баланс, щиты, посохи, двуручное оружие, продуманные скрипты - далеко не всё, что вас ожидает.

Пока крутые мужики пилят ремейк, я же пилю аддон к Проклятым Землям... Как только будет что вам показать - я обязательно напишу новую статью про это знаменательное событие. А пока что лишь загадочно скажу, что SpellAddon - в каком-то смысле основа для моего аддона. У меня никогда не стояло цели заморочиться и сделать движок ПЗ супер универсальным и вытащить оттуда в конфигурационный файл все формулы, как то сделали, например, Мэтью и Demoth. Явно далеко не все формулы есть смысл менять, но это уже совсем другая история.

Такие наработки, как, например, длинный ник, расширенные параметры (для сетевого перса), новые заклинания для нашей замечательной игры, ребаланс траты запаса сил, кастомизация цен навыков, возможность сбрость агро противника вы все так же можете найти в моём расширении движка SpellAddon. Это расширение используется и в Жамевю, что не удивительно! Я так много видела желаний по поводу нормального запаса сил, который не тратится одинаково быстро у чувака с 50 силами и с 300 силами, по поводу возможности убежать от врага, что я просто не могла оставить это так! И для меня эти нововведения также были очень желанными. С 2018 убежать от противника стало абсолютно реально!

С некоторыми из моих разработок вы можете ознакомиться также на канале на YouTube по Проклятым Землям и Аллодам от PlayHard_GoPro! Этот чувак знает буквально всё о Проклятых Землях и делится своими знаниями не только об оригинальной игре, но и о разных разработках. В том числе и своих.

Также пару лет назад я создала XXX-MOD, который не стал сильно популярным, наверное, из-за своей сложности. Прочем, ютубер Vismyt Play знает как проходить извращенские модификации - и уже на днях вы можете заценить как он проходит мой ультрасложный мод! 1 стрим он уже даже провёл.

Такое вот зрение будет у всех...
Такое вот зрение будет у всех...

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

Hidden text

Изменения по сравнению с оригиналом:
Можно убежать от противника (сбросить агрессию).
Магом можно играть с самого начала - задрана сложность сильных рун, но уменьшена сложность заклов магии стихий.
Улучшены зелья лечения.
Усилены противники.
Ослаблена броня у игрока.
Украшены сцены.
Усложнена игра воином без магии.
Изменён баланс заклинаний.
Фирез - больше не косорукий дурак.
Добавлен экспериментальный респаун противников.
Теперь на Джигране цены соответствуют сюжету.
Бутылки стали интереснее.
Опыт дают только за задания - больше не нужно бегать за всеми по всей карте.
Опыт и деньги за задания увеличены.
Возвращены батарейки.
Добавлены новые материалы на Джигран.
Возвращены "Пески" на Гипат (просто побить мобов).
ИИ тревоги в посёлках на Гипате сменён на ИИ зова о помощи (раньше атаковали игрока без каких-то дополнительных условий).
Добавлены работяги зоны Портала.
Элемы на Суслангере дропают камень.
В "Пещере Червелицых" появились сундуки с кусками толстой кожи.
Любители геноцидить мобов без опыта и лута теперь могут постравливать драконов со стражей.
Изменены секретные задания!

Модмейкингом занимаются очень много людей как для игры 2000 года.

Взглянем на то, что сделала команда по разработке народного ремейка! От команды Jamais Vu ожидается качество, которое даже не снилось многим работающим в наше время геймдизайнерам. Общаясь с некоторыми из них, у меня создавалось впечатление, что эти люди не знают что такое халтура, прямо как те люди, которые в своё время создавали шикарнейшее произведение "Властелин Колец".

Работа программиста Павла Шатова
Работа программиста Павла Шатова

Оружие в 2-х руках. Абсолютно рабочий персонаж (не фотошоп, не подредактированная модель с дублированным оружием), которому можно выдавать любые комбинации одноручного оружия. Естественно, это далеко не всё, на что способны программисты, любящие Проклятые Земли, статью одной из которых вы прямо сейчас читаете.

Работа программиста Павла Шатова
Работа программиста Павла Шатова

Комментарии излишни. Это - шедевр! Как и то, что вытворили PlayHard_GoPro и Selenor:

Minor-Mod от двух нагибаторов Проклятых Земель
Minor-Mod от двух нагибаторов Проклятых Земель

Забавно, что оба этих чувака прошли Проклятые земли без смертей на своих каналах. Selenor выступает геймдизайнером в этом тандеме, а PlayHard_GoPro - программистом. Конечно, это не столь грандиозная работа, как HG или Jamais Vu, но это, пожалуй, лучшее что может найти для себя консерватор из всего многообразия модов.

Эти фонари!
Эти фонари!

Не знаю как вы, а я от этих фонарей просто в восторге!

Мне было очень приятно понять, что люди всё ещё не забыли про Проклятые Земли и в целом про вселенную Аллодов! Ютубер CveaChannel так вообще заспидранил эту игру за 58 минут, сделав несколько десятков попыток на своих стримах.

Надеюсь, что мы с вами со временем узнаем ещё больше о том что скрывает этот мир... Мир Проклятых Земель!

Дракон с новой текстурой из Жамевю!
Дракон с новой текстурой из Жамевю!

Ссылки на интересный контент:
https://evilislandsaddon.forumotion.com/t2-spelladdon - форум, на котором можно скачать расширение движка SpellAddon и XXX-MOD для ПЗ: ЗвА
https://www.youtube.com/channel/UCrv33tEsSUhf3wVaht9y2oA - мой кент, крутой ютубер и просто прекрасный человек
https://vk.com/evil_islands_jamaisvu - группа мода-ремейка Jamais Vu
https://vk.com/hgmod - группа Honest Group мода
http://gipatgroup.org/ - ранее самый популярный форум Проклятых Земель
https://www.gipat.ru/ - рабочий форум Проклятых Земель
https://vk.com/eiclassicmod - группа Classic-mod
https://vk.com/evil.islands - группа ПЗ
https://www.youtube.com/watch?v=6Z_sb_9dO14 - активный стример и ютубер Vismyt Play
https://www.youtube.com/watch?v=TMiWd1UP100 - стример и ютубер Selenor
https://www.youtube.com/watch?v=HOHmZjwTfSg - спидранер, ютубер, тестер CveaChannel
https://www.youtube.com/watch?v=f7Q8E2b2_zg - Павел Шатов, разрабатывающий Аллоды 2: Повелитель Душ на движке Unreal Engine
http://honestgroup.net/forum/ - к сожалению, заброшенный форум Honest Group

Всем удачи!

Теги:
Хабы:
Всего голосов 100: ↑100 и ↓0+100
Комментарии48

Публикации

Истории

Работа

QT разработчик
13 вакансий
Программист C++
109 вакансий

Ближайшие события

Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
AdIndex City Conference 2024
Дата26 июня
Время09:30
Место
Москва
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область