• 🏆 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 System

This bundle is marked as pending. It has not been reviewed by a staff member yet.
This is a simple recipe system that can dynamically create, modify, enable/disable and delete recipes/components. You can have an unlimited* number of recipes, with an unlimited* number of components. The recipe results are not coded into the system, as seen in the example, because it would just limit the potential of the system.

*limited by the operations limit


JASS:
/* API DOCUMENTATION:

RECIPES
=================================================
public function New takes nothing returns integer
    - Used to create a new recipe. The integer returned is the recipe identifier.

public function Destroy takes integer recipe returns nothing
    - Used to destroy a recipe and all data attached to it.

public function SetEnabled takes integer recipe, boolean enabled returns nothing
    - Used to enable / disable a recipe.

public function IsEnabled takes integer recipe returns boolean
    - Checks recipe enable / disable status

public function GetLast takes nothing returns integer
    - Returns the last created recipe identifier.

COMPONENTS
=================================================
public function Component_Add takes integer recipe, integer itemType, integer charges returns integer
    - Adds a component requirement to a recipe. Items with no charge (ex: permanent items) get counted
    as having one charge. So if you had three permanent items, it would be counted as 3 charges.

    - Returns the component number in the recipe, which can be used to modify or remove components

public function Component_Modify takes integer recipe, integer component, integer itemType, integer charges returns nothing
    - Modify a component using the component number.

public function Component_Remove takes integer recipe, integer component returns nothing
    - Removes a component using the component number.

public function Component_GetType takes integer recipe, integer component returns integer
    - Gets a recipe component item type.

public function Component_GetCharge takes integer recipe, integer component returns integer
    - Gets a recipe component charge.

CATALOG
=================================================
function CatalogItem takes item curItem returns nothing
    - Adds a specific item to the list of items considered for recipe detection. (ex: can add items on the ground,
    in inventories etc)

function CatalogInventory takes unit u returns nothing
    - Adds all items in a unit's inventory to the current catalog.

function ClearCatalog takes nothing returns nothing
    - Clears the current catalog of all items.

RECIPE COMPLETION CHECKS
=================================================
public function Check_Recipe takes integer recipe returns boolean
    - Checks a specific recipe to see if the current catalog has the components for completion

public function Check_Item takes integer itemType returns integer
    - Checks all recipes an item is a component for.

    - Returns a recipe identifier if the component requirements are met. Returns 0 if there are not enough
    components.

public function Check_ItemsForRecipesInCommon takes integer itemType1, integer itemType2 returns integer
    - Checks and compares two item types only for recipes that they share.

    - Returns a recipe identifier if the component requirements are met. Returns 0 if there are not enough
    components.

public function Check_All takes nothing returns integer
    - Checks all recipes that the cataloged items could create.

    - Returns a recipe identifier if the component requirements are met. Returns 0 if there are not enough
    components.

public function DestroyItemComponents takes integer recipe returns nothing
    - Used to consume the specific recipe component items in a catalog (Use after a check to see if the items are present) */

library Recipe

    globals
        private item array RETURN_ITEMS
        private integer array RETURN_CHARGES
        private integer RETURNED_ITEMS
        private integer array RETURN_TYPES
        private integer array RETURN_TYPES_CHARGES
        private integer RETURNED_TYPES
        private integer RECIPE_MAX = 0
        private hashtable HASHTABLE = InitHashtable()
        private integer array RECIPE_LIST
        private integer RECIPE_LIST_MAX = 0
        private integer RECIPE_LAST
    endglobals

    private function GetOffset takes integer component returns integer
        return 2 + component * 2
    endfunction

    public function Component_Remove takes integer recipe, integer component returns nothing
        local integer componentLast = LoadInteger(HASHTABLE, recipe, 0)
        local integer offset = GetOffset(component)
        local integer offsetNext = GetOffset(componentLast)
        local integer itemType = LoadInteger(HASHTABLE, recipe, offset)
        local integer recipesInItem = LoadInteger(HASHTABLE, itemType, 0)
        local integer curCount = 0

        call SaveInteger(HASHTABLE, recipe, offset, LoadInteger(HASHTABLE, recipe, offsetNext))
        call SaveInteger(HASHTABLE, recipe, offset + 1, LoadInteger(HASHTABLE, recipe, offsetNext + 1))
        call RemoveSavedInteger(HASHTABLE, recipe, offsetNext)
        call RemoveSavedInteger(HASHTABLE, recipe, offsetNext + 1)
        call SaveInteger(HASHTABLE, recipe, 0, componentLast - 1)
        loop
            set curCount = curCount + 1
            exitwhen curCount > recipesInItem or LoadInteger(HASHTABLE, itemType, curCount) == recipe
        endloop
        call SaveInteger(HASHTABLE,itemType, curCount, LoadInteger(HASHTABLE, itemType, recipesInItem))
        call RemoveSavedInteger(HASHTABLE, itemType, recipesInItem)
        call SaveInteger(HASHTABLE, itemType, 0, recipesInItem - 1)
    endfunction

    public function Component_Modify takes integer recipe, integer component, integer itemType, integer charges returns nothing
        local integer offset = GetOffset(component)

        call SaveInteger(HASHTABLE, recipe, offset, itemType)
        call SaveInteger(HASHTABLE, recipe, offset + 1, charges)
    endfunction

    public function Component_Add takes integer recipe, integer itemType, integer charges returns integer
        local integer component = LoadInteger(HASHTABLE, recipe, 0) + 1
        local integer recipeInItem = LoadInteger(HASHTABLE, itemType, 0)
        local integer offset = GetOffset(component)
        local integer i = 0

        call SaveInteger(HASHTABLE, recipe, 0, component)
        call SaveInteger(HASHTABLE, recipe, offset, itemType)
        call SaveInteger(HASHTABLE, recipe, offset + 1, charges)

        loop
            set i = i + 1
            exitwhen i > recipeInItem or recipe == LoadInteger(HASHTABLE, itemType, i)
        endloop
        if i > recipeInItem then
            call SaveInteger(HASHTABLE, itemType, 0, i)
            call SaveInteger(HASHTABLE, itemType, i, recipe)
              endif
        return component
    endfunction

    public function Component_GetType takes integer recipe, integer component returns integer
        return LoadInteger(HASHTABLE, recipe, GetOffset(component))
    endfunction

    public function Component_GetCharge takes integer recipe, integer component returns integer
        return LoadInteger(HASHTABLE, recipe, GetOffset(component) + 1)
    endfunction

    public function New takes nothing returns integer
        if RECIPE_LIST_MAX > 0 then
            set RECIPE_LAST = RECIPE_LIST[RECIPE_LIST_MAX]
            set RECIPE_LIST_MAX = RECIPE_LIST_MAX - 1
        else
            set RECIPE_MAX = RECIPE_MAX + 1
            set RECIPE_LAST = RECIPE_MAX
        endif
        call SaveBoolean(HASHTABLE,RECIPE_LAST, 1, true)
        return RECIPE_LAST
    endfunction

    public function Destroy takes integer recipe returns nothing
        local integer curItemType
        local integer forItemsInRecipe = 0
        local integer itemsInRecipe = LoadInteger(HASHTABLE, recipe, 0)
        local integer itemInRecipeOffset
        local integer recipesInItem

        loop
            set forItemsInRecipe = forItemsInRecipe + 1
            exitwhen forItemsInRecipe > itemsInRecipe
            set curItemType = LoadInteger(HASHTABLE, recipe, GetOffset(forItemsInRecipe))
            set itemInRecipeOffset = 0
            loop
                set itemInRecipeOffset = itemInRecipeOffset + 1
                exitwhen recipe == LoadInteger(HASHTABLE, curItemType, itemInRecipeOffset)
            endloop
            set recipesInItem = LoadInteger(HASHTABLE, curItemType, 0)
            call SaveInteger(HASHTABLE, curItemType, itemInRecipeOffset, LoadInteger(HASHTABLE, curItemType, recipesInItem))
            call RemoveSavedInteger(HASHTABLE, curItemType, recipesInItem)
            call SaveInteger(HASHTABLE, curItemType, 0, recipesInItem - 1)
        endloop
        call FlushChildHashtable(HASHTABLE, recipe)
        set RECIPE_LIST_MAX = RECIPE_LIST_MAX + 1
        set RECIPE_LIST[RECIPE_LIST_MAX] = recipe
    endfunction

    public function SetEnabled takes integer recipe, boolean enabled returns nothing
        call SaveBoolean(HASHTABLE, recipe, 1, enabled)
    endfunction

    public function IsEnabled takes integer recipe returns boolean
        return LoadBoolean(HASHTABLE, recipe, 1)
    endfunction

    public function GetLast takes nothing returns integer
        return RECIPE_LAST
    endfunction

    function ClearCatalog takes nothing returns nothing
        set RETURNED_ITEMS = 0
        set RETURNED_TYPES = 0
    endfunction

    function CatalogItem takes item curItem returns nothing
        local integer curType
        local integer charge
        local integer forType = 0

        set RETURNED_ITEMS = RETURNED_ITEMS + 1
        set RETURN_ITEMS[RETURNED_ITEMS] = curItem
        set charge = GetItemCharges(curItem)
        if charge == 0 then
            set charge = 1
        endif
        set RETURN_CHARGES[RETURNED_ITEMS] = charge
        set curType = GetItemTypeId(curItem)
        loop
            set forType = forType + 1
            exitwhen forType > RETURNED_TYPES or curType == RETURN_TYPES[forType]
        endloop
        if RETURNED_TYPES < forType then
            set RETURN_TYPES_CHARGES[forType] = 0
            set RETURNED_TYPES = forType
        endif
        set RETURN_TYPES[forType] = curType
        set RETURN_TYPES_CHARGES[forType] = RETURN_TYPES_CHARGES[forType] + charge
    endfunction

    function CatalogInventory takes unit u returns nothing
        local integer forSlot = 0
        local integer forType
        local item curItem

        loop
            set curItem = UnitItemInSlot(u, forSlot)
            if curItem != null then
                call CatalogItem(curItem)
            endif
            exitwhen forSlot > 5
            set forSlot = forSlot + 1
        endloop
        set curItem = null
    endfunction

    public function Check_Recipe takes integer recipe returns boolean
        local integer completedComponents = 0
        local integer itemsInRecipe = LoadInteger(HASHTABLE, recipe, 0)
        local integer typeNumber = 0
        local integer recipeListNumber
        local integer offset

        if LoadBoolean(HASHTABLE, recipe, 1) then
            loop
                set typeNumber = typeNumber + 1
                exitwhen typeNumber > RETURNED_TYPES
                set recipeListNumber = 0
                loop
                    set recipeListNumber = recipeListNumber + 1
                    exitwhen recipeListNumber > itemsInRecipe
                    set offset = GetOffset(recipeListNumber)
                    if LoadInteger(HASHTABLE, recipe, offset) == RETURN_TYPES[typeNumber] then
                        if LoadInteger(HASHTABLE, recipe, offset + 1) <= RETURN_TYPES_CHARGES[typeNumber] then
                            set completedComponents = completedComponents + 1
                        endif
                    endif
                endloop
            endloop
            return completedComponents >= itemsInRecipe
        else
            return false
        endif
    endfunction

    public function Check_Item takes integer itemType returns integer
        local integer recipeNumber = 0
        local integer curRecipe
        local integer recipesMax = LoadInteger(HASHTABLE, itemType, 0)

        loop
            set recipeNumber = recipeNumber + 1
            exitwhen recipeNumber > recipesMax
            set curRecipe = LoadInteger(HASHTABLE, itemType, recipeNumber)
            if Check_Recipe(curRecipe) then
                return curRecipe
            endif
        endloop
        return 0
    endfunction

    public function Check_ItemsForRecipesInCommon takes integer itemType1, integer itemType2 returns integer
        local integer array recipeList
        local boolean array recipeInCommon
        local integer listCur
        local integer listMax = 0
        local integer recipesMax
        local integer recipeNumber
        local integer curRecipe
        local integer forType
        local integer i = 0

        loop
            set i = i + 1
            exitwhen i > 2
            if i == 1 then
                set forType = itemType1
            else
                set forType = itemType2
            endif
            set recipesMax = LoadInteger(HASHTABLE, forType, 0)
            set recipeNumber = 0
            loop
                set recipeNumber = recipeNumber + 1
                exitwhen recipeNumber > recipesMax
                set curRecipe = LoadInteger(HASHTABLE, forType, recipeNumber)
                set listCur = 0
                loop
                    set listCur = listCur + 1
                    exitwhen listCur > listMax or curRecipe == recipeList[listCur]
                endloop
                if curRecipe != recipeList[listCur] then
                    set listMax = listCur
                    set recipeList[listCur] = curRecipe
                elseif curRecipe == recipeList[listCur] then
                    set recipeInCommon[listCur] = true
                endif
            endloop
        endloop
        loop
            exitwhen listMax <= 0
            set curRecipe = recipeList[listMax]
            if recipeInCommon[listMax] then
                if Check_Recipe(curRecipe) then
                    return curRecipe
                endif
            endif
            set listMax = listMax - 1
        endloop
        return 0
    endfunction

    public function Check_All takes nothing returns integer
        local integer array recipes
        local integer recipesTotal = 0
        local integer recipesCheck
        local integer typeNumber = 0
        local integer curRecipe
        local integer recipesMax
        local integer recipeNumber
        local integer itemType

        loop
            set typeNumber = typeNumber + 1
            exitwhen typeNumber > RETURNED_TYPES
            set itemType = RETURN_TYPES[typeNumber]
            set recipesMax = LoadInteger(HASHTABLE, itemType, 0)
            set recipeNumber = 0
            loop
                set recipeNumber = recipeNumber +1
                exitwhen recipeNumber > recipesMax
                set curRecipe = LoadInteger(HASHTABLE, itemType, recipeNumber)
                set recipesCheck = 0
                loop
                    set recipesCheck = recipesCheck + 1
                    exitwhen recipesCheck > recipesTotal or curRecipe == recipes[recipesCheck]
                endloop
                if recipesTotal < recipesCheck then
                    set recipesTotal = recipesCheck
                    set recipes[recipesCheck] = curRecipe
                endif
            endloop
        endloop
        loop
            exitwhen recipesTotal == 0
            if Check_Recipe(recipes[recipesTotal]) then
                return recipes[recipesTotal]
            endif
            set recipesTotal = recipesTotal - 1
        endloop
        return 0
    endfunction

    public function DestroyItemComponents takes integer recipe returns nothing
        local integer itemsInRecipe = LoadInteger(HASHTABLE, recipe, 0)
        local integer itemNumber
        local integer curType
        local integer curCharge
        local integer curRecipeStep = 0
        local integer offset
        loop
            set curRecipeStep = curRecipeStep + 1
            exitwhen curRecipeStep > itemsInRecipe
            set offset = GetOffset(curRecipeStep)
            set curType = LoadInteger(HASHTABLE, recipe, offset)
            set curCharge = LoadInteger(HASHTABLE, recipe, offset + 1)
            set itemNumber = 0
            loop
                set itemNumber = itemNumber + 1
                exitwhen itemNumber > RETURNED_ITEMS or curCharge == 0
                if curType == GetItemTypeId(RETURN_ITEMS[itemNumber]) then
                    if RETURN_CHARGES[itemNumber] > curCharge then
                        set RETURN_CHARGES[itemNumber] = RETURN_CHARGES[itemNumber] - curCharge
                        call SetItemCharges(RETURN_ITEMS[itemNumber], RETURN_CHARGES[itemNumber])
                        set curCharge = 0
                    else
                        call RemoveItem(RETURN_ITEMS[itemNumber])
                        set curCharge = curCharge - RETURN_CHARGES[itemNumber]
                        set RETURN_ITEMS[itemNumber] = null
                        set RETURN_CHARGES[itemNumber] = 0
                    endif
                endif
            endloop
        endloop
    endfunction

endlibrary
Contents

Recipe System (Map)

Level 2
Joined
Jul 1, 2020
Messages
22
Because an item could be used in multiple recipes both as a component and as a result.
And how would that effect the -recipe command?
You can search for components to see what they can be made into.
And Search for Results and see what they can be made into. no biggy.
 
Level 18
Joined
Oct 17, 2012
Messages
818
Such a chat command should not be a part of the core system itself. The user should implement it himself via the core system because everyone wants different things.

@maddeem The check recipe functions would be better off returning a list of valid recipes, not just one. It is rather limiting as is. I would like to combine a whole slew of items simultaneously.
 
Level 18
Joined
Oct 17, 2012
Messages
818
This is long overdue, but your recipe system has a bit of an issue. I tried to combine several items simultaneously via entering a region, but one recipe consumed more items than it should.

I had two Healing stones with 6 charges and a Skull of Gul'dan. The well defined recipe for Greater Healing Stone should have only consumed 1 healing stone, but it consumed both upon creation. I guess your recipe system was never meant for combing multiple items at once and is therefore a limitation of your system.

Nonetheless, I love the fact that you left the combining of items up to the user. Your system reminds me of @Chaosy 's, which is of a similar nature. It is just that it does not suit my modding needs.

Edit: Nvm, I see that it combined into 2 Greater Healing stones, so false alarm. It looks like I will be using your system after all as the base for my own system. Cool Beans!
 
Last edited:
Level 18
Joined
Oct 17, 2012
Messages
818
Yeah, I just repeat the actions in a loop to do all possible recipes, so it is fine. Yet, a global array beats having to run the Check_All function over and over again. As more recipes are added to a map, I imagine the function becoming quite the heavyweight.
 
Last edited:
Level 25
Joined
Feb 2, 2006
Messages
1,669
Really nice and simple core system. I like its minimalistic approach. It should get approved if no bugs have been found.
You could add a second library as helper system based on it which removes the items and creates a new item combining the core functions. It probably exists in the map but would be useful in the description to be copied and used.
Otherwise, you have to open the map, copy the triggers and recreate it. I think the usage is mostly the same in most maps. You want to use this system to create a specific item type.
 
Level 18
Joined
Oct 17, 2012
Messages
818
Example code can be copied from the Preview Triggers window. Still, it would be nice to have the example code in the description as a convenience for the user. After all, the main library is already in the description, so why not also the example code?
 
Top