В прошлом посте я показал, как у меня с монстров сыплются одни зелёные вещи. В комментариях спросили: как? Рассказываю подробно, с формулами и байтами. Вдруг кому-то пригодится, кто тоже 20 лет не может собрать сет.
Играю в «Гроздья гнева»: это русский мод на Diablo 2 версии 1.09.
Часть 1. Где игра хранит правила.
Всё содержимое Diablo 2 лежит в архивах .mpq, это формат Blizzard. Мод добавляет свой архив, Rus_Grapes_of_Wrath.mpq. Внутри лежат таблицы: оружие, броня, камни, рецепты куба, таблицы дропа.
Первая засада: в моде таблицы лежат только в скомпилированном виде (.bin), без исходных .txt. Это сырые массивы записей фиксированной длины без заголовков полей. У записи брони 564 байта, а что где лежит, не написано.
Как я их расшифровал: в обычном патче игры (patch_d2.mpq) есть и .txt, и .bin для одних и тех же таблиц. Я написал скрипт, который для каждого столбца из .txt перебирает все смещения в .bin и ищет, где значения совпадают во всех строках сразу. Так нашлась карта полей, например для брони:
Структура у мода та же, поэтому карта подошла и к его таблицам.
Часть 2. Как игра решает, что выпадет
Когда монстр умирает, игра делает два независимых выбора.
Шаг А: какой предмет. У каждого монстра есть «класс сокровищ» (Treasure Class), например Act 5 (H) H2H A. Это список вариантов с весами: золото, зелья, «экипировка». Есть ещё вес NoDrop, ничего не выпало. Игра делает несколько бросков (Picks) и спускается по вложенным спискам. В конце она попадает в автоматические группы вроде armo63 или weap66, куда игра сама раскладывает все предметы по уровню с шагом 3, и выбирает конкретную базу: «Готический доспех», «Корона» и так далее.
Шаг Б: какого качества. Только теперь игра проверяет качество, строго по очереди: уникальная, сетовая, редкая, магическая, «превосходная», обычная. Каждая проверка (по исследованиям сообщества, Phrozen Keep) выглядит так:
шанс = (Ratio − (ур.монстра − ур.предмета) / Divisor) × 128 шанс = шанс × 100 / (100 + MF_эфф) если шанс < Min, то шанс = Min успех, если random(0..шанс) < 128
Ratio, Divisor и Min берутся из таблицы itemratio. В ней 6 строк: классика или дополнение, обычные, «убер» и классовые вещи. Например, для уникальных в моде стоит Ratio 500, Divisor 6, Min 50.
Отсюда, кстати, видно, почему MF быстро упирается в потолок. Для уникальных он режется по формуле MF × 250 / (MF + 250), для сетов × 500 / (MF + 500). Я собрал персонажа с 570% MF (об этом ниже), а на уникальные это дало реальные +174%. На практике шанс уникальной с предмета поднялся примерно с 0,2% до 0,55%.
Часть 3. Первая попытка и облом
Логичный ход: поставить сетам Ratio = 1, Divisor = 10000, Min = 128. Тогда шанс = 128, и random(0..128) < 128 выполняется всегда. А уникальным, чтобы они не перехватывали проверку (они идут первыми), поставить Ratio 100000 и Min 10 000 000.
Нюанс: числа нельзя брать слишком большими. Внутри игра умножает шанс на 100, и 32-битное число может переполниться. 100000 × 128 × 100 ≈ 1,28 млрд, это ещё влезает.
Результат: падают… белые и синие вещи.
Почему: шаг Б идёт после шага А. Если игра уже выбрала базу, у которой нет сетовой версии, проверка «сет?» проваливается, и вещь становится магической или обычной. А сетовые версии есть далеко не у всех баз.
Часть 4. Решение: подменить выбор базы
Раз качество зависит от базы, нужно сделать так, чтобы игра выбирала только «правильные» базы.
Разобрал таблицу сетов (setitems.bin). В 1.09 одна запись — это целый сет до 6 предметов, коды предметов лежат с шагом 60 байт начиная со смещения 140. В моде 63 сета и 217 баз: 157 брони, 57 оружия, 3 прочих.
Всем остальным предметам брони и оружия обнулил флаг spawnable. Отключено 450 баз, монстры их больше не выбирают.
Подводный камень: игра раскладывает предметы по группам уровней (weap33, armo63…). Если в группе не останется ни одного предмета, игра может упасть. У мода есть несколько «дыр» в уровнях 4–6, 10–12, 28–36 и 43–45, где нет ни одного сетового оружия. В этих группах я оставил обычные базы. Поэтому иногда, особенно на Normal и Nightmare, всё ещё может выпасть обычное или магическое оружие.
Итог: если с монстра падает экипировка, она почти всегда зелёная.
В таблице дропа (treasureclassex.bin, запись 732 байта: имя, Picks, NoDrop, 10 ссылок по 64 байта и 10 весов):
обнулил NoDrop у всех 928 классов сокровищ;
удвоил Picks у 168 классов обычных монстров во всех актах и на всех сложностях, плюс у коров и Совета.
Боссов не трогал: у них бросков и так много, а игра ограничивает число вещей с одного монстра.
Часть 6. Рецепт для дублей
Приятный сюрприз: в 1.09 рецепты куба хранятся в .bin обычным текстом. 7 входов по 32 байта, результат со смещения 236, и разбирает их сама игра при загрузке. Поэтому новый рецепт — это просто новая запись в конце файла:
"any,set,qty=3" → "usetype,set" (уровень предмета 99)
Три любые сетовые вещи дают новую сетовую вещь того же типа, что и первая положенная. Из 3411 рецептов мода нашёлся и родной вариант: сет + совершенный изумруд дают новый сет.
Часть 7. Персонаж-пылесос
Перед всем этим я честно пытался поднять MF. Файл персонажа .d2s я правил вручную. Формат 1.09 такой:
в начале сигнатура 0xAA55AA55, версия 92 и контрольная сумма: циклический сдвиг влево и сложение по всем байтам;
характеристики записаны как 16-битная маска, за которой идут 32-битные значения; здоровье и мана хранятся умноженными на 256;
предметы — это битовые структуры, которые начинаются с JM. Камень в ячейке — отдельный предмет с «местом» = 6, он идёт сразу за вещью-владельцем и не входит в общий счётчик предметов.
В моде камни переделаны (gems.bin, 144 записи вместо 68 в оригинале):
Perfect Topaz в шлеме или броне даёт 30% MF вместо 24% и ещё +60% к золоту;
руна Ist даёт MF даже в оружии и щите;
ячейки разрешены у сапог, перчаток, поясов, амулетов и колец.
Итог: 19 совершенных топазов в 8 вещах, до 570% MF. Только, как мы выяснили выше, для уникальных и сетов это почти ничего не даёт. Поэтому я и полез в таблицы.
StormLib (открытая библиотека для .mpq): распаковка и упаковка архивов;
Python: разбор .bin и .d2s, пересчёт контрольной суммы;
Hero Editor от ZonFire99, чтобы посмотреть на результат.
Изменённые файлы не выкладываю: это архивы мода и игры. Но по описанию выше всё повторяется.