System Artificial's Recipe System

Discussion in 'Systems and Snippets' started by Artificial, Aug 8, 2008.

  1. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    Yes, it's true. Yet another one.
    RecipeSYS
    v0.7b
    RecipeSYS is a system that allows you to have recipes that combine two or more (up to six) items into one item. The code has a rather good documentation on how to use it, and the map also contains an example trigger of usage in GUI.
    All you need to do is add one line of Jass for each recipe you want, and the system will do the rest.
    The system can even be used for stacking charged items (not the easiest way of doing that, though, as you would need one line for every item that can stack).

    Features
    <3 Supports having several items of the same type as ingredients in the same recipe.
    <3 Supports having the same item type as an ingredient in several recipes.
    <3 Allows to override the (customizable) default special point and attachment point by using the Ex functions.
    <3 All you need to do is add the recipes.
    <3 You can remove recipes.
    <3 You can add recipes with results that have charges.
    <3 The amount of charges can be predefined, the sum of charges left in the ingredients, or the amount of charges in the ingredient with most charges.
    <3 You can disable and enable single recipes or the whole system.
    <3 You can disassemble items.
    <3 You can use any event you want.

    Requirements
    A vJass compiler (NewGen includes one).
    Table by Vexorian.

    The Code
    JASS:
    library RecipeSYS initializer init uses Table
        
        //               ¤           v0.7b           ¤
        //              ( *'*-._.-*'*-._.-*'*-._.-*'* )
        //               )         RecipeSYS         (
        //              (    Created by Artificial    )
        //               )*'*-._.-*'*-._.-*'*-._.-*'*(
        //              ¤                             ¤
        //
        //                  How to use the RecipeSYS
        //                  ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
        //  ---- Adding recipes
        //      Adding recipes is rather easy. You just call a function
        //      and give it some arguments. The function you call is
        //      recipe.create, and is called like this:
        //
        //          call recipe.create(i1, i2, i3, i4, i5, i6, result, sfx, attPoint, charges, chargeType)
        //
        //      i1 - i6 are the raw codes* of the items required for the
        //      recipe to combine. result is the raw code of the item they
        //      will combine to. sfx is the path of the special effect you
        //      want to use (give "" for default), and attPoint the attachment
        //      point of it ("" for default). The boolean charges is whether
        //      the result should have some other amount of charges than the
        //      default one. If it is true, chargeType determines what amount
        //      of charges the result will get when combined (-1 => amount of
        //      charges left in the ingredient with most charges, 0 => sum of
        //      charges left in the ingredients, anything else => the number
        //      given as the argument).
        //
        //     * You can find out the rawcodes by going to the object
        //       editor and pressing ctrl + D. The first 4 characters
        //       are the rawcode of the item. When calling the function,
        //       remember to put the rawcode between ' and ' in case
        //       you want it to work.
        //
        //      If you want the recipe to require less than 6 items, just give
        //      zeros as ingredient arguments.
        //
        //          --- AddRecipe Wrappers
        //              You can also add recipes using some of the wrapper
        //              functions: AddRecipe, AddRecipeEx, AddRecipeWithCharges,
        //              and AddRecipeWithChargesEx.
        //              
        //              AddRecipe               takes i1, i2, i3, i4, i5, i6, result
        //              AddRecipeEx             takes i1, i2, i3, i4, i5, i6, result, sfx, attPoint
        //              AddRecipeWithCharges    takes i1, i2, i3, i4, i5, i6, result, chargeType
        //              AddRecipeWithChargesEx  takes i1, i2, i3, i4, i5, i6, result, chargeType, sfx, attPoint
        //
        //  ---- Removing Recipes
        //      There are two ways of removing recipes: destroyin a specific
        //      recipe and removing recipes by the result (removes all recipes
        //      with that item as result).
        //      Removing a specific recipe is done by calling the .destroy method
        //      for that recipe, and removing by result is done by using the
        //      RemoveRecipeByResult function, eg. like this:
        //
        //          call RemoveRecipeByResult('belv')
        //
        //  ---- Changing the Result / Ingredients
        //      Because of how the system works, you can't just do set r.result = 'aooo',
        //      but you must use the methods SetResult and SetIngredient.
        //      SetResult takes only the new item's raw code, and SetIngredient takes that
        //      and the ingredient number (0 - 5).
        //      
        //          call re.SetIngredient('rde1', 0)
        //
        //  ---- Disassembling Items
        //      Disassembling an item that is a result of some recipe would cause
        //      the item to be replaced with the ingredients of the recipe.
        //      Example of usage:
        //
        //          call DisassembleItem(GetManipulatedItem(), GetTriggerUnit())
        //
        //      The first argument is the item to be disassembled and the second
        //      one is the unit to give the ingredients to. In case you don't want
        //      to give the items to an unit, give null as the unit argument.
        //      You also have the DisassembleItemByRecipe function, which takes
        //      one argument more: the recipe whose ingredients you want the item
        //      to be disassembled to.
        //
        //      There are a few restrictions with the disassembling:
        //          - If the item being disassemled isn't created by the system,
        //            and used on an item that is the result of several recipes
        //            the system will just give the items that are required for one
        //            of the recipes.
        //          - When used on an recipe added via the charged recipe thingy
        //            the ingredient items won't recieve the amount of items they
        //            had when the result was made, but the default amount of charges.
        //
        //          Note: If you are using the function with the 'A unit Loses an item'
        //                event you'll need to add a little wait before the function
        //                call in case you want the items to be created at the position
        //                where the item was dropped. And the wait is also needed when
        //                the item being disassembled is the result of a recipe that has
        //                only the same type of items as ingredients (or at least I think so).
        //
        //  ---- Disabling and Re-Enabling Recipes
        //      Instead of removing a recipe to disable it and then adding the
        //      recipe again you can also disable and enable the recipe.
        //      This is done by setting the enabled member of the recipe to false
        //      (or to true if enabling). You can also use the EnableRecipeByResult
        //      function:
        //
        //          call EnableRecipeByResult('fgdg', false)
        //
        //      That would disable the recipes that have the item 'fgdg' as result.
        //      The enabling of the recipe would happen with the same function, but
        //      with true as the boolean argument.
        //
        //  ---- Disabling/Re-enabling the System
        //      If you need to disable/enable the system for some time, you can use these lines:
        //
        //          call DisableTrigger(RecipeSYS_TRIGGER)
        //          call EnableTrigger(RecipeSYS_TRIGGER)
        //
        //      Note, that this only disables the combining of items by the system itself,
        //      if you're using a combining trigger of your own, you'll need to disable that
        //      trigger.
        //
        //  ---- Manual Checking For Recipes
        //      In case you'd want to use some other event than the 'A unit Acquires an
        //      item', you can set AUTO_COMBINE to false, and create a trigger that has
        //      the event you want (eg. 'A unit Starts the effect of an ability'), and
        //      then call the RecipeSYS_Combine function from there. The arguments it takes
        //      are the item that should be checked if it belongs to a recipe and the unit
        //      the result should be created to (if any is created).
        //      The function returns a boolean depending on whether it combined the item
        //      to something or not. And if it combined something, you can refer to the
        //      created item with bj_lastCreatedItem (Last created item in GUI).
        //
        //     Strengths of RecipeSYS
        //     ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
        //     - Supports having several items of the same type as
        //       ingredients in the same recipe.
        //     - Supports having the same item type as an ingredient in
        //       several recipes.
        //     - Allows to specify the special effect and it's attachment
        //       point (each recipe can have its own ones, if you wish so).
        //     - All you need to do is add the recipes.
        //     - You can remove recipes.
        //     - You can add recipes with results that have charges.
        //     - The amount of charges can be predefined, the sum of charges
        //       left in the ingredients, or the amount of charges in the
        //       ingredient with most charges.
        //     - You can disable and enable single recipes or the whole sytem.
        //     - You can disassemble items.
        //     - You can disable the automatic combining of items, and create
        //       a trigger that combines items manually (so you can have any
        //       event you want).
        //
        //     Weaknesses of RecipeSYS
        //     ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
        //     - Don't know if it's optimized as much as it could nor if it's
        //       the best recipe system out there. ^_^
        //     - The item disassembling has some restrictions, and doesn't work
        //       flawlessy with the 'A unit Loses an item' event (see the Test2
        //       trigger in the demo map or the instructions of the function for
        //       additional information).
        //     - The max amount of item types you can use in recipes as
        //       ingredients is 8190 / AMOUNT_ING and as results 8190 / AMOUNT_RES.
        //
        //                  How to import RecipeSYS
        //                  ¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨
        //      Two easy steps:
        //          - Copy this trigger to your map.
        //          - Copy the trigger 'Table' to your map.
        //        Alternatively:
        //          - Create a trigger named 'RecipeSYS' in your map.
        //          - Convert it to Jass (Custom Script) and replace all
        //            of the code in there with the code in this trigger.
        //          - Create a trigger named 'Table' in your map.
        //          - Convert it to Jass (Custom Script) and replace all
        //            of the code in there with the code in the trigger 'Table'.
        
        globals
            
        //          ¤                           ¤
        //         ( *'*-._.-*'*-._.-*'*-._.-*'* )
        //          )  RecipeSYS configuration  (
        //         ( *'*-._.-*'*-._.-*'*-._.-*'* )
        //         ¤                             ¤
            
            private constant boolean AUTO_COMBINE = true
            //Whether you want the system to automatically combine
            //the ingredients of a recipe to the result when a unit
            //has all of them.
            private constant boolean NEED_PICK_UP = true
            // Whether the item actually has to be picked up by the
            // unit in order for the recipe to combine, or if it's
            // enough for the unit to be ordered to pick the item
            // (this as false will make you able to "pick" the last
            // ingredient needed for the recipe and combine the recipe
            // even if the inventory is full (still plays the sound "Inventory
            // is full" and displayes the message, but they can be hidden in
            // game interface)).
            private constant real MAX_RANGE = 200.
            // How far away from the item can the unit be in order to
            // be able to "pick it up" when NEED_PICK_UP is false.
            private constant string SFX_PATH  = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
            //The path of the default effect.
            private constant string SFX_POINT = "origin"
            //The default attachment point.
            private constant integer AMOUNT_ING = 10
            //The amount of recipes an item can be an ingredient in.
            private constant integer AMOUNT_RES = 10
            //The amount of recipes an item can be a result in.
            
        //          ¤                           ¤
        //         ( *'*-._.-*'*-._.-*'*-._.-*'* )
        //          )DO NOT EDIT PAST THIS POINT(
        //         ( *'*-._.-*'*-._.-*'*-._.-*'* )
        //          )Well, you can if you really(
        //         ( want to, but only if you    )
        //          )know what you're doing. And(
        //         ( it's not needed, anyways.   )
        //          )*'*-._.-*'*-._.-*'*-._.-*'*(
        //         ¤                             ¤
            
            private constant real MAX_RANGE_POW2 = MAX_RANGE * MAX_RANGE
            public constant trigger TRIGGER = CreateTrigger()
            private Table ING_TABLE
            private Table RES_TABLE
            private HandleTable COMBINE_TABLE
        endglobals
        
        private type ingIn extends recipe array [AMOUNT_ING]
        private type resIn extends recipe array [AMOUNT_RES]
        
        struct recipe
            readonly integer array items [6]
            readonly integer result
            integer chargeType
            string sfx
            string point
            boolean resultHasCharges
            boolean enabled
            
            static method create takes integer i1, integer i2, integer i3, integer i4, integer i5, integer i6, integer result, string sfx, string attPoint, boolean charges, integer chargetype returns recipe
                local recipe i = recipe.allocate()
                local ingIn ing
                local resIn res
                local boolean error = false // Stupid 1.24
                local boolean array b
                local integer j = 0
                local integer k
                local integer l = 0
                set i.items[0] = i1
                set i.items[1] = i2
                set i.items[2] = i3
                set i.items[3] = i4
                set i.items[4] = i5
                set i.items[5] = i6
                set i.result   = result
                set i.enabled  = true
                set i.resultHasCharges  = charges
                set i.chargeType        = chargetype
                
                //To prevent linking the recipe to the item type twice.
                set b[0] = true
                set b[1] = i2 != i1
                set b[2] = i3 != i2 and i3 != i1
                set b[3] = i4 != i3 and i4 != i2 and i4 != i1
                set b[4] = i5 != i4 and i5 != i3 and i5 != i2 and i5 != i1
                set b[5] = i6 != i5 and i6 != i4 and i6 != i3 and i6 != i2 and i6 != i1
                
                //Set the SFX things (default or specified).
                if sfx == "" then
                    set i.sfx = SFX_PATH
                else
                    set i.sfx = sfx
                endif
                if attPoint == "" then
                    set i.point = SFX_POINT
                else
                    set i.point = attPoint
                endif
                
                //Linking the recipe to the item types.
                loop
                    if i.items[j] != 0 and b[j] then
                        set k = ING_TABLE[i.items[j]]
                        if k != 0 then
                            //Item type is in another recipe aswell.
                            set ing = ingIn(k)
                            set k = 0
                            //Seek for an open slot.
                            loop
                                exitwhen error or ing[k] == 0
                                set k = k + 1
                                if k == ingIn.size then
                                    debug call BJDebugMsg("|cffff0202RecipeSYS Error:|r AMOUNT_ING is too small!")
                                    set error = true
                                endif
                            endloop
                        else
                            //First appearance for the item type.
                            set ing = ingIn.create()
                            set k = 0
                        endif
                        
                        exitwhen error
                        
                        set ing[k] = i
                        set ING_TABLE[i.items[j]] = integer(ing)
                    endif
                    set j = j + 1
                    exitwhen j == 6
                endloop
                
                if not error then
                    set k = RES_TABLE[i.result]
                    if k != 0 then
                        //Item type is in some other recipe, too.
                        set res = resIn(k)
                        set k = 0
                        loop
                            exitwhen error or res[k] == 0
                            set k = k + 1
                            if k == resIn.size then
                                debug call BJDebugMsg("|cffff0202RecipeSYS Error:|r AMOUNT_RES too small!")
                                set error = true
                            endif
                        endloop
                    else
                        //First time being in a recipe for the item type.
                        set res = resIn.create()
                    endif
                    
                    if not error then
                        set res[k] = i
                        set RES_TABLE[i.result] = integer(res)
                    endif
                endif
                
                if error then
                    call i.destroy()
                    return recipe(0)
                else
                    return i
                endif
            endmethod
            
            private method ClearRecipeFromIngredient takes integer it returns nothing
                local integer i = 0
                local integer j = 0
                local integer k
                local ingIn ing = ingIn(ING_TABLE[it])
                
                if it == 0 or integer(ing[0]) == 0 then
                    return
                endif
                
                //Finding the right array slot and setting it's value to 0.
                loop
                    exitwhen i == ingIn.size
                    if ing[i] == this then
                        set j = i + 1
                        
                        //Finding the last used array slot.
                        loop
                            exitwhen j == ingIn.size or ing[j] == 0
                            set j = j + 1
                        endloop
                        set j = j - 1
                        
                        set ing[i] = ing[j]
                        set ing[j] = 0
                        exitwhen true
                    endif
                    
                    set i = i + 1
                endloop
                
                //Free the array and flush if the slots are empty.
                if integer(ing[0]) == 0 then
                    call ING_TABLE.flush(it)
                    call ing.destroy()
                endif
            endmethod
            
            private method ClearRecipeFromResult takes integer it returns nothing
                local integer i = 0
                local integer j
                local resIn res = resIn(RES_TABLE[it])
                
                if it == 0 then
                    return
                endif
                
                //Finding the right array slot and setting it's value to 0.
                loop
                    exitwhen i == resIn.size
                    if res[i] == this then
                        set j = i + 1
                        
                        //Finding the last used array slot.
                        loop
                            exitwhen j == resIn.size or res[j] == 0
                            set j = j + 1
                        endloop
                        set j = j - 1
                        
                        set res[i] = res[j]
                        set res[j] = 0
                        exitwhen true
                    endif
                    
                    set i = i + 1
                endloop
                
                //Free the array slots and flush if it's empty.
                if res[0] == 0 then
                    call RES_TABLE.flush(it)
                    call res.destroy()
                endif
            endmethod
            
            // Used in SetIngredient to clear the old slot.
            private method ClearSlot takes integer slot returns nothing
                local integer i = 0
                
                loop
                    exitwhen i == 6
                    if slot != i and .items[i] == .items[slot] then
                        // If the item is several times in the recipe,
                        // setting this one to 0 is enough.
                        set .items[slot] = 0
                        return
                    endif
                    set i = i + 1
                endloop
                
                // Otherwise we need to clear the recipe from the item.
                call .ClearRecipeFromIngredient(.items[slot])
            endmethod
            
            method SetIngredient takes integer id, integer slot returns nothing
                local ingIn ing
                call .ClearSlot(slot)
                set .items[slot] = id
                set slot = ING_TABLE[id]
                if slot != 0 then
                    //Item type is in another recipe aswell.
                    set ing = ingIn(slot)
                    set slot = 0
                    //Seek for an open slot.
                    loop
                        exitwhen ing[slot] == 0
                        set slot = slot + 1
                        if slot == ingIn.size then
                            debug call BJDebugMsg("|cffff0202RecipeSYS Error:|r AMOUNT_ING is too small!")
                            return
                        endif
                    endloop
                else
                    //First appearance for the item type.
                    set ing = ingIn.create()
                    set slot = 0
                endif
                set ing[slot] = this
                set ING_TABLE[id] = integer(ing)
            endmethod
            
            method SetResult takes integer id returns nothing
                local resIn res
                local integer k = RES_TABLE[id]
                call .ClearRecipeFromResult(.result)
                set .result = id
                
                if k != 0 then
                    //Item type is in some other recipe, too.
                    set res = resIn(k)
                    set k = 0
                    loop
                        exitwhen res[k] == 0
                        set k = k + 1
                        if k == resIn.size then
                            debug call BJDebugMsg("|cffff0202RecipeSYS Error:|r AMOUNT_RES too small!")
                            return
                        endif
                    endloop
                else
                    //First time being in a recipe for the item type.
                    set res = resIn.create()
                endif
                set res[k] = this
                set RES_TABLE[.result] = integer(res)
            endmethod
            
            method onDestroy takes nothing returns nothing
                local integer i = 0
                loop
                    call .ClearRecipeFromIngredient(.items[i])
                    set i = i + 1
                    exitwhen i == 6
                endloop
                
                call .ClearRecipeFromResult(.result)
                set .result = 0
            endmethod
        endstruct
        
        //****************************************************
        //**    Recipes Without Charges                     **
        //****************************************************
        
        function AddRecipe takes integer item1, integer item2, integer item3, integer item4, integer item5, integer item6, integer result returns recipe
            return recipe.create(item1, item2, item3, item4, item5, item6, result, "", "", false, 0)
        endfunction
        
        function AddRecipeEx takes integer item1, integer item2, integer item3, integer item4, integer item5, integer item6, integer result, string sfx, string attachmentPoint returns recipe
            return recipe.create(item1, item2, item3, item4, item5, item6, result, sfx, attachmentPoint, false, 0)
        endfunction
        
        //****************************************************
        //**    Recipes With Charges                        **
        //****************************************************
        
        function AddRecipeWithCharges takes integer item1, integer item2, integer item3, integer item4, integer item5, integer item6, integer result, integer chargeType returns recipe
            return recipe.create(item1, item2, item3, item4, item5, item6, result, "", "", true, chargeType)
        endfunction
        
        function AddRecipeWithChargesEx takes integer item1, integer item2, integer item3, integer item4, integer item5, integer item6, integer result, integer chargeType, string sfx, string attachmentPoint returns recipe
    
            return recipe.create(item1, item2, item3, item4, item5, item6, result, sfx, attachmentPoint, true, chargeType)
        endfunction
        
        //****************************************************
        //**    Removing Recipes                            **
        //****************************************************
        
        function RemoveRecipeByResult takes integer result returns nothing
            local resIn res = resIn(RES_TABLE[result])
            
            loop
                exitwhen integer(res[0]) == 0
                call res[0].destroy()
            endloop
        endfunction
        
        //****************************************************
        //**    Disabling and enabling recipes              **
        //****************************************************
        
        function EnableRecipeByResult takes integer result, boolean enable returns nothing
            local resIn res = resIn(RES_TABLE[result])
            local integer i = 0
            
            loop
                exitwhen integer(res[i]) == 0
                set res[i].enabled = enable
                set i = i + 1
            endloop
        endfunction
        
        //****************************************************
        //**    Disassembling Combined Items                **
        //****************************************************
        
        function DisassembleItemByRecipe takes item whichItem, unit whichUnit, recipe re returns nothing
            local integer i = 0
            local real x = GetItemX(whichItem)
            local real y = GetItemY(whichItem)
            local boolean b = IsTriggerEnabled(GetTriggeringTrigger())
            local boolean c = IsTriggerEnabled(TRIGGER)
            
            if re.result == GetItemTypeId(whichItem) then
                if b then
                    //Avoid clashing with some events.
                    call DisableTrigger(GetTriggeringTrigger())
                endif
                call COMBINE_TABLE.flush(whichItem)
                call RemoveItem(whichItem)
                
                //Should the items be created for a unit or at ground?
                if whichUnit == null then
                    loop
                        call CreateItem(re.items[i], x, y)
                        set i = i + 1
                        exitwhen i == 6
                    endloop
                else
                    if c then
                        //Preventing infinite loop.
                        call DisableTrigger(TRIGGER)
                    endif
                    
                    loop
                        call UnitAddItemById(whichUnit, re.items[i])
                        set i = i + 1
                        exitwhen i == 6
                    endloop
                    
                    if c then
                        call EnableTrigger(TRIGGER)
                    endif
                endif
                
                if b then
                    call EnableTrigger(GetTriggeringTrigger())
                endif
            endif
        endfunction
        
        function DisassembleItem takes item whichItem, unit whichUnit returns nothing
            local integer id = GetItemTypeId(whichItem)
            local resIn r = resIn(RES_TABLE[id])
            local recipe re = recipe(COMBINE_TABLE[whichItem])
            
            if integer(re) == 0 or re.result != id then //The last one just in case the recipe has been destroyed.
                if integer(r[0]) == 0 then
                    return
                endif
                set re = r[0]
            endif
            
            call DisassembleItemByRecipe(whichItem, whichUnit, re)
        endfunction
        
        //****************************************************
        //**    Combining                                   **
        //****************************************************
        
        private function DoCombine takes recipe rec, unit u, item noncarried returns boolean
            local integer array itemsReq
            local integer noncarriedId = GetItemTypeId(noncarried)
            local integer itemInSlot
            local integer topCharges = 0
            local integer sumCharges = 0
            local integer i = 0
            local integer j = 0
            local item resu
            local item array itemsToCombine
            
            //Setting the items.
            loop
                set itemsReq[i] = rec.items[i]
                set i = i + 1
                exitwhen i == 6
            endloop
            
            set i = 0
            //Finding the items.
            loop
                set itemInSlot = GetItemTypeId(UnitItemInSlot(u, i))
                loop
                    if itemsReq[j] == noncarriedId and noncarried != null then
                        set itemsToCombine[j] = noncarried
                        set itemsReq[j] = 0
                        set noncarried  = null
                        exitwhen true
                    elseif itemsReq[j] == itemInSlot and itemInSlot != 0 then
                        set itemsToCombine[j] = UnitItemInSlot(u, i)
                        set itemsReq[j] = 0
                        exitwhen true
                    endif
                    set j = j + 1
                    exitwhen j == 6
                endloop
                set j = 0
                set i = i + 1
                exitwhen i == 6
            endloop
            set noncarried = null
            
            set i = 0
            set j = 0
            //Checking if all items were found.
            loop
                if itemsReq[i] != 0 then
                    loop
                        set itemsToCombine[j] = null
                        set j = j + 1
                        exitwhen j == 6
                    endloop
                    return false
                endif
                set i = i + 1
                exitwhen i == 6
            endloop
            
            set i = 0
            //Remove the old items.
            loop
                //Set the different charge amounts in case the result has charges.
                set j = GetItemCharges(itemsToCombine[i])
                set sumCharges = sumCharges + j
                if j > topCharges then
                    set topCharges = j
                endif
                
                call COMBINE_TABLE.flush(itemsToCombine[i])
                call RemoveItem(itemsToCombine[i])
                set itemsToCombine[i] = null
                set i = i + 1
                exitwhen i == 6
            endloop
            
            //Create the result.
            set resu = UnitAddItemById(u, rec.result)
            call DestroyEffect(AddSpecialEffectTarget(rec.sfx, u, rec.point))
            
            //Set charges if it should have any.
            if rec.resultHasCharges then
                if rec.chargeType == 0 then
                    call SetItemCharges(resu, sumCharges)
                elseif rec.chargeType == -1 then
                    call SetItemCharges(resu, topCharges)
                else
                    call SetItemCharges(resu, rec.chargeType)
                endif
            endif
            
            //Set the data if wanted.
            set COMBINE_TABLE[resu] = integer(rec)
            
            set bj_lastCreatedItem = resu
            set resu = null
            return true
        endfunction
        
        //****************************************************
        //**    Recipe Finding                              **
        //****************************************************
        
        function GetRecipeItemCombinedFrom takes item i returns recipe
            local recipe r = recipe(COMBINE_TABLE[i])
            if integer(r) == 0 or GetItemTypeId(i) != r.result then
                set r = ingIn(ING_TABLE[GetItemTypeId(i)])[0]
            endif
            return r
        endfunction
        
        public function Combine takes item check, unit u returns boolean
            local boolean isCarried = UnitHasItem(u, check)
            local ingIn r = ingIn(ING_TABLE[GetItemTypeId(check)])
            local integer i = 0
            
            if check == null then
                return false
            endif
            
            loop
                exitwhen integer(r[i]) == 0 or i == r.size
                
                if r[i].enabled then
                    if isCarried then
                        if DoCombine(r[i], u, null) then
                            return true
                        endif
                    else
                        if DoCombine(r[i], u, check) then
                            return true
                        endif
                    endif
                endif
                set i = i + 1
            endloop
            
            return false
        endfunction
        
        private function Check takes nothing returns boolean
            local real dx
            local real dy
            
            if GetIssuedOrderId() == OrderId("smart") then
                if GetOrderTargetItem() == null then
                    return false
                endif
                set dx = GetItemX(GetOrderTargetItem()) - GetUnitX(GetTriggerUnit())
                set dy = GetItemY(GetOrderTargetItem()) - GetUnitY(GetTriggerUnit())
                if dx * dx + dy * dy <= MAX_RANGE_POW2 then
                    return Combine(GetOrderTargetItem(), GetTriggerUnit())
                endif
                return false
            endif
            
            return Combine(GetManipulatedItem(), GetTriggerUnit())
        endfunction
        
        //=====================================================================
        private function SafeFilt takes nothing returns boolean
            return true
        endfunction
        //=====================================================================
        
        private function init takes nothing returns nothing
            local integer index = 0
            if AUTO_COMBINE then
                loop
                    call TriggerRegisterPlayerUnitEvent(TRIGGER, Player(index), EVENT_PLAYER_UNIT_PICKUP_ITEM, Filter(function SafeFilt))
                    if not NEED_PICK_UP then
                        call TriggerRegisterPlayerUnitEvent(TRIGGER, Player(index), EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, Filter(function SafeFilt))
                    endif
                    set index = index + 1
                    exitwhen index == bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddCondition(TRIGGER, Condition(function Check))
            endif
            set ING_TABLE = Table.create()
            set RES_TABLE = Table.create()
            set COMBINE_TABLE = HandleTable.create()
        endfunction
        
    endlibrary


    Example of Usage
    Trigger:
    • LoopingTest
      • Events
        • Map initialization
      • Conditions
      • Actions
        • -------- Recipe 1 --------
        • Set Ingredient1[1] = Ring of Protection +3
        • Set Ingredient2[1] = Ring of Protection +3
        • Set Result[1] = Boots of Quel'Thalas +6
        • -------- Recipe 2 --------
        • Set Ingredient1[2] = Ring of Protection +2
        • Set Ingredient2[2] = Slippers of Agility +3
        • Set Result[2] = Ring of Protection +3
        • -------- Recipe 2 --------
        • Set Ingredient1[3] = Demonic Figurine
        • Set Ingredient2[3] = Boots of Quel'Thalas +6
        • Set Ingredient3[3] = Boots of Quel'Thalas +6
        • Set Ingredient4[3] = Slippers of Agility +3
        • Set Result[3] = Cheese
        • -------- 3 is the amount of recipes --------
        • For each (Integer A) from 1 to 3, do (Actions)
          • Loop - Actions
            • Custom script: call AddRecipe(udg_Ingredient1[bj_forLoopAIndex], udg_Ingredient2[bj_forLoopAIndex], udg_Ingredient3[bj_forLoopAIndex], udg_Ingredient4[bj_forLoopAIndex], udg_Ingredient5[bj_forLoopAIndex], udg_Ingredient6[bj_forLoopAIndex], udg_Result[bj_forLoopAIndex])


    Importing
    Copy the trigger called RecipeSYS from the attached map or create a trigger named RecipeSYS, convert it to custom text, and replace everything inside it with the code above.

    Q&A
    Q: Help! I have a problem!
    A: I'd suggest you post your problem as a separate thread in the World Editor Help forum (so more people can see it and help you) and link to that thread either here or in some other kind of message to me so I can take a look as well. Some information you should try to include in that thread:
    • Your RecipeSYS configuration (especially AMOUNT_ING and AMOUNT_RES).
    • The code that creates the recipes. If you can't post all of the code, at least post all the parts that have anything to do with the 20th and 21st recipe (including used variables and possible loops).
    • Any debug messages you see when the system stops working. If you don't see any, make sure you're saving the map in debug mode (I can't remember where to check it, but I think it's something like "JASS Helper > Debug Mode" in the main editor window's menu).

    Changelog
    Code:
    v0.7b
     Fixed 1.24 compatibility issues (stupid patch).
     Fixed some bugs with the NEED_PICK_UP stuff.
      When NEED_PICK_UP is true and a unit with full inventory is ordered 
      to pick up an item that's too far away for it to pick up, it won't 
      automatically be ordered to move to the item anymore. (This had
      to be done to fix a bug.)
    
    v0.7
     Renamed CheckItemRecipes to RecipeSYS_Combine.
     Added the option to pick up the last ingredient needed for a recipe even though the inventory is full.
      DoCombine updated a bit (and name changed a bit).
      Added NEED_PICK_UP.
      Added MAX_RANGE.
     Added method SetIngredient.
     Added method SetResult.
     Added GetRecipeItemCombinedFrom.
     Made members "items" and "result" read-only fields.
    
    v0.6d
     Made the system flush the entrys for items it destroys.
     Removed a scenario that made the system try to destroy unexistant dynamic arrays.
    
    v0.6c
     Removed some duplication of code.
     Removed the usage of item data, now using Table for it.
     Removed some unnecessary variables.
     Made ClearRecipeFrom... methods free the arrays and flush tha table thingy when the array is empty.
     Renamed ingTable to ING_TABLE and resTable to RES_TABLE so it'd be easier to see they are globals.
    
    
    v0.6b
     Split up ClearRecipeFromItem to ClearRecipeFromIngredient and ClearRecipeFromResult.
     Split up GetItemTypeRecipes to GetResultRecipes and GetIngredientRecipes.
     Removed GetResultRecipes and GetIngredientRecipes.
     Made ClearRecipeFromItem take care of holes in the arrays.
     Made ingIn and resIn extend recipe arrays instead of integer arrays.
     Removed the type recipes.
     Removed the recipe array RECIPES.
     Removed count (the global variable).
     Changed StringTables to Tables.
    
    v0.6
     Changed pretty much stuff.
     Made it use Table (by Vexorian) for better performance.
     The functions now take and return recipes instead of ids.
     The struct recipe is not private anymore.
     You can now also create recipes with the create method of the struct.
     Some functions were renamed:
     - EnableRecipe        -> EnableRecipeByResult
     - EnableRecipeById    -> set (yourRecipe).enabled =
     - RemoveRecipe        -> RemoveRecipeByResult
     - RemoveRecipeById    -> (yourRecipe).destroy
     - DisassembleItemById -> DisassembleItemByRecipe
     Added GetItemTypeRecipes and RecipesRelease.
     Added three globals: AMOUNT_ING, AMOUNT_RES, and AMOUNT_RECS.
    
    
    0.5b
     Fixed the bug with having AUTO_COMBINE as false making it unpossible to uncombine items.
    
    0.5
     Added CheckForRecipes.
     Added AUTO_COMBINE.
     Made TRIGGER unconstant.
     Made Combine store the result to bj_lastCreatedItem.
    
    0.4
     Fixed a problem with the DisassembleItem function enabling triggers.
     Added DisassembleItemById.
     Added USE_ITEM_DATA.
     Some minor changes in a couple of functions.
    
    0.3
     Made the functions that add the recipes return the id of the recipe (used for 'ById' functions).
     Added RemoveRecipeById.
     Added EnableRecipeById.
     Removed the "set RECIPES[count] = 0" from RemoveRecipe.
     Made Combine return a boolean and updated Check a bit.
    
    0.2
     Added EnableRecipe function (suggested by Tom_Kazansky).
     Added DisassembleItem function (suggested by waaaks!).
     Added the global trigger RecipeSYS_TRIGGER so you can disable and enable the system completely.
    
    0.1
     Made it possible to use the same item as an ingredient for several recipes.
     Added support for charged results.
     
    0.0b
     Inlined TriggerRegisterAnyUnitEvent (suggested by T.s.e).
     Changed the action to condition (suggested by T.s.e).
     
    0.0
     First release.
     

    Attached Files:

    • Like Like x 14
  2. Tom_Kazansky

    Tom_Kazansky --- wraith it ! ---

    Ratings:
    +157 / 0 / -0
    I suggest you should add a DisableRecipe function, temporary disabling a recipe.

    For example:
    I have 5 recipe: recipe1, recipe2, recipe3, recipe4, recipe5 and the recipe 2 is needed for recipe 5.
    When I disassemble the recipe2 and I don't want the new items (its components) to assemble again (in case I add the components to the hero), I have to disable the recipe2.
    If I use RemoveRecipe( recipe2 ), so the recipe queue will be: recipe1, recipe3, recipe4, recipe5 (right ?) and then re-add the recipe2, the recipe queue now is: recipe1, recipe3, recipe4, recipe5, recipe 2 => so recipe5 is checked before the recipe2 and I have to drop the recipe2 item, repick it to get the recipe5. (right ?)
     
    • Like Like x 1
  3. waaaks!

    waaaks! Zinctified

    Ratings:
    +256 / 0 / -0
    recipe with charges? nice

    i suggest adding a disassemble recipe function, where u pick a recipe item from the hero, remove it then drops the ingredients
     
    • Like Like x 1
  4. Venge22

    Venge22 New Member

    Ratings:
    +10 / 0 / -0
    :( u dont have a w3m map with that example?
     
  5. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    @Tom_Kazansky
    I don't think you would need to drop the item, as when you will acquire the result of the recipe 2, the trigger will fire again and check if it is the ingredient of some recipe.
    But I might still add a DisableRecipe function, as someone might want to use it over removing the recipe and then creating it again, and it wouldn't be hard to make.
    Thanks for the suggestion. :)

    @waaaks!
    A disassembling function might be nice, but it might be hard to do it, as an item can be the result for several recipes. Eg. if you have 2 recipes: one that has item1 and item2 as ingredients and item5 as the result, and one that has item3 and item4 as ingredients and item5 as the result. I'm not sure about how easy it'd be to find out whether the items 1 and 2 or the items 3 or 4 should be given when the item 5 is disassembled.
    I'll add this, though, if I figure it out or something. Thanks for the suggestion. :p

    @Venge22
    Having that example as a RoC version is hard, as it seems like the 'Custom Script' line action is a TFT action. But I made you this map that's compatible with RoC but has the example trigger in Jass (and an uglier terrain).
     

    Attached Files:

    • Like Like x 3
  6. cr4xzZz

    cr4xzZz Also known as azwraith_ftL.

    Ratings:
    +51 / 0 / -0
    Err, am I blind or I misunderstood something... But to whom do you give the recipes? There is not unit argument in the functions...
     
  7. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    You create the recipes at eg. map initialization (as in the example), and the system will give the result to the unit that acquires all of the ingredients. And even if this wouldn't work that way, a unit argument wouldn't be needed as the function could still use GetTriggerUnit.

    EDIT:

    New version (0.2)!
    Added the functions EnableRecipe and DisassembleItem, and the public global TRIGGER.
     
  8. Tom_Kazansky

    Tom_Kazansky --- wraith it ! ---

    Ratings:
    +157 / 0 / -0
    This is what I need, good job :thup:
     
  9. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    Version 0.3 is here with some changes and two new functions (they might even be useful for someone).
    Code:
     Made the functions that add the recipes return the id of the recipe (used for 'ById' functions).
     Added RemoveRecipeById.
     Added EnableRecipeById.
     Removed the "set RECIPES[count] = 0" from RemoveRecipe as unnecessary.
     Made Combine return a boolean and updated Check a bit to avoid unnecessary stuff do be processed.
    Wouldn't mind getting some suggestions so I could make better updates. Also other comments would be appreciated. ^_^

    Nice to heart that. :)
     
  10. Trollvottel

    Trollvottel never aging title

    Ratings:
    +261 / 0 / -0
    seems userfriendly and useful to me.

    documentation is good.

    + 4 rep from me
     
  11. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    And thanks for the ones that already have made my previous posts useful. ^_^

    EDIT:

    The version 0.4 is now available with some fixes, additions, and improvements.
    Code:
     Fixed a problem with the DisassembleItem function enabling the triggering trigger and the RecipeSYS trigger even if they were disabled.
     Added the function DisassembleItemById.
     Added an option to use item custom values (the boolean USE_ITEM_DATA) in order to get more accurate disassembling when having several recipes with the same result.
     Some minor changes in a couple of functions in order to make the custom value usage work.

    EDIT:

    Version 0.5 is out!
    Added a possibility to use any event you want to combine the items. You can now also refer to the combined item by using bj_lastCreatedItem (Last created item in GUI).
    Code:
     Added the custom check function and AUTO_COMBINE (=> changed the initialization a bit, made TRIGGER unconstant).
     Made Combine set the created result to bj_lastCreatedItem for easier access (might be useful when using the custom check function).
    I'm thinking about renaming 'CheckForRecipes', but I can't come up with stuff that'd be good as the name. These are the ones I've come up with:
    Code:
    RecipeSYS_Check
    CheckItemForRecipes
    CheckRecipes
    CheckForRecipes <-- In use
    CombineItem
    And my personal favourite:
    Code:
    CheckIfItemBelongsToSomeRecipeAndCombineTheItemsIfItDoesAndAllItemsAreFound
    That'd certainly say what it does, unlike most of the other ones, but this is a bit long, don't you think? :(

    Anyone has suggestions for a short, descriptive name? :p
     
  12. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    Version 0.6 is now usable.
    There were quite a few changes, mainly because of making it better performance-wise.
    Code:
    v0.6
     Changed pretty much stuff.
     Made it use Table (by Vexorian) for better performance.
     The functions now take and return recipes instead of ids.
     The struct recipe is not private anymore.
     You can now also create recipes with the create method of the struct.
     Some functions were renamed:
     - EnableRecipe        -> EnableRecipeByResult
     - EnableRecipeById    -> set (yourRecipe).enabled =
     - RemoveRecipe        -> RemoveRecipeByResult
     - RemoveRecipeById    -> (yourRecipe).destroy
     - DisassembleItemById -> DisassembleItemByRecipe
     Added GetItemTypeRecipes and RecipesRelease.
     Added three globals: AMOUNT_ING, AMOUNT_RES, and AMOUNT_RECS.

    Edit: Version 0.6b is now here aswell.
    Code:
    v0.6b
     Split up ClearRecipeFromItem to ClearRecipeFromIngredient and ClearRecipeFromResult.
     Split up GetItemTypeRecipes to GetResultRecipes and GetIngredientRecipes.
     Removed GetResultRecipes and GetIngredientRecipes.
     Made ClearRecipeFromItem take care of holes in the arrays.
     Made ingIn and resIn extend recipe arrays instead of integer arrays.
     Removed the type recipes.
     Removed the recipe array RECIPES.
     Removed count (the global variable).
     Changed StringTables to Tables.

    Edit2: Version 0.6c is out, too (less changes this time).
    Code:
    v0.6c
     Removed some duplication of code.
     Removed the usage of item data, now using Table for it.
     Removed some unnecessary variables.
     Made ClearRecipeFrom... methods free the arrays and flush tha table thingy when the array is empty.
     Renamed ingTable to ING_TABLE and resTable to RES_TABLE so it'd be easier to see they are globals.

    Edit3: Version 0.6d is also available. :D
    This version has been approved at WC3C, btw.
    Code:
    v0.6d
     Made the system flush the entrys for items it destroys.
     Removed a scenario that made the system try to destroy unexistant dynamic arrays.
     
    • Like Like x 1
  13. SerraAvenger

    SerraAvenger Cuz I can

    Ratings:
    +236 / 0 / -0
    really interesting, just that I wouldn't use gc for this ; )
     
  14. Tukki

    Tukki is Skeleton Pirate.

    Ratings:
    +29 / 0 / -0
    Actually, it's a lot more efficient than looping through all items in the unit's inventory to check if they match a recipe.
    (As long as you have more than 5 recipes that is)

    EDIT: I missed to comment on the system ^^

    It looks good and the fact that it uses Table instead of some own-initializated cache is good :thup:
    Also that it may dissemble recipes is nice :)

    Good job on this one sir :)
    + Rep
     
  15. SerraAvenger

    SerraAvenger Cuz I can

    Ratings:
    +236 / 0 / -0
    not really. Just depends on what you're using. If you use a normal search, then for sure. But a good hashtable is faster I would say.
     
  16. Tukki

    Tukki is Skeleton Pirate.

    Ratings:
    +29 / 0 / -0
    It could be, but then you'd need to create one. And using Table is really simple, and is faster than an O(n) search as long as there are more than 4 recipes.

    So basically it doesn't matter THAT much -- it works nice and should not lag unless you have some abnormally large amount of recipes upon pickup.
     
  17. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    > I wouldn't use gc for this
    At first the system was just looping through all of the recipes, but then I was adviced at WC3C to do something about it; either use a hashtable or gamecache. As I had no clue about which one is better, I made versions for both. No one said which one would be better, but Anitarf said I should rather use Table than my own GC, so I took that as a hint for going with Table instead of hashing. :p
     
  18. quraji

    quraji zap

    Ratings:
    +143 / 0 / -0
    Nice system =]

    Seems to work just dandy, and I like the effect and sound, nice touch.

    Good work :thup:

    Note: I haven't tested it thoroughly yet, when/if I do I'll come back and tell you if I broke it or not :D
     
  19. Artificial

    Artificial Without Intelligence

    Ratings:
    +325 / 0 / -0
    I hope you manage to break it so I'll get something to do. :p
     
  20. Nexor

    Nexor ...

    Ratings:
    +74 / 0 / -0
    I tested the map and when I picked up 2 demonic figurine it converted to one with 2 charges, after that I picked another demonic figurine, and put this one with 3 charges on the floor but there was only 2 with 1 charge :S
     
    • Like Like x 1

Share This Page