Diablo2 - визуализация улучшения рун

Опубликовано oioioooi - 18/01/2022 - 20:39
rune-clock

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

Переходите по ссылке чтобы увидеть как происходит улучшение рун - https://d2r.annoyed.dev/

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

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

Если пойти по совсем экстремальному пути и попытаться превратить самые простые El руны в самую редкую Zod - сколько рун потребуется? И сколько по времени?

Немного расчетов... До 21-й руны - Pul - игроку потребуется по 3 одинаковых руны (не считая камней). Следующие 12 уровней, потребуется уже по две руны и так до самой Zod.

Таким образом, перемножая наборы - 320*212 - получаем примерно 14 триллионов (14.281.868.906.496 если точно) El рун!

Как это бы выглядело в реальности? Для визуализации процесса был создан симулятор улучшения рун - https://d2r.annoyed.dev/

Достаточно сказать, что со времени релиза Diablo2 (в 2000 году) и взяв за основу что каждую секунду в сундук добавляется по одной руне El, то на данный момент мы дошли только до руны Fal.

Как это работает

Идея была создать интерфейс который симулирует добавление в сундук рун и их превращение. Каждая руна это список из 5 элементов: название, камень (если нужен) и три слота для рун. В зависимости от уровня руны, показаны 3 или 2 руны соответственно.

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

Страница использует обычный javascript для визуализации процесса.

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

setInterval(function () {
    if (runeSetFilled(current)) {
        // Whether the rune set is filled (all runes exist to transmute), reset it.
        // Move forward until there are full rune sets.
        while (runeSetFilled(current)) {
            runeSetReset(current);
            current = next(current);

            // We reached Zod, which is unlikely in near future.
            if (current === null) {
                current = elRune;
            }
        }
    }

    // Add rune to current set.
    runeSetFill(current);

    // If we moved forward, get back to El rune, to start over.
    if (!runeSetFilled(current)) {
        current = elRune;
    }

}, timeout);

Задан временной интервал для каждого прохода, присвоенный к переменной timeout. Временной интервал по умолчанию равен 1000, что является 1000 миллисекундам или 1 секунде.

Задаем начальное состояние счетчиков

Для каждого набора рун выведен их счетчик. Следует инициализировать эти счетчики и задать им кол-во рун созданных до текущего момента. Стартовая дата для расчетов по умолчанию задана как дата релиза игры - 29 июня 2000.

После загрузки страницы, код высчитывает разницу в секундах между стартовой и текущей датой (вплоть до секунды). Исходя из того, что скорость генерации/улучшения рун равна одной секунде, получаем что разница в датах в секундах равна кол-ву рун El которые могли быть собраны за это время.

Для подсчета кол-ва рун в каждом наборе, зададим понятие веса. Вес в этом случае, это кол-во шагов требуемых для создания руны заданного уровня. Для экономии в расчетах, вес каждой руны заранее просчитан и задан как data атрибут в каждом наборе. На практике вес, равен кол-ву рун El плюс кол-во шагов затраченных для их превращения.

В цифрах, вес рун выглядит следующим образом:

Руна Вес
El 1
Eld 4
Tir 13
Nef 40
Eth 121
Ith 364
Tal 1093
Ral 3280
Ort 9841
Thul 29524
Amn 88573
Sol 265720
Shael 797161
Dol 2391484
Hel 7174453
Io 21523360
Lum 64570081
Ko 193710244
Fal 581130733
Lem 1743392200
Pul 3486784401
Um 6973568803
Mal 13947137607
Ist 27894275215
Gul 55788550431
Vex 111577100863
Ohm 223154201727
Lo 446308403455
Sur 892616806911
Ber 1785233613823
Jah 3570467227647
Cham 7140934455295
Zod 14281868910591

 

Вес руны Eld равен 4, т.к. нужны три руны El и одно превращение.

Вес руны Tir равен 13, т.к. требуется 9 рун El для получения 3-х рун Eld и одно превращение этих рун в руну Tir. Иначе говоря, нужны 4 шага чтобы получить руну Eld, затем три этих руны чтобы получить руну Tir. 3*4 = 12 и один шаг на превращение - 12+1=13. Аналогичный подход используется для всех остальных рун. Разница лишь в том, что расчеты приобретают характер вычислений с большими числами. И тем они больше, чем выше уровень руны.

Весь процесс расчетов можно поделить на два этапа:

  1. Рассчитать какие руны подсветить;
  2. Рассчитать кол-во рун в счетчиках в каждом наборе.

Расчет видимых (подсвеченных) рун

Расчет начинаем с руны с максимально большим возможным весом. Удобно делать расчет с самой "дорогой" руны, т.к. это позволяет легко вычислить кол-во рун нужных для получения этой самой дорогой руны. К примеру, легко вычислить что для руны Fal нужны три руны Ko, 9 рун Lum и т.д.

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

Предположим, что в разница во времени равна 20 секундам. Самая дорогая руна, чей вес вмещается в эту разницу, является руна Tir, с весом 13. 20 секунд минус 13 "веса" равен 7 секундам разницы. В эти 7 секунд можно уместить лишь одну руну Eld с весом 4. 7 секунд минус 4, равен 3, что равно весу трех рун El. Легко заметить, что сумма кол-ва рун в итоге равно разнице во времени полученной изначально.

 

d2r rune counter

Расчет рун в счетчиках

После того, как были подсвечены руны в каждом наборе, осталось высчитать кол-во рун в счетчиках в этих наборах. В случае если мы на руне Ort, а до этого мы посчитали 6 рун Tal, требуется добавить к текущему кол0ву рун Ort результат умножения кол-ва этих рун требуемых для получения текущего кол-ва рун Tal.

function initializeRunesCount() {
    let start = new Date('2000-06-29T00:00:00.000Z');
    let now = new Date();
    let diff =  Math.round((now.getTime() - start.getTime()) / 1000);
    let remainder = diff;

    let runes = document.querySelectorAll('.rune__item');

    // Rune "cost";
    let runeWeight = 0;
    // Count of previous, high level, runes.
    let prevCount = 0;
    // How many runes we need to get next rune.
    let runeMultiplier = 1;
    // Move backwards.
    for (let i = runes.length - 1; i >= 0; i--) {
        runeWeight = parseInt(runes[i].dataset.weight);
        runeMultiplier = parseInt(runes[i].dataset.required);

        // Seek further for higher weighted rune.
        if (runeWeight > diff) {
            continue;
        }

        // Rune's weight is within the time diff. 
        // Visually mark the runes that are left over.
        while (runeWeight <= remainder) {
            if (remainder > 0) {
                remainder -= runeWeight;
            }

            runeSetMark(runes[i]);
            runeSetCountSet(runes[i], runeSetCountGet(runes[i]) + 1);
        }

        // Now calculate their total amount, assuming next level rune count.
        runeSetCountSet(runes[i], runeSetCountGet(runes[i]) + prevCount * runeMultiplier);
        prevCount = runeSetCountGet(runes[i]);
    }
}

Реализация на javascript довольно прямолинейна и не требует каких-либо сторонних библиотек. В силу специфики языка и подхода к манипуляции DOM, это довольно простецкий подход, без попыток "сделать красиво". В будущих статьях, посмотрим как этот код можно привести в более читаемый и переносимый вид, используя более современные и удобные подходы.

Теги