Recipe Engine v2.0.0.0

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
This system is basically an update to Diablo-dk's Recipe System.
It functioned perfectly, but it needed a new API, and it needed to be more efficient.

Please don't rate it based on originality. It's not my fault lots of new comers upload their crap.

Here's the code:

JASS:
/***************************************************
*
*   Recipe Engine
*   v2.0.0.0
*   By Magtheridon96
*
*   - Allows the creation of item recipes with charged items as requirements.
*   - Recipe limit: 1365 (682 if PLAYER_SPECIFIC_RECIPES = true)
*   - If you require more recipes, contact me and I will give 
*     a special version of this system with a limit of 8191 recipes.
*
*   Very Important:
*   ---------------
*
*       - If PLAYER_SPECIFIC_RECIPES == true:
*           - Players will be authorized by default
*           - Max Recipe Count will go down (By 50%)
*
*   API:
*   ----
*
*       ---------------------------------------------------------------------------
*       |                               Struct API                                |
*       ---------------------------------------------------------------------------
*
*       struct Recipe extends array
*
*           - static boolean enabled
*               - Used to enable or disable the system.
*
*           - method requires takes integer id, integer charges returns thistype
*               - Adds a requirement to a recipe given the required item Id and the required charges
*           - static method create takes integer id, integer charges returns thistype
*               - Creates a recipe object given the result and the desired charges
*           - method enable takes nothing returns nothing
*               - Enables the compiling of a recipe
*           - method disable takes nothing returns nothing
*               - Disables the compiling of a recipe
*           - method isEnabled takes nothing returns boolean
*               - Determines if a recipe is enabled or not
*           - static method disassemble takes item whichItem returns integer
*               - Disassembles an item regardless of whether it is carried or not
*               - If DisassembleItem returns 0 -> Success
*               - If DisassembleItem returns 1 -> Invalid Item
*               - If DisassembleItem returns 2 -> Not enough space
*
*           For Optional Player Specific Recipes:
*
*           - method authorize takes integer id returns nothing
*               - Allows a player to compile a recipe.
*           - method unauthorize takes integer id returns nothing
*               - Remove player authorization of a recipe. Players are not authorized by default.
*           - method isAuthorized takes integer id returns boolean
*               - Checks whether a player is authorized or not.
*
*       ---------------------------------------------------------------------------
*       |                                 Jass API                                |
*       ---------------------------------------------------------------------------
*
*       - function CreateRecipe takes integer result, integer charge returns Recipe
*           - Creates a recipe object given the result and the desired charges.
*       - function AddRecipeRequirement takes Recipe this, integer id, integer charges returns nothing
*           - Adds a requirement to a recipe given the required item Id and the required charges.
*       - function EnableRecipe takes Recipe this returns nothing
*           - Enables the compiling of a recipe
*       - function DisableRecipe takes Recipe this returns nothing
*           - Disables the compiling of a recipe
*       - function IsRecipeEnabled takes Recipe this returns boolean
*           - Determines if a recipe is enabled or not
*       - function EnableRecipeSystem takes nothing returns nothing
*           - Enables the system. The system is enabled by default.
*       - function DisableRecipeSystem takes nothing returns nothing
*           - Disables the system.
*       - function IsRecipeSystemEnabled takes nothing returns boolean
*           - Checks whether the system is enabled or not.
*       - function DisassembleItem takes item it returns integer
*           - Disassembles an item regardless of whether it is carried or not.
*           - If DisassembleItem returns 0 -> Success
*           - If DisassembleItem returns 1 -> Invalid Item
*           - If DisassembleItem returns 2 -> Not enough space
*
*       For Optional Player Specific Recipes:
*
*       - function AuthorizePlayerRecipe takes player p, Recipe this returns nothing
*       - function AuthorizePlayerRecipeById takes integer p, Recipe this returns nothing
*           - Allows a player to compile a recipe.
*
*       - function UnauthorizePlayerRecipe takes player p, Recipe this returns nothing
*       - function UnauthorizePlayerRecipeById takes integer p, Recipe this returns nothing
*           - Remove player authorization of a recipe. Players are not authorized by default.
*
*       - function IsPlayerAuthorizedRecipe takes player p, Recipe this returns boolean
*       - function IsPlayerAuthorizedRecipeById takes integer p, Recipe this returns boolean
*           - Checks whether a player is authorized or not.
*
***************************************************/
library RecipeEngine requires Table, RegisterPlayerUnitEvent, GetItemOwner

    // Configuration
    globals
        // The effect that appears on Recipe Compiling
        private constant string EFFECT_PATH = "Abilities\\Spells\\Items\\AIem\\AIemTarget.mdl"
        // The attachment point of the effect
        private constant string ATTACHMENT = "origin"
        // The boolean that allows you to create Player specific recipes
        private constant boolean PLAYER_SPECIFIC_RECIPES = false
        // Disassembling Constants
        constant integer DISASSEMBLE_SUCCESS = 0
        constant integer DISASSEMBLE_ERROR_INVALID = 1
        constant integer DISASSEMBLE_ERROR_INVENTORY = 2
    endglobals
    // End of Configuration
    
    private module Init
        private static method onInit takes nothing returns nothing
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.main)
            set queue = TableArray[0x2000]
            set itemStack = Table.create()
            set ds = Table.create()
            set tb = Table.create()
        endmethod
    endmodule
    
    struct Recipe extends array
    
        private static TableArray queue
        private static Table itemStack
        
        private static Table ds = 0
        private static Table tb = 0
        private static integer stack = 1
        
        private static integer array itemId
        private static integer array charge
        
        private static integer array output
        private static integer array outputCharge
        private static integer array count
        
        private static boolean array recipeOff
        
        private static constant integer NULL = -1
        static boolean enabled = true
        
        method enable takes nothing returns nothing
            set recipeOff[this] = false
        endmethod
        
        method disable takes nothing returns nothing
            set recipeOff[this] = true
        endmethod
        
        method isEnabled takes nothing returns boolean
            return not recipeOff[this]
        endmethod
        
        static if PLAYER_SPECIFIC_RECIPES then
            static boolean array au
            method authorize takes integer id returns nothing
                set au[this * 12 + id] = true
            endmethod
            method unauthorize takes integer id returns nothing
                set au[this * 12 + id] = false
            endmethod
            method isAuthorized takes integer id returns nothing
                return au[this * 12 + id]
            endmethod
        endif
        
        static method disassemble takes item it returns integer
            local thistype this = ds[GetHandleId(it)]
            local integer i = 0
            local real x = GetWidgetX(it)
            local real y = GetWidgetY(it)
            local unit u = GetItemOwner(it)
            local integer size = UnitInventorySize(u)
            local integer empty = 1
            local boolean validOwner = (u != null)
            local item iq
            if this != 0 then
                set enabled = false
                if validOwner then
                    loop
                        exitwhen i == size
                        if UnitItemInSlot(u, i) == null then
                            set empty = empty + 1
                        endif
                        set i = i + 1
                    endloop
                    if count[this] > empty then
                        set u = null
                        return DISASSEMBLE_ERROR_INVENTORY
                    endif
                    set i = 0
                endif
                call RemoveItem(it)
                loop
                    if itemId[6 * this + i] != NULL then
                        set iq = CreateItem(itemId[6 * this + i], x, y)
                        if validOwner then
                            call UnitAddItem(u, iq)
                        endif
                        if charge[6 * this + i] > 0 then
                            call SetItemCharges(iq, charge[6 * this + i])
                        endif
                    endif
                    exitwhen i == 5
                    set i = i + 1
                endloop
                set enabled = true
                set iq = null
                set u = null
                return DISASSEMBLE_SUCCESS
            endif
            set u = null
            return DISASSEMBLE_ERROR_INVALID
        endmethod
        
        private static method systemConditions takes integer this returns boolean
            static if PLAYER_SPECIFIC_RECIPES then
                return enabled and au[this * 12 + GetPlayerId(GetTriggerPlayer())] and not recipeOff[this]
            else
                return enabled and not recipeOff[this]
            endif
        endmethod
        
        private static method main takes nothing returns nothing
            local integer id = GetItemTypeId(GetManipulatedItem())
            local unit u = GetManipulatingUnit()
            local thistype this = queue[id][0]
            local integer in = 0
            local integer h
            local integer i = 0
            local integer int = 0
            local integer index = 0
            local integer slotIndex = 0
            local integer itemInSlot
            local integer charges
            local integer arrayIndex = this * 6
            local integer getIndex = 0
            local item k = null
            if systemConditions(this) then
                loop
                    exitwhen this == 0
                    loop
                        exitwhen slotIndex == 6
                        set itemInSlot = GetItemTypeId(UnitItemInSlot(u, slotIndex))
                        set charges = GetItemCharges(UnitItemInSlot(u, slotIndex))
                        loop
                            if itemInSlot == itemId[arrayIndex + index] and tb[index] == 0 and charges >= charge[arrayIndex + index] then
                                set tb[index] = 1
                                set int = int + 1
                                exitwhen true
                            endif
                            exitwhen index == 5
                            set index = index + 1
                        endloop
                        set index = 0
                        if int == count[this] then
                            call tb.flush()
                            loop
                                exitwhen i == count[this]
                                loop
                                    exitwhen getIndex > 5
                                    if GetItemTypeId(UnitItemInSlot(u, getIndex)) == itemId[6 * this + i] then
                                        set k = UnitItemInSlot(u, getIndex)
                                    endif
                                    set getIndex = getIndex + 1
                                endloop
                                set h = GetItemCharges(k)
                                if h > charge[6 * this + i] then
                                    call SetItemCharges(k, h - charge[6 * this + i])
                                else
                                    call RemoveItem(k)
                                endif
                                set i = i + 1
                                set getIndex = 0
                            endloop
                            call DestroyEffect(AddSpecialEffectTarget(EFFECT_PATH, u, ATTACHMENT))
                            set k = CreateItem(output[this], 0, 0)
                            if outputCharge[this] > 0 then
                                call SetItemCharges(k, outputCharge[this])
                            endif
                            set ds[GetHandleId(k)] = this
                            call UnitAddItem(u, k)
                            set k = null
                            set u = null
                            return
                        endif
                        set slotIndex = slotIndex + 1
                    endloop
                    call tb.flush()
                    set in = in + 1
                    set this = queue[id][in]
                endloop
            endif
        endmethod
        
        method requires takes integer id, integer charges returns thistype
            set itemId[6 * this + count[this]] = id
            set charge[6 * this + count[this]] = charges
            if itemStack[id] == null then
                set itemStack[id] = 0
            endif
            set queue[id][itemStack[id]] = this
            set itemStack[id] = itemStack[id] + 1
            set count[this] = count[this] + 1
            return this
        endmethod
        
        static method create takes integer result, integer charges returns thistype
            local thistype this = stack
            local integer i = 0
            set stack = stack + 1
            loop
                set itemId[6 * this + i] = NULL
                exitwhen i == 5
                set i = i + 1
            endloop
            set output[this] = result
            set outputCharge[this] = charges
            return this
        endmethod
        
        implement Init
    endstruct
    
    function CreateRecipe takes integer id, integer charge returns Recipe
        return Recipe.create(id, charge)
    endfunction
    
    function AddRecipeRequirement takes Recipe this, integer id, integer charge returns nothing
        call this.requires(id, charge)
    endfunction
    
    function EnableRecipe takes Recipe this returns nothing
        call this.enable()
    endfunction
    
    function DisableRecipe takes Recipe this returns nothing
        call this.disable()
    endfunction
    
    function IsRecipeEnabled takes Recipe this returns boolean
        return this.isEnabled()
    endfunction
    
    function EnableRecipeSystem takes nothing returns nothing
        set Recipe.enabled = true
    endfunction
    
    function DisableRecipeSystem takes nothing returns nothing
        set Recipe.enabled = false
    endfunction
    
    function IsRecipeSystemEnabled takes nothing returns boolean
        return Recipe.enabled
    endfunction
    
    function DisassembleItem takes item it returns integer
        return Recipe.disassemble(it)
    endfunction
    
    static if PLAYER_SPECIFIC_RECIPES then
        function AuthorizePlayerRecipeById takes integer id, Recipe this returns nothing
            call this.authorize(id)
        endfunction
        
        function UnauthorizePlayerRecipeById takes integer id, Recipe this returns nothing
            call this.unauthorize(id)
        endfunction
        
        function IsPlayerAuthorizedRecipeById takes integer id, Recipe this returns boolean
            return this.isAuthorized(id)
        endfunction
        
        function AuthorizePlayerRecipe takes player p, Recipe this returns nothing
            call this.authorize(GetPlayerId(p))
        endfunction
        
        function UnauthorizePlayerRecipe takes player p, Recipe this returns nothing
            call this.unauthorize(GetPlayerId(p))
        endfunction
        
        function IsPlayerAuthorizedRecipe takes player p, Recipe this returns boolean
            return this.isAuthorized(GetPlayerId(p))
        endfunction
    endif
    
endlibrary

You have the option to create Player Specific recipes.
Each player is authorized by default.

This system also comes with a Disassemble function.
Unlike Diablo-dk's system, I'm not using ItemUserData.

And to make things look better for the system, it can support item charges too :D

Plus, the API is beautiful.
Here's a Demo Code to show you:

JASS:
struct Demo extends array

    static Recipe Orb
    static Recipe SpiderRing
    static Recipe Ring
    static Recipe Wand
    
    private static method onInit takes nothing returns nothing
        // Beautiful API!
        set Orb         = CreateRecipe('rde3', 0)
        set SpiderRing  = CreateRecipe('sprn', 0)
        set Ring        = CreateRecipe('sora', 0)
        set Wand        = CreateRecipe('wlsd', 5)
        
        call Orb.requires('rde1', 0).requires('rde1', 0)
        
        call SpiderRing.requires('ram1', 0).requires('ram1', 0)
        call SpiderRing.requires('ram1', 0).requires('ram2', 0)
        
        call Ring.requires('sor5', 0).requires('sor3', 0).requires('sor2', 0)
        
        call Wand.requires('will', 3).requires('I000', 1)
    endmethod
endstruct

Please give credits to me if you use this.
Also, give credits to Diablo-dk since this system is based on his old algorithms.

This system doesn't support items with more than 6 requirements.
There's a WiP in the testmap for the 6+ item requirement version that I might get back to.

Happy Mapping!

Keywords:
DotA, recipes, recipe, item, system, combine, items, engine, simple, easy, advanced, recipe system, awesome, Magtheridon96, Bribe, troll, Vexorian, Ha
Contents

Just another Warcraft III map (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 30 Jan 2012 Bribe: Awaiting further updates before approval.

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

30 Jan 2012
Bribe: Awaiting further updates before approval.
 
Level 14
Joined
Nov 18, 2007
Messages
815
  • Encapsulation
  • Array members over table instances (who needs >500 recipes?). Also, please mention somewhere that its not compatible with Vexs Table (which it could be).
  • Why bother with a constant boolean to enable or disable an API, just enable it always, the optimizer will take care of superfluous functions
 
This kind of fails...

move it to a tree or tables of tables >.>


Look at the tree structure Encoder uses. That'd be perfect for this =)


You could also expand it into tables of tables so that items point directly to each other, eliminating the searches all together.

Why does it fail? Just because it's NOT using some QueueQueue?
I like your suggestion about the Table of Tables though
I'll try to decide what data structure I'll use.

Array members over table instances (who needs >500 recipes?). Also, please mention somewhere that its not compatible with Vexs Table (which it could be).

Non-static Array members are trash. A table is faster.
I'll mention it isn't compatible with Vex's Table though. :)
 
I'm rewriting this system so that it's incredibly fast ;D
I'm using a QueueQueue :D
This will be the fastest recipe system ever :)

@Death-Blade:
:L Doing this in GUI is suicide :L

@Bribe:
I'll do that :3

edit

Here's a WiP for one of my static methods:

JASS:
        static method create takes integer i1, integer i2, integer i3, integer i4, integer i5, integer i6, integer i7 returns thistype
            local thistype this = thistype.allocate()
            local integer array i
            local boolean array e
            local integer j=1
            local integer k=1
            local integer m=1
            set i[1]=i1
            set i[2]=i2
            set i[3]=i3
            set i[4]=i4
            set i[5]=i5
            set i[6]=i6
            loop
                exitwhen j>6
                if 0!=i[j] then
                    set e[j]=true
                    set .z[m]=i[j]
                    set m=m+1
                endif
                set j=j+1
            endloop
            set j=1
            set m=1
            loop
                exitwhen 6<k
                if e[k] then
                    loop
                        exitwhen 6<j
                        if e[j] and j!=k then
                            set q[i[k]][w[i[k]]][m]=i[j]
                            set q[i[k]][w[i[k]]+OFFSET]=this
                            set m=m+1
                        endif
                        set j=j+1
                    endloop
                    set .n=.n+1
                    set w[i[k]]=w[i[k]]+1
                endif
                set j=1
                set m=1
                set k=k+1
            endloop
            set .o=i7
            return this
        endmethod

Are my algorithms ugly enough? Want MOAR? :D
 
Last edited:
Yeah, ım updatıng thıs thıng :p
Im not goıng to use any complıcated data structures or anythıng..
Im goıng to use lınks so that recıpe ınstances are lınked to the ıtems needed to create them. That way ınstead of loopıng through a lınked lıst, you could just make a very small search usıng hashtable lookups to loop only through the recıpes that the aquıred ıtem ıs lınked to. Thıs wıll also make the dısassemble feature more .. .. ... well, you get the poınt :p
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
JASS:
//          function AuthorizePlayerRecipe takes player p, recipe this returns nothing
//              - p    : The player you want to authorize
//              - this : The recipe
//
//          function UnauthorizePlayerRecipe takes player p, recipe this returns nothing
//              - p    : The player you want to unauthorize
//              - this : The recipe

These functions should be be merged as one function and take a boolean flag instead. So basic function AuthorizePlayerRecipe takes player p, recipe this, boolean flag returns boolean (dunno if it's necessary to return the flagged boolean but someone might need it).
 
These functions should be be merged as one function and take a boolean flag instead. So basic function AuthorizePlayerRecipe takes player p, recipe this, boolean flag returns boolean (dunno if it's necessary to return the flagged boolean but someone might need it).

Since the functions are going to be inlined anyways, I dont think it would matter, but I'll do it anyways ^^

For comparisons
d[j]==0

Always put 0 first
0==d[j]

it's faster that way

Thanks for catching that, I was trying to make all my comparisons that way :p

Does this support stacked items?

Most likely.
If it causes any problems, you could disable the system, do whatever actions you want, then enable the system :)
 
I was thinking of doing one, but you already made it... haha... ^_^

this is certainly useful...

And adding support for stacked items (charges), will need just a bit more parameters to the set-up part and a bit more lines to the core of the code... Its a good thing if you could add that so that we can make recipes require an item to have a specific number of charges...

but it might be better to just add it as an extension/extra version of this system so that users who will not need it can just use the base system...
 
And adding support for stacked items (charges), will need just a bit more parameters to the set-up part and a bit more lines to the core of the code... Its a good thing if you could add that so that we can make recipes require an item to have a specific number of charges...

but it might be better to just add it as an extension/extra version of this system so that users who will not need it can just use the base system...

I'll add that eventually :)

Right now, my main concern is how I'm going to get my "Load List of Instances from Item Id" version to work :p
That system can be upto 40 times faster than this xD
 
The 7-requirement thing comes with an extra feature:
- Picking up items with a full inventory.

Unfortunately, these kinds of systems would need a lot of overhead and they'd be extremely buggy. That's why it's best to make them map-specific. (using powerups)

I will however aid users with this by providing functions that check if a unit has all the items for a recipe given one item exception. Those functions would compile the recipes automatically. :D

edit

Drat, I repeated the operation 6*this even after caching it into a local.
I'll update this tomorrow.
 
Last edited:
Level 10
Joined
May 27, 2009
Messages
494
i guess you may need additional library for full inventory recipe creation or powerups

esp checking if the unit is near the item (if not carried by any unit) and if it is, check if recipe exist, then perform recipe creation 'cause you know mate, when an item is dropped, a specific item is different from a powerup item model =))
like itemposition library or something like those functions

i guess i'll still use artificial's.. even though the only flaw is it doesn't include those checking of positions... and the one included in its thread is kinda lame...
 
Level 16
Joined
Aug 7, 2009
Messages
1,407
Mag, here's one thing you should implement, that would make the system way cooler:
".requires" should take an extra parameter: "costPerItem". So if the player doesn't have the required items, but has enough gold for the whole recipe, it would automatically create the item for the player. I found seriously annoying in DotA that you have to keep picking up and dropping items (of course my does it; but it has the whole recipe engine reinvented :p)
 
Shouldn't I make create take an extra parameter for the recipe cost? :p

esp checking if the unit is near the item

This is one of the reasons Hard-coded recipe systems are better (You could get pushed or knocked-back towards an item you targeted and pick it up when you weren't supposed to :p), but I'm trying to improve this so that it can easily be used for picking up items with a full inventory (You make a powerup and call hasItemsEx(recipe, item) where item is something you load from a Table based on the Id of the powerup :p
hasItemsEx is already finished, but the problem is that it's buggy :eek:

Wow ,u guys code youre butts off
nice system, im gonna give some+rep

im only 13 but i cannot code like that lol
i understand only little bits of it!
:D

I'm glad you like it :D
I was procrastinating a lot during the development of this :p

edit

Ok, I came up with a better idea for the Recipe costs.
Instead of adding that feature specifically, I'm going to allow you to attach an integer to a certain recipe.
Some people would use it for recipe costs, others would use it for ... no idea :p
 
Level 16
Joined
Aug 7, 2009
Messages
1,407
Shouldn't I make create take an extra parameter for the recipe cost? :p

Yea, forgot to mention that; both create and requires should take an extra parameter. Create would take the recipe's cost (not the total) and requires the item's cost. The way I'm doing it:

My map has custom item structs as well and they contain its price, therefore I don't have to bother with costs.
I'm initializing everything at map init. A one-shot timer fires with "0." callback time and fires a method that loops through all the recipes, setting a private member, called "totalCost" according the values (recipe cost + item costs).
I'm using a local and initialize it as the recipe"s total gold cost (speaking of the "onUse" method). Then I loop through its reagents and check if the hero has them. After the correct cost is determined I simply do a check; if the player has enough gold, all the items are removed, however if not, an error message pops up.

I think this method is efficient enough and user-friendly as well.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Hm... you should add some events like onDismantle and onBuild or w/e : ). Dismantle should give a linked list of the dismantled items and onBuild should give a linked list of the built items.


Furthermore, items should be able to be built into items ; O. For example, 3 bat wings and 2 milk drops of evanescence should be able to make a dew drop wing and left over waste, like a faded milk drop of evanescence.


Essentially, create the recipe first. Add items to be built to that recipe (like a linked list of items or w/e, each with charges). Then add items that are required to build it to that recipe. Furthermore, rather than auto making it, the user should be able to decide whether to auto make or have the player pick from a list or w/e. For example, 10 spider webs might make a sticky net and 5 spider webs might make a small sticky net.



1 more version mag : P. 3.0.0.0 will be brilliant tho >: ).
 
Yeah, 3.0.0.0 is also going to include hasItemsEx too :p
That'll put this system in the lead ^.^

I guess I can add a function like:

JASS:
method outputs takes integer itemId, integer charges returns thistype
    // add item and register charges
    return this
endmethod

And making the user chose whether a recipe autocompiles or not is a good idea :D
I'll also split this into functions so that it can be more readable (The cost is 2 function calls, but hey, relative to the amount of function calls I already have in there, 2 is nothing)

edit
I'm also going to use LinkedLists for item->recipe pointers because 2 hashtable-lookups is too much overhead relative to what a simple array read costs. (I'll just have to write an ItemIdIndexer resource in this case)

edit
And I'm going to allow custom special effects for each recipe (setting them will be optional since I'm going to use a lot of method operators in 3.0.0.0)
 
Level 3
Joined
Oct 7, 2011
Messages
37
Is this system actually in working condition?
I just ran the test map and there are tons of error debug statements when you load the map and when you pick up items
IndexError: Tried to get key [1936683571] from outside TableArray bounds: 8192

I tried picking up 2 rings of protection +2 to create the ring +4 and it didn't work as well
the demo struct is working because it outputs the init debug statement not sure what else could be wrong unless its a non working edition of the library?

Just checking cause it seems pretty awesome and I wanted to use the system
 
Last edited:
Level 3
Joined
Oct 7, 2011
Messages
37
That would be fantastic, the syntax for creating recipes and implementation of the system is awesome light years beyond what i was working with before
Hope you get the time to update it
and thanks for replying :)
 
Top