• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Recipe

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
Recipe (the name of the library) allows you to declaratively define your item recipes (as opposed to writing [v]Jass)
using JSON (JavaScript Object Notation).

A simple tool (an .html page) is provided that converts the recipes into ordinary jass.

Recipe tries to be fast (the generated jass code is only assignments to an array, which is processed by 2 functions).

In order to use Recipe you have to include the following code into your map:
JASS:
library Recipe

globals
    /* export */ integer array RECIPE


    // product types

    // public means those vars can be referenced outside of the library
    // with Recipe_<var-name>

    public integer ITEM = 1
    public integer RANDOM_SET = 2


    // RANDOM_SET methods to choose the items from the set

    // pick means that once an item was picked it can't be
    // picked again; kind of like an urn with balls, i.e
    // you pick a ball and never return it back
    //
    public integer RANDOM_SET_PICK = 1

    // the metaphor for roll comes from rolling a die (singular for dice)
    // if you roll it 2 times you could get the same number
    //
    public integer RANDOM_SET_ROLL = 2

    private boolean array random_set_item_was_picked
endglobals


public function merge_charge_items takes unit u returns nothing
    local integer inventory_size = UnitInventorySize(u)
    local integer current_item_index
    local item    current_item
    local integer current_item_id
    local item    matching_item
    local integer matching_item_index
    local integer total_charges

    set current_item_index = 0
    loop
        exitwhen current_item_index >= inventory_size

        set current_item = UnitItemInSlot(u, current_item_index)
        if current_item != null and GetItemCharges(current_item) > 0 then
            // Find all items with ids matching that of current_item,
            // sum up their charges and remove them. Then set
            // the charges of the current_item to the sum of all charges.
            set current_item_id = GetItemTypeId(current_item)

            set total_charges = GetItemCharges(current_item)
            set matching_item_index = current_item_index + 1
            loop
                exitwhen matching_item_index >= inventory_size

                set matching_item = UnitItemInSlot(u, matching_item_index)
                if matching_item != null and GetItemTypeId(matching_item) == current_item_id then
                    set total_charges = total_charges + GetItemCharges(matching_item)
                    // The unit drops the item but it doesn't get removed from the game.
                    // call UnitRemoveItemFromSlot(u, matching_item_index)
                    // Same.
                    // call UnitRemoveItem(u, matching_item)

                    // Removes the item from the unit's inventory and from the game,
                    // which makes sense.
                    call RemoveItem(matching_item)
                endif

                set matching_item_index = matching_item_index + 1
            endloop

            call SetItemCharges(current_item, total_charges)
        endif

        set current_item_index = current_item_index + 1
    endloop

    set current_item = null
    set matching_item = null
endfunction


public function evaluate takes unit u returns boolean
    local integer recipe_index
    local integer recipe_length
    local integer ingredient_index
    local integer ingredient_id
    local integer ingredient_charges
    local integer ingredients_count
    local boolean ingredient_matched
    local boolean ingredients_matched
    local integer ingredients_end_index
    local integer product_index
    // local integer products_count
    local integer products_end_index
    local integer product_type
    local integer product_id
    local integer product_charges
    local item    product_item
    local integer inventory_size = UnitInventorySize(u)
    local integer current_item_index
    local item    current_item
    local integer current_item_charges
    local integer charges_diff
    local integer random_set_index
    local integer random_set_items_count
    local integer random_set_method
    local integer random_set_item_base_index
    local integer random_set_item_id
    local integer random_set_item_charges
    local item    random_set_item
    local integer random_set_pick
    local integer random_set_roll
    local integer random_set_item_was_picked_index

    call merge_charge_items(u)

    set recipe_index = 0
    loop
        set recipe_length = RECIPE[recipe_index]
        exitwhen recipe_length == 0

        set ingredients_count = RECIPE[recipe_index + 1]
        set ingredients_end_index = recipe_index + 2 + 2 * ingredients_count
        set ingredients_matched = true
        set ingredient_index = recipe_index + 2
        loop
            exitwhen ingredient_index >= ingredients_end_index

            set ingredient_id = RECIPE[ingredient_index]
            set ingredient_charges = RECIPE[ingredient_index + 1]

            set ingredient_matched = false
            set current_item_index = 0
            loop
                exitwhen current_item_index >= inventory_size
                set current_item = UnitItemInSlot(u, current_item_index)

                if current_item != null and GetItemTypeId(current_item) == ingredient_id and GetItemCharges(current_item) >= ingredient_charges then
                    // next ingredient
                    set ingredient_matched = true
                    exitwhen true
                endif

                set current_item_index = current_item_index + 1
            endloop

            if not ingredient_matched then
                // next recipe
                set ingredients_matched = false
                exitwhen true
            endif

            set ingredient_index = ingredient_index + 2
        endloop

        if ingredients_matched then
            // debug call say("recipe matched")

            // Goood, the recipe matched. Now remove
            // the ingredients of the recipe from the unit
            // and add the products of the recipe to it.

            set ingredient_index = recipe_index + 2
            loop
                exitwhen ingredient_index >= ingredients_end_index

                set ingredient_id = RECIPE[ingredient_index]
                set ingredient_charges = RECIPE[ingredient_index + 1]

                set current_item_index = 0
                loop
                    exitwhen current_item_index >= inventory_size
                    set current_item = UnitItemInSlot(u, current_item_index)

                    if current_item != null and GetItemTypeId(current_item) == ingredient_id and GetItemCharges(current_item) >= ingredient_charges then
                        set current_item_charges = GetItemCharges(current_item)
                        set charges_diff = current_item_charges - ingredient_charges

                        if charges_diff == 0 then
                            // We had just enough charges for the recipe.
                            call RemoveItem(current_item)
                        else
                            // We have some remaining charges.
                            call SetItemCharges(current_item, charges_diff)
                        endif

                        // next ingredient
                        exitwhen true
                    endif

                    set current_item_index = current_item_index + 1
                endloop

                set ingredient_index = ingredient_index + 2
            endloop


            // set products_count = RECIPE[ingredients_end_index]
            set products_end_index = recipe_index + recipe_length
            set product_index = ingredients_end_index // + 1
            loop
                exitwhen product_index >= products_end_index

                set product_type = RECIPE[product_index]

                if product_type == ITEM then
                    set product_id      = RECIPE[product_index + 1]
                    set product_charges = RECIPE[product_index + 2]
                    set product_index   = product_index + 3

                    set product_item = UnitAddItemById(u, product_id)
                    call SetItemCharges(product_item, product_charges)

                elseif product_type == RANDOM_SET then
                    set random_set_items_count = RECIPE[product_index + 1]
                    set random_set_method      = RECIPE[product_index + 2]

                    if random_set_method == RANDOM_SET_ROLL then

                        set random_set_roll  = RECIPE[product_index + 3]
                        set random_set_index = product_index + 4
                        set product_index    = product_index + 4 + 2 * random_set_items_count

                        loop
                            exitwhen random_set_roll == 0
                            set random_set_roll = random_set_roll - 1

                            set random_set_item_base_index = random_set_index + 2 * GetRandomInt(0, random_set_items_count - 1)
                            set random_set_item_id = RECIPE[random_set_item_base_index]
                            set random_set_item_charges = RECIPE[random_set_item_base_index + 1]

                            set random_set_item = UnitAddItemById(u, random_set_item_id)
                            call SetItemCharges(random_set_item, random_set_item_charges)
                        endloop

                    elseif random_set_method == RANDOM_SET_PICK then

                        set random_set_pick  = RECIPE[product_index + 3]
                        set random_set_index = product_index + 4
                        set product_index    = product_index + 4 + 2 * random_set_items_count

                        // reset the random_set_item_was_picked array
                        set random_set_item_was_picked_index = random_set_index
                        loop
                            exitwhen random_set_item_was_picked_index >= product_index // points to the start of the next product

                            set random_set_item_was_picked[random_set_item_was_picked_index] = false

                            set random_set_item_was_picked_index = random_set_item_was_picked_index + 2
                        endloop

                        loop
                            exitwhen random_set_pick == 0

                            set random_set_item_base_index = random_set_index + 2 * GetRandomInt(0, random_set_items_count - 1)

                            if not random_set_item_was_picked[random_set_item_base_index] then
                                set random_set_item_was_picked[random_set_item_base_index] = true
                                set random_set_pick = random_set_pick - 1

                                set random_set_item_id = RECIPE[random_set_item_base_index]
                                set random_set_item_charges = RECIPE[random_set_item_base_index + 1]

                                set random_set_item = UnitAddItemById(u, random_set_item_id)
                                call SetItemCharges(random_set_item, random_set_item_charges)
                            endif

                        endloop

                    endif

                endif

            endloop


            // TODO: come up with recipes that cascade and implement the
            // recursive step.
            //
            // Because this recipe matched and the unit recived the products
            // of the recipe, it is possible that another recipe will
            // match as well, i.e it could cascade, so recurse?
            //
            // It turns out we are already recursive because we are called
            // every time a unit picks up an item and that includes the
            // time when we give the unit a recipe's products.
            // So every time we call UnitAddItemById(...) we recurse.


            // A recipe matched no need to check the other recipes.
            exitwhen true // last recipe
        else
            // debug call say("recipe didn't match")
            // This recipe didn't match, try the next one.
            set recipe_index = recipe_index + recipe_length
        endif

    endloop


    set current_item    = null
    set product_item    = null
    set random_set_item = null

    return false
endfunction

endlibrary


After that you have to come up with some recipes and write them in the following manner

JASS:
[
    { ingredients: [{ id: 'phea', charges: 3 }],
      products: [{ type: 'item', id: 'pghe', charges: 1 }]},

    { ingredients: [{ id: 'mlst' },
                    { id: 'engs' }],
      products: [{ type: 'item', id: 'mlst' },
                 { type: 'random-set',
                   set: [{ id: 'hlst', charges: 1 },
                         { id: 'shar', charges: 1 },
                         { id: 'infs', charges: 1 },
                         { id: 'mnst', charges: 1 }],
                   pick: 3 }]},
]

Notice that each recipe has a list of "ingredients", i.e what the recipe requires
and a list of products, i.e what the recipe produces when all the ingredients are present in the unit's inventory.

Each ingredient is a simple "object" with 2 properties
JASS:
    {
        id:      '<id-of-the-item>',
        charges: <number-of-charges> // this property is optional, defaults to 0
    }


Each product has a type (and other properties depending on the type).
There are currenly two types of products: 'item' and 'random-set'.

A product with type of 'item' has the properties:
JASS:
    {
        type:    'item',
        id:      '<id-of-the-item>',
        charges: <number-of-charges> // optional, defaults to 0
    }


A product with type of 'random-set' has the properties:
JASS:
    {
        type: 'random-set',

        set:  <a-list-of-set-items>,
        // A "set-item" is similiar to an ingredient, i.e
        // it has only two properties: id and charges

        // The 3rd propery of a random-set can be either "pick", or "roll"
        pick: <number-of-items-to-pick>  OR  roll: <number-of-items-to-roll>

        // pick means that once an item was picked it can't be
        // picked again; kind of like an urn with balls, i.e
        // you pick a ball from the urn but never return it back

        // the metaphor for roll comes from rolling a die (singular for dice)
        // if you roll it 2 times you could get the same number
    }

My suggestion is to write the recies in your favourite editor that has
syntax highlighting for javascript (as opposed to directily in the right
box in wc3-item-recipe.html).


After you are done writing your recipes you use the wc3-item-recipe.html "tool"
to convert them to jass (it should be self explanatory how to use it).

And the last step is to copy the generated jass code and put it in your.
Make sure it get's executed before you call Recipe_evaluate.


JASS:
library YourMap initializer init uses Recipe

    private function init_recipes takes nothing returns nothing
        // make sure the generated jass code is runned before you call Recipe_evaluate(...)
        // I.e copy-paste the generated jass code here... for example
    endfunction

    private function on_item_pickup takes nothing returns nothing
        local unit u = GetTriggerUnit()

        // Recpe_evaluate takes a unit and tries to match the items
        // in the unit's inventory to the ingredients of all the recipes.
        // If a recipe is found the unit recives the products of the recipe.
        call Recipe_evaluate(u)

        // There is one more function that you might want to use
        // call Recipe_merge_charge_items(u)
        // If a unit has multiple items with the same id
        // it sums their charges and leaves only one of them with
        // charges equal to the sum.

        set u = null
    endfunction

    private function init takes nothing returns nothing
        local trigger t

        call ExecuteFunc(SCOPE_PRIVATE + "init_recipes")

        set t = CreateTrigger()
        call TriggerRegisterPlayerUnitEvent(t, P, EVENT_PLAYER_UNIT_PICKUP_ITEM, null)
        call TriggerAddAction(t, function on_item_pickup)
    endfunction

endlibrary


PS:
wc3-item-recipe.html is uploaded as wc3-item-recipe.txt
Simply rename wc3-item-recipe.txt to wc3-item-recipe.html and open it with your favourite browser.
 

Attachments

  • recipe-demo.w3x
    20.1 KB · Views: 56
  • wc3-item-recipe.txt
    19 KB · Views: 103
Status
Not open for further replies.
Top