Раздел:
Примеры использования

Предисловие

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

Один предмет - один рецепт

Начнем с более простого случая - каждый предмет в любой момент может быть изготовлен из определенного фиксированного списка ингредиентов.
Первым делом подготовим базу данных, в которой будут храниться рецепты. Поскольку рецепт это по сути список целочисленных значений, то в качестве хранилища рецептов логично выбрать целочисленный массив. Каждый рецепт в массиве будет представлен последовательностью целых чисел, начинающейся с ячейки, содержащей количество ингредиентов, за которой подряд следуют равкоды всех ингредиентов.
Таким образом каждый рецепт однозначно характеризуется смещением от начала массива до первой ячейки рецепта.
Интерфейс для потокового заполнения базы рецептами:
//Добавляет в записываемый рецепт еще один ингредиент. Если в данный момент нет активного для записи рецепта, создает его.
function AddItemToRecipe takes integer id returns notnig

//Завершает запись рецепта, следующий вызов функции AddItemToRecipe начнет запись нового рецепта.
//Возвращает смещение первой ячейки записанного рецепта.
function FinishRecipe takes nothing returns integer
Для упрощения записи данных в массив вместо прямого порядка считывания можно использовать обратный, записывая длину рецепта, используя смещение не на первую, а на последнюю ячейку рецепта и перебирая ингредиенты рецепта с конца.
Также нам могут пригодиться данные о том, какие рецепты могут быть изготовлены с использованием каждого предмета. Для хранения таких данных удобно использовать хештаблицу. В качестве ключа хештаблицы будет выступать равкод предмета, по смещению 0 будет храниться количество рецептов, в которых используется данный предмет, а по всем остальным смещениям будут последовательно храниться индексы рецептов, в которых применяется данный предмет. (Я выбрал хештаблицу из соображений простоты использования, но никто не мешает реализовать то-же самое на массивах, более того препроцессор может сосчитать рецепты для каждого предмета чтобы правильно заполнить таблицу и для этого не потребуется реализовывать динамически расширяемые списки на jass)
Таким образом у нас будут все необходимые данные для двух самых распространенных способов крафта - 1 с "ключем" или физическим рецептом, к которому можно привязать смещение рецепта в массиве и 2 без такового, когда для сужения списка рецептов подлежащих проверке используются данные о том, в каких рецептах упоминаются имеющиеся у нас предметы.
С теорией и подготовительной частью разобрались, приступим к непосредственному написанию кода для препроцессора
Для хранения рецептов в РО будем использовать такую фичу препроцессора как пользовательские категории объектов с параметрами. А именно, добавим категорию craftable всем предметам подлежащие сборке через рецепты и в качестве параметров категории через запятую перечислим все ингредиенты.
//например так
@[craftable:I000,I001,I003,I005]
Эти данные элементарно считываются и помещаются в код с помощью конструкции вида:
<list craftable as item>
    <list item.craftable as ingredient>
        AddItemToRecipe('${ingredient}')
    </list>
    FinishRecipe()
    DisplayTextToPlayer(Player(0),0,0,"Рецепт предмета ${item.name} с равкодом ${item} сохранен.")
    DisplayTextToPlayer(Player(0),0,0,"Равкод можно получить и в виде числа "+I2S('${item}')+", вот так!")
</list>
Естественно это только демонстрационный пример и на практике будет необходимо выполнить несколько дополнительных действий, включая сопоставление равкода предмета рецепту и разбиение блока записи рецептов в базу на несколько частей для избежания прерывания потока по лимиту операций.

Нужно больше рецептов

Представим себе ситуацию, когда один предмет может иметь несколько способов сборки, а чтобы жизнь малиной не казалась добавим еще возможность создания побочного продукта при изготовлении предмета, а если этого слишком мало чтобы вызвать непроизвольное слюноотделение и дрожь, используем для этих целей возможность указать имя триггера, который будет вызываться через TriggerEvaluate.
Для определенности предположим что наши рецепты будут содержать ровно по два ингредиента, изменить это дело пары секунд. Не намного сложнее реализовать поддержку различного количества ингредиентов в разных рецептах.
При условии фиксированной длины рецепта можно изменить интерфейс записи нового рецепта в базу:
//принимает сразу все данные, необходимые для сохранения рецепта в базе и выдает его уникальный идентификатор
function AddFixedRecipe takes integer id0, integer id1, integer bonus, trigger evaluate returns integer
Хранить данные в РО будем тем же способом что и в первый раз, но добавим две дополнительные категории для того чтобы указать для каждой пары ингредиентов дополнительно еще один создаваемый предмет и триггер.
Тогда заполнение базы будет выглядеть примерно так:
//перебираем все объекты в категории craftable 
<list craftable as i>

    //заносим в переменные значение параметров категорий для данного объекта
    <assign x=i.craftable>
    <assign y=i.bonus>
    <assign z=i.evaluate>

    //считаем количество пар параметров, можно заменить целочисленным делением
    //сделано из расчета на добавление рецептов различной длины
    <assign count = 0>
    <assign offset = 0>
    <list x as j>
        <if offset==0>
            <assign offset = 1>
        <else>
            <assign offset = 0>
            <assign count = count+1>
        </if>
    </list>

    //перебираем все пары ингредиентов и помещаем данные в базу
    <list 0..count-1 as j>
        <if ${y[j]}=="0">
            AddFixedRecipe ( '${x[j*2]}', '${x[j*2+1]}', 0, ${z[j]} )
        <else>
            AddFixedRecipe ( '${x[j*2]}', '${x[j*2+1]}', '${y[j]}', ${z[j]} )
        </if>
    </list>
</list>
Опять же, это демонстрационный пример и на практике необходимо добавить проверку на количество параметров к категориям bonus и evaluate чтобы если пользователю, например, не нужно для предмета побочных продуктов крафта или триггера со специальными действиями, он мог их не указывать.
ToDo: дописать продолжение