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

ItemHierarchy v1.00

This bundle is marked as awaiting update. A staff member has requested changes to it before it can be approved.
full

JASS:
library ItemHierarchy /*
 
    ItemHierarchy v1.00
 
    by Spellbound
 
    */ requires /*
    */ Table    /*
 
    DESCRIPTION
 
        ItemHierarchy creates a list of items that automatically upgrade when another item in the
        list, aka the hierarchy, is picked up. If you've played the Night Elf campaign in Frozen
        Throne, Maieve can at one point collect Shadow Orb Fragments to increase the power of her
        Shadow Orb. This is exactly what this system does.
 
 
    API
     
        ItemHierarchy.create( takes integer itemId, boolean automaticUpgrade )
         
            This method will create the baseline of the hierarchy. The item HAS to be a power-up,
            similar to how the Shadow Orb has Shadow Orb Fragments. The baseline is how to reference
            the entire hierarchy later on by using it's item id in. See Soul Gem onKill for more info.
         
            the boolean argument should be true by default unless you wish to handle the upgrade
            manually and just need the hierarchy as a referrence.
         
        ItemHierarchy.addItem( takes integer baselineId, integer itemId, integer i )
     
            Once your hierarchy has been created, add every subsequent level to it with the above
            method. baselineId will identify the hierarchy itself, itemId is the item you wish to
            add, and the integer i argument says where in the hierarchy the item goes, aka what is
            its level.
         
        ItemHierarchy.setItemLevel( takes unit u, item itm, integer i ) returns item
     
            In the event that you need to manually modify the level of an item, use the above
            method. unit u is the carrier of the item, itm is the item itself, and i is the level
            you wish the item to have. This method returns the item that was created to replace
            the old one.
         
        GetItemHierarchyLevel( takes integer itemId ) returns integer
     
            If an item is inside of a hierarchy, this will return the level of the item inside
            that hierarchy. It will NOT return the Object Editor item level.
         
        GetHierarchyTable( takes integer itemId ) returns integer
         
            This returns the instance of the Table in which the items of the hierarchy is stored.
            See Soul Gem onKill for more information.
     
        GetHierarchyInstance( takes integer itemId ) returns integer
         
            This returns the instance of the hierarchy itself, allowing you to reference values like
            this.maxItemLevels, etc. See Soul Gem onKill for more info.
         
    */
 
    globals
        private trigger itemChangeTrig = CreateTrigger()
        private Table instanceStorage
    endglobals
 
    function GetItemHierarchyLevel takes integer itemId returns integer
        local ItemHierarchy this = instanceStorage.integer[itemId]
        return this.itemLevel
    endfunction
 
    function GetHierarchyTable takes integer itemId returns integer
        local ItemHierarchy this = instanceStorage.integer[itemId]
        return this.tableInstance
    endfunction
 
    function GetHierarchyInstance takes integer itemId returns integer
        return instanceStorage.integer[itemId]
    endfunction
 
    struct IH
        static Table LIST
    endstruct
 
    struct ItemHierarchy
     
        boolean isBaselineItem
        boolean isUpgradeAutomatic
        integer instance
        integer tableInstance
        integer baselineInstance
        integer itemLevel
        integer maxItemLevels
     
        static method setItemLevel takes unit u, item itm, integer i returns item
         
            local integer itemId = GetItemTypeId(itm)
            local thistype this = instanceStorage.integer[itemId]
            local integer slot = 0
         
            if itm != null then
         
                call DisableTrigger(itemChangeTrig)
             
                loop
                    exitwhen itm == UnitItemInSlot(u, slot) or slot >= bj_MAX_INVENTORY
                    set slot = slot + 1
                endloop
             
                call RemoveItem(itm)
                set IH.LIST = this.tableInstance
                call UnitAddItemToSlotById(u, IH.LIST.integer[i], slot)
             
                call EnableTrigger(itemChangeTrig)
         
            endif
         
            return UnitItemInSlot(u, slot)
        endmethod
     
        private static method upgradeItemAutomatic takes nothing returns boolean
     
            local item itm = GetManipulatedItem()
            local unit u = GetTriggerUnit()
            local integer itemId = GetItemTypeId(itm)
            local thistype newItem = instanceStorage.integer[itemId]
         
            local integer i
            local integer slot
            local item indexItem
            local thistype baseline = newItem.baselineInstance
         
            local integer levelCount = 0
            local integer levelOverflow = 0
         
            local real x
            local real y
         
            if baseline.isUpgradeAutomatic then
         
                if newItem != 0 then
             
                    set IH.LIST = baseline.tableInstance
                    set i = 0
                    loop
                        set i = i + 1
                        exitwhen i >= baseline.maxItemLevels //set the loop to ignore max-levelled items
                        set slot = 0 //inventory slots start on zero in JASS
                        loop
                            set indexItem = UnitItemInSlot(u, slot)
                            if (indexItem != null) and (GetItemTypeId(indexItem) == IH.LIST.integer[i]) then
                                set levelCount = levelCount + i
                                call RemoveItem(indexItem)
                            endif
                            set slot = slot + 1
                            exitwhen slot >= bj_MAX_INVENTORY
                        endloop
                    endloop
                    set indexItem = null
                 
                    call DisableTrigger(itemChangeTrig)
                 
                    if newItem.isBaselineItem then
                        set levelCount = levelCount + 1
                    endif
                 
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                 
                    if levelCount > baseline.maxItemLevels then
                        set levelOverflow = levelCount - baseline.maxItemLevels
                        call UnitAddItem(u, CreateItem(IH.LIST.integer[baseline.maxItemLevels], x, y))
                        loop
                            if levelOverflow > baseline.maxItemLevels then
                                call UnitAddItem(u, CreateItem(IH.LIST.integer[baseline.maxItemLevels], x, y))
                                set levelOverflow = levelOverflow - baseline.maxItemLevels
                            else
                                call UnitAddItem(u, CreateItem(IH.LIST.integer[levelOverflow], x, y))
                                set levelOverflow = 0
                            endif
                            exitwhen levelOverflow == 0
                        endloop
                    else
                        call UnitAddItem(u, CreateItem(IH.LIST.integer[levelCount], x, y))
                    endif
                 
                    call EnableTrigger(itemChangeTrig)
                 
                endif
             
            endif
         
            return true
        endmethod
     
        static method addItem takes integer baselineId, integer itemId, integer i returns nothing
            local thistype baseline = instanceStorage.integer[baselineId]
            local thistype newItem = instanceStorage.integer[itemId]
            if baseline != 0 and newItem == 0 then
                set IH.LIST = baseline.tableInstance
                set IH.LIST.integer[i] = itemId
                set newItem = allocate()
                set newItem.itemLevel = i
                set newItem.tableInstance = IH.LIST
                set newItem.isBaselineItem = false
                set newItem.baselineInstance = baseline
                set newItem.isUpgradeAutomatic = false
                set instanceStorage.integer[itemId] = newItem
                set baseline.maxItemLevels = baseline.maxItemLevels + 1
            endif
        endmethod
     
        static method create takes integer itemId, boolean automaticUpgrade returns thistype
            local thistype baseline
            if instanceStorage.integer[itemId] == 0 then
                set IH.LIST = Table.create()
                set IH.LIST.integer[0] = itemId
                set baseline = allocate()
                set baseline.tableInstance = IH.LIST
                set baseline.maxItemLevels = 0
                set baseline.isBaselineItem = true
                set baseline.itemLevel = 1
                set baseline.baselineInstance = baseline
                set baseline.isUpgradeAutomatic = automaticUpgrade
                set instanceStorage.integer[itemId] = baseline
            endif
            return baseline
        endmethod
     
        private static method onInit takes nothing returns nothing
            set instanceStorage = Table.create()
            call TriggerRegisterAnyUnitEventBJ(itemChangeTrig, EVENT_PLAYER_UNIT_PICKUP_ITEM)
            call TriggerAddCondition(itemChangeTrig, function thistype.upgradeItemAutomatic)
        endmethod
     
    endstruct
 
endlibrary

Demo:
JASS:
scope SetupItemLevels initializer init

    private function Actions takes nothing returns boolean
     
        local integer baseline
     
        //Shadow Orb
        set baseline = 'sorf'
        call ItemHierarchy.create(baseline, true)
        call ItemHierarchy.addItem(baseline, 'sor1', 1)
        call ItemHierarchy.addItem(baseline, 'sor2', 2)
        call ItemHierarchy.addItem(baseline, 'sor3', 3)
        call ItemHierarchy.addItem(baseline, 'sor4', 4)
        call ItemHierarchy.addItem(baseline, 'sor5', 5)
        call ItemHierarchy.addItem(baseline, 'sor6', 6)
        call ItemHierarchy.addItem(baseline, 'sor7', 7)
        call ItemHierarchy.addItem(baseline, 'sor8', 8)
        call ItemHierarchy.addItem(baseline, 'sor9', 9)
        call ItemHierarchy.addItem(baseline, 'sora', 10)
     
        //Soul Gem
        set baseline = 'sg00'
        call ItemHierarchy.create(baseline, false)
        call ItemHierarchy.addItem(baseline, 'sg01', 1)
        call ItemHierarchy.addItem(baseline, 'sg02', 2)
        call ItemHierarchy.addItem(baseline, 'sg03', 3)
        call ItemHierarchy.addItem(baseline, 'sg04', 4)
        call ItemHierarchy.addItem(baseline, 'sg05', 5)
     
        return false
    endfunction
 
    private function init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        call TriggerRegisterTimerEvent(trig, 0., false)
        call TriggerAddCondition(trig, function Actions)
        set trig = null
    endfunction
 
endscope
Contents

ItemHierarchy (Map)

Reviews
MyPad
Nitpicks: How would this be different from a recipe system? Notes: To be tested. Status: Awaiting Update
Level 9
Joined
Dec 12, 2007
Messages
489
I haven't tried the testmap, but based on your description on Maiev's shadow orb,
it seems the system is similar to usual recipe system with retrievable "level/grade" attached to the instance.

few questions:
1. I don't think user would want to set the automaticupgrade to false, even if the user want to,
how can the user retrieve the event of picking hierarchy item?

as written in the description:
the boolean argument should be true by default unless you wish to handle the upgrade manually and just need the hierarchy as a reference.


2. how about adding feature something like:
JASS:
call ItemHierarchy.addnextItem(baseline, 'sor2')
so it automatically add the item to the next level?

3. what will happen if I do something like this?
JASS:
call ItemHierarchy.addItem(baseline, 'sor2', 2)
call ItemHierarchy.addItem(baseline, 'sor3', 2)
will it just update the value of level 2 or what?

4. are user allowed to use same item rawcode on single instance of hierarchy or perhaps allowed on different instance or do user have to use unique item rawcode on both case?

for an example,
JASS:
call ItemHierarchy.addItem(baseline, 'sor3', 3)
call ItemHierarchy.addItem(baseline, 'sor3', 4)
is this ok or should be avoided?

5. so to use .setitemlevel on an item, that item must be in an unit's inventory? do user have to workaround with dummy if they want to .setitemlevel on an item on the ground?

6. any limitations or something the users have to be aware of? like level limit? or conflict with other resource? inability to use certain feature of other system maybe?


p.s. I'm sorry if what I ask above is out of the context because I misunderstand your resource.
 
1. I don't think user would want to set the automaticupgrade to false, even if the user want to,
how can the user retrieve the event of picking hierarchy item?
There's an example of automaticupgrade set to false in the test map. Then again, you could always just not have any baseline items in your map, but I put it there just in case.

2. how about adding feature something like:
JASS:
call ItemHierarchy.addnextItem(baseline, 'sor2')
so it automatically add the item to the next level?
The system was initially going to be like this but I figured I'd make the system require a number to enable the user to visualise the list he/she is making better (aka, you can tell from a glance what number what item id is bound to)

3. what will happen if I do something like this?
JASS:
call ItemHierarchy.addItem(baseline, 'sor2', 2)
call ItemHierarchy.addItem(baseline, 'sor3', 2)
will it just update the value of level 2 or what?
The second line will be ignored. Your first entry is permanent.

4. are user allowed to use same item rawcode on single instance of hierarchy or perhaps allowed on different instance or do user have to use unique item rawcode on both case?

for an example,
JASS:
call ItemHierarchy.addItem(baseline, 'sor3', 3)
call ItemHierarchy.addItem(baseline, 'sor3', 4)
is this ok or should be avoided?
Level 3 and 4 of the item hierarchy will return the same item in this instance.

5. so to use .setitemlevel on an item, that item must be in an unit's inventory? do user have to workaround with dummy if they want to .setitemlevel on an item on the ground?
Yep, .setItemLevel only work if it exists in a unit's inventory. I figured if users want to set the level of an item on the ground they can just remove and replace. That's given me something to thing about, however.

6. any limitations or something the users have to be aware of? like level limit? or conflict with other resource? inability to use certain feature of other system maybe?
None that I'm aware of.
 
I guess it's a more straightforward library for upgrading an item into stronger variants of itself? It's also a reference list so if you have a Shadow Orb +3 and pick a +4, your Shadow Orb automatically becomes +7. I've never used a recipe system before (as a modder) so I guess this is just simpler to use?
 
Hmm... Fair point. So, going by logical operation, this could potentially be an add-on to a recipe system, but implemented such that it stands on its' own, yet...

Going by recipe systems, this would be quite costly to implement, having a worst case of O(n^2). However, this has a behavior-specified set of operations that reduce the worst case to O(c), where c is a constant.

So far, I don't see any flops, though I have yet to test the map. The status shall be determined in near future.
 
Level 13
Joined
Nov 7, 2014
Messages
571
It seems to me that this could be implemented in a "simpler" manner and have a simpler user level API.

I took the liberty of renaming ItemHierarchy to Item_Chain because hierarchies tend to have a "tree-like" structure and chains are really just lists.

JASS:
library libassert

function assert takes string s, boolean b returns nothing
    if not b then
        call BJDebugMsg("|cffFF0000assert failed: |r" + s)
        call I2S(1/0)
    endif
endfunction

function writeln takes string s returns nothing
    call BJDebugMsg(s)
endfunction

endlibrary


library libitemchain initializer init uses libassert

globals
    private hashtable ht = InitHashtable()
endglobals

struct Item_Chain extends array
    integer frag_item_id
    integer max_val
    boolean disabled
endstruct

// returns 0 if item_id is not part of an item chain
function item_get_chain takes integer item_id returns Item_Chain
    return Item_Chain(LoadInteger(ht, -1, item_id))
endfunction
private function item_set_chain takes integer item_id, Item_Chain chain returns nothing
    debug call assert("[item_set_chain] item(" + I2S(item_id) + ") is already part of Item_Chain(" + I2S(integer(item_get_chain(item_id))) + ")", /*
        */ 0 == item_get_chain(item_id))
    call SaveInteger(ht, -1, item_id, integer(chain))
endfunction

function item_get_chain_val takes integer item_id returns integer
    debug call assert("[item_set_chain] item(" + I2S(item_id) + ") is not part of any Item_Chain()", /*
        */ 0 != item_get_chain(item_id))
    return LoadInteger(ht, -2, item_id)
endfunction
private function item_set_chain_val takes integer item_id, integer val returns nothing
    call SaveInteger(ht, -2, item_id, val)
endfunction

function chain_val_get_item takes Item_Chain chain, integer chain_val returns integer
    debug call assert("[chain_val_get_item] invalid chain_val(" + I2S(chain_val) + " for Item_Chain(" + I2S(integer(chain)) + ")", /*
    */ 0 != LoadInteger(ht, integer(chain), chain_val))
    return LoadInteger(ht, integer(chain), chain_val)
endfunction
private function chain_val_set_item takes Item_Chain chain, integer chain_val, integer item_id returns nothing
    call SaveInteger(ht, integer(chain), chain_val, item_id)
endfunction

globals
    private integer next_chain_id = 0
    private Item_Chain curr_chain = 0
endglobals

// frag_item_id can be 0
function begin_item_chain takes integer frag_item_id returns nothing
    set next_chain_id = next_chain_id + 1
    set curr_chain = Item_Chain(next_chain_id)
    set curr_chain.max_val = 0
    set curr_chain.disabled = false

    set curr_chain.frag_item_id = frag_item_id
    call item_set_chain(frag_item_id, curr_chain)
endfunction

function chain_is_disabled takes nothing returns nothing
    set curr_chain.disabled = true
endfunction

function chain_add_item takes integer item_id returns nothing
    set curr_chain.max_val = curr_chain.max_val + 1
    call item_set_chain(item_id, curr_chain)
    call item_set_chain_val(item_id, curr_chain.max_val)
    call chain_val_set_item(curr_chain, curr_chain.max_val, item_id)
endfunction

// for symmetry with begin_item_chain
function end_item_chain takes nothing returns nothing
    // do nothing
endfunction

private function unit_get_item_slot takes unit u, item it returns integer
    local integer a = 0
    local integer aa = UnitInventorySize(u)
    loop
        exitwhen a == aa
        if it == UnitItemInSlot(u, a) then
            return a
        endif
        set a = a + 1
    endloop
    return -1
endfunction

// returns the item's slot
private function find_another_item_from_the_same_chain_not_maxed takes unit u, integer b_item_slot, Item_Chain chain returns integer
    local integer a = 0
    local integer aa = UnitInventorySize(u)
    local item it
    local integer item_id
    loop
        exitwhen a == aa
        if a != b_item_slot then
            set it = UnitItemInSlot(u, a)
            if it != null then
                set item_id = GetItemTypeId(it)
                if chain == item_get_chain(item_id) then
                    if chain.max_val !=  item_get_chain_val(item_id) then
                        return a
                    endif
                endif
            endif
        endif
        set a = a + 1
    endloop
    return -1
endfunction

private function first_free_slot takes unit u returns integer
    local integer a = 0
    local integer aa = UnitInventorySize(u)
    loop
        exitwhen a == aa
        if null == UnitItemInSlot(u, a) then
            return a
        endif
        set a = a + 1
    endloop
    call assert("[first_free_slot] unreachable", false)
    return -1
endfunction

private function on_item_pickup takes unit u, item picked_item returns nothing
    local Item_Chain chain
    local integer chain_first_item_id
    local integer b
    local integer b_item_id
    local integer b_item_slot
    local integer a
    local integer a_item_id
    local integer a_item_slot
    local integer res
    local integer res_item_id
    local integer diff
    local integer diff_item_id

    set b_item_id = GetItemTypeId(picked_item)
    set chain = item_get_chain(b_item_id)
    if chain == 0 or chain.disabled then
        return
    endif

    set b_item_slot = unit_get_item_slot(u, picked_item)
    if b_item_slot == -1 then
        // a tome-like item
        set b_item_slot = first_free_slot(u)
    endif

    if b_item_id == chain.frag_item_id then
        call RemoveItem(UnitItemInSlot(u, b_item_slot))
        set chain_first_item_id = chain_val_get_item(chain, 1)
        call UnitAddItemToSlotById(u, chain_first_item_id, b_item_slot) // invoke on_item_pickup recursively
        return
    endif

    set a_item_slot = find_another_item_from_the_same_chain_not_maxed(u, b_item_slot, chain)
    if a_item_slot == -1 then
        return
    endif
    set a_item_id = GetItemTypeId(UnitItemInSlot(u, a_item_slot))

    set a = item_get_chain_val(a_item_id)
    set b = item_get_chain_val(b_item_id)
    set res = a + b
    set diff = 0
    if res > chain.max_val then
        set diff = res - chain.max_val
        set res = chain.max_val
    endif

    call DisableTrigger(GetTriggeringTrigger())

    set res_item_id = chain_val_get_item(chain, res)
    call RemoveItem(UnitItemInSlot(u, a_item_slot))
    call RemoveItem(UnitItemInSlot(u, b_item_slot))
    call UnitAddItemToSlotById(u, res_item_id, a_item_slot)
    if diff != 0 then
        set diff_item_id = chain_val_get_item(chain, diff)
        call UnitAddItemToSlotById(u, diff_item_id, b_item_slot)
    endif

    call EnableTrigger(GetTriggeringTrigger())
endfunction

private function on_item_pickup_action takes nothing returns nothing
    call on_item_pickup(GetTriggerUnit(), GetManipulatedItem())
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
    call TriggerAddAction(t, function on_item_pickup_action)
endfunction

endlibrary // libitemchain

usage:
JASS:
function init_item_chains takes nothing returns nothing
    call begin_item_chain('sorf' /*can be 0*/)
        call chain_add_item('sor1')
        call chain_add_item('sor2')
        call chain_add_item('sor3')
        call chain_add_item('sor4')
        call chain_add_item('sor5')
        call chain_add_item('sor6')
        call chain_add_item('sor7')
        call chain_add_item('sor8')
        call chain_add_item('sor9')
        call chain_add_item('sora')
    call end_item_chain()

    call begin_item_chain('sg00')
        call chain_is_disabled()
        call chain_add_item('sg01')
        call chain_add_item('sg02')
        call chain_add_item('sg03')
        call chain_add_item('sg04')
        call chain_add_item('sg05')
    call end_item_chain()
endfunction

function on_unit_death takes nothing returns boolean
    local unit k = GetKillingUnit()
    local unit d = GetDyingUnit()
    local item it
    local integer item_id
    local Item_Chain chain
    local Item_Chain array searched_chains
    local integer searched_chains_len
    local integer aa
    local integer a
    local integer b
    local integer bb
    local integer res

    set searched_chains[0] = item_get_chain('sg00')
    set searched_chains[1] = item_get_chain('sorf')
    set searched_chains_len = 2

    if k == null or k == d then
        set k = null
        set d = null
        return
    endif

    set a = 0
    set aa = UnitInventorySize(k)
    loop
        set it = UnitItemInSlot(k, a)

        set chain = 0
        if it != null then
            set item_id = GetItemTypeId(it)
            set chain = item_get_chain(item_id)
        endif

        if chain != 0 then
            set bb = searched_chains_len
            set b = 0
            loop
                exitwhen b == bb

                if chain == searched_chains[b] then
                    set res = item_get_chain_val(item_id) + 1
                    if res <= chain.max_val then
                        call RemoveItem(UnitItemInSlot(k, a))
                        call UnitAddItemToSlotById(k, chain_val_get_item(chain, res), a)
                    endif
                endif

                set b = b + 1
            endloop
        endif

        set a = a + 1
    endloop

    set k = null
    set d = null
    set it = null

    return
endfunction
 
Top