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

[vJASS] ItemIndexer

Level 5
Joined
Mar 15, 2017
Messages
96
This is a simple ItemIndexer.
vJASS:
library ItemIndex /* v1.2.1
****************************************************************************************************
*                                      _____________________
*                                      *     ItemIndex     *
*                                      * By: BloodForBlood *
*                                      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   */requires/*
*
*       */ optional Table /*
*           - https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*       */ optional ExtensionMethods /*
*           - https://www.hiveworkshop.com/pastebin/bacb4501906dae6e0545fa5da8b57a4615770/
*
*   What is ItemIndex:
*       It assigns 1-8191 index to items.An item get indexed if
*       you retrieved its index.In other words, it's not automatic.
*
*   Features:
*       1.Auto Item DeIndexing
*       2.Index Recycling
*
****************************************************************************************************
*   Configurations:                                                                               */
        globals
            private constant boolean USE_USER_DATA = true   // Enables retrieving item's index
                                                            // through "GetItemUserData"
/***************************************************************************************************
*
*   API:
*       function GetItemIndex takes item i returns integer
*       - Returns the index of the item
*           i: The item you want to get the index
*
*       function GetIndexItem takes integer i returns item
*       - Returns the item by using the index
*           i: The item index
*
*       function LockItem takes item i returns nothing
*       - Locks an item from being indexed or deindexed
*           i: The item you want to lock
*
*       function UnlockItem takes item i, boolean autoAction returns nothing
*       - Unlocks an item, the item can now be indexed or deindexed
*           i: The item you want to unlock
*           autoAction: If set to true, the item will be automatically indexed,
*                       or deindexed if the item doesn't exist
*
*   Special Thanks to:
*       Bribe - for "New Table"
*
***************************************************************************************************/
        private integer Index = 0
        private integer array RecycleIndex
        private integer RecycleArray = 0
        private item array Item
        private trigger DEATH_TRIG
    endglobals
   
    private struct Data
        static if LIBRARY_Table then
            static HashTable htb = HashTable.create
        else
            static hashtable ht = InitHashtable()
        endif
    endstruct
   
    function GetIndexItem takes integer i returns item
        return Item[i]
    endfunction
   
    private function Widget2Item takes widget w returns item
        static if LIBRARY_Table then
            return Data.htb[GetHandleId(w)].item[StringHash("ItemIndex_Widget2Item")]
        else
            return LoadItemHandle(Data.ht, GetHandleId(w), StringHash("ItemIndex_Widget2Item"))
        endif
        return null
    endfunction
   
    private function IndexItem takes item it returns nothing
        local item i = it
        local boolean b = false
        local integer id = GetHandleId(i)
        static if LIBRARY_Table then
            set b = Data.htb[id].boolean[StringHash("ItemIndex_Indexed")]
        else
            set b = LoadBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"))
        endif
        if (i != null) then
            if (not b) then
                if (RecycleArray < 1) then
                    set Index = Index + 1
                    static if LIBRARY_Table then
                        set Data.htb[id][StringHash("ItemIndex_Index")] = Index
                        set Data.htb[id].boolean[StringHash("ItemIndex_Indexed")] = true
                        set Data.htb[id].item[StringHash("ItemIndex_Widget2Item")] = i
                        call TriggerRegisterDeathEvent(DEATH_TRIG, i)
                    else
                        call SaveInteger(Data.ht, id, StringHash("ItemIndex_Index"), Index)
                        call SaveBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"), true)
                        call SaveItemHandle(Data.ht, id, StringHash("ItemIndex_Widget2Item"), i)
                        call TriggerRegisterDeathEvent(DEATH_TRIG, i)
                    endif
                    set Item[Index] = i
                else
                    static if LIBRARY_Table then
                        set Data.htb[id][StringHash("ItemIndex_Index")] = RecycleIndex[RecycleArray]
                        set Data.htb[id].boolean[StringHash("ItemIndex_Indexed")] = true
                        set Data.htb[id].item[StringHash("ItemIndex_Widget2Item")] = i
                        call TriggerRegisterDeathEvent(DEATH_TRIG, i)
                    else
                        call SaveInteger(Data.ht, id, StringHash("ItemIndex_Index"), RecycleIndex[RecycleArray])
                        call SaveBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"), true)
                        call SaveItemHandle(Data.ht, id, StringHash("ItemIndex_Widget2Item"), i)
                        call TriggerRegisterDeathEvent(DEATH_TRIG, i)
                    endif
                    set Item[Index] = i
                    set RecycleArray = RecycleArray - 1
                endif
            endif
        endif
        set i = null
    endfunction
   
    function GetItemIndex takes item it returns integer
        local integer i = 0
        local integer id = GetHandleId(it)
        local boolean b
        static if LIBRARY_Table then
            set b = Data.htb[id].boolean[StringHash("ItemIndex_Indexed")]
            if (not b) then
                static if LIBRARY_Table then
                    if not Data.htb[GetHandleId(it)].boolean[StringHash("ItemIndex_Locked")] then
                        call IndexItem(it)
                    endif
                else
                    if not LoadBoolean(Data.ht, GetHandleId(it), StringHash("ItemIndex_Locked")) then
                        call IndexItem(it)
                    endif
                endif
            endif
            set i = Data.htb[id][StringHash("ItemIndex_Index")]
        else
            set b = LoadBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"))
            if (not b) then
                static if LIBRARY_Table then
                    if not Data.htb[GetHandleId(it)].boolean[StringHash("ItemIndex_Locked")] then
                        call IndexItem(it)
                    endif
                else
                    if not LoadBoolean(Data.ht, GetHandleId(it), StringHash("ItemIndex_Locked")) then
                        call IndexItem(it)
                    endif
                endif
            endif
            set i = LoadInteger(Data.ht, id, StringHash("ItemIndex_Index"))
        endif
        return i
    endfunction
   
    private function DeIndexItem takes item it returns nothing
        local item i = it
        local integer dex = 0
        local boolean b = false
        local integer id = GetHandleId(i)
        static if LIBRARY_Table then
            set dex = Data.htb[id][StringHash("ItemIndex_Index")]
        else
            set dex = LoadInteger(Data.ht, id, StringHash("ItemIndex_Index"))
        endif
        static if LIBRARY_Table then
            set b = Data.htb[id].boolean[StringHash("ItemIndex_Indexed")]
        else
            set b = LoadBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"))
        endif
        if (i != null) then
            if (b and GetItemIndex(i) > 0) then
                set RecycleArray = RecycleArray + 1
                set RecycleIndex[RecycleArray] = dex
                static if LIBRARY_Table then
                    set Data.htb[id][StringHash("ItemIndex_Index")] = 0
                    set Data.htb[id].boolean[StringHash("ItemIndex_Indexed")] = false
                    set Data.htb[id].boolean[StringHash("ItemIndex_Locked")] = true
                else
                    call SaveInteger(Data.ht, id, StringHash("ItemIndex_Index"), 0)
                    call SaveBoolean(Data.ht, id, StringHash("ItemIndex_Indexed"), false)
                    call SaveBoolean(Data.ht, id, StringHash("ItemIndex_Locked"), true)
                endif
            endif
        endif
        set i = null
    endfunction
   
    function LockItem takes item i returns nothing
        static if LIBRARY_Table then
            set Data.htb[GetHandleId(i)].boolean[StringHash("ItemIndex_Locked")] = true
        else
            call SaveBoolean(Data.ht, GetHandleId(i), StringHash("ItemIndex_Locked"), true)
        endif
    endfunction
   
    function UnlockItem takes item i, boolean autoAction returns nothing
        static if LIBRARY_Table then
            set Data.htb[GetHandleId(i)].boolean[StringHash("ItemIndex_Locked")] = false
        else
            call SaveBoolean(Data.ht, GetHandleId(i), StringHash("ItemIndex_Locked"), false)
        endif
        if (autoAction) then
            static if LIBRARY_ExtensionMethods then
                if (not IsItemAlive(i)) then
                    call DeIndexItem(i)
                else
                    call IndexItem(i)
                endif
            else
                if (GetItemTypeId(i) == 0 and GetWidgetLife(i) <= 0.405) then
                    call DeIndexItem(i)
                else
                    call IndexItem(i)
                endif
            endif
        endif
    endfunction
   
    private function Leave takes nothing returns boolean
        local item i = Widget2Item(GetTriggerWidget())
        static if LIBRARY_Table then
            if not Data.htb[GetHandleId(i)].boolean[StringHash("ItemIndex_Locked")] then
                call DeIndexItem(i)
            endif
        else
            if not LoadBoolean(Data.ht, GetHandleId(i), StringHash("ItemIndex_Locked")) then
                call DeIndexItem(i)
            endif
        endif
        set i = null
        return false
    endfunction
   
    private function GetItemUserDataHook takes item i returns nothing
        if USE_USER_DATA then
            call SetItemUserData(i, GetItemIndex(i))
        endif
    endfunction
   
    hook GetItemUserData GetItemUserDataHook
   
    private module m
        private static method onInit takes nothing returns nothing
            set DEATH_TRIG = CreateTrigger()
            call TriggerAddCondition(DEATH_TRIG, Condition(function Leave))
        endmethod
    endmodule
   
    private struct s
        implement m
    endstruct
   
endlibrary

Demo:
vJASS:
scope Test initializer onInit
 
    private function onInit takes nothing returns nothing
        local item i = CreateItem('afac', GetStartLocationX(0), GetStartLocationY(0))
        call DisplayTimedTextToPlayer(Player(0), 0, 0, 3, "I used 'GetItemIndex' to retrieve " + GetItemName(i) + "'s index. Index: " + I2S(GetItemIndex(i)))
        set i = CreateItem('spsh', GetStartLocationX(0), GetStartLocationY(0))
        call DisplayTimedTextToPlayer(Player(0), 0, 0, 3, "I used 'GetItemUserData' to retrieve " + GetItemName(i) + "'s index. Index: " + I2S(GetItemUserData(i)))
        set i = null
    endfunction
 
endscope
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
No module initializer breaks compatibility with other systems.
This snippet will fail when item drops off mob.
Spam GetHandleId should be replaced with locals.

Such snippets were already submitted on this forum, yet all rejected due to holes and issues that cannot be easily fixed without Blizzard intervention.
Due to fact that this is not the first attempt, and all others were graveyarded, I vote for graveyard.
Otherwise, the older libs should be ungraveyarded and approved instead of this.
 
Level 5
Joined
Mar 15, 2017
Messages
96
No module initializer breaks compatibility with other systems.
This snippet will fail when item drops off mob.
Spam GetHandleId should be replaced with locals.

Such snippets were already submitted on this forum, yet all rejected due to holes and issues that cannot be easily fixed without Blizzard intervention.
Due to fact that this is not the first attempt, and all others were graveyarded, I vote for graveyard.
Otherwise, the older libs should be ungraveyarded and approved instead of this.
Hmm, I think you're right...But I don't understand why Init functions should be module... :con:
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
It's because of jasshelper's fked up initialization process. It's not as simple as let's say in Wurst, where all initialization ends up being placed in init block.

You need to know the initialization order to create secure code. For system codes, it's preferable to use module initializers to force initialization as quickly as possible.
Otherwise, any other system that would requires / uses / needs yours and uses module initializer rather than function initializer could experience unexpected behavior.

Let's use library MyItemIndexerPlugin requires ItemIndexer as an example and remind ourselves of ItemIndexer's initializer content:
JASS:
    private function Init takes nothing returns nothing
        local group g = CreateGroup()
        set ht = HashTable.create()
        call RegisterItemEnterEvent(function Enter)
        call RegisterItemLeaveEvent(function Leave)
        if (isIgnoredPreplaced()) then
            call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
            call ForGroup(g, function indexOnInventory)
            call EnumItemsInRect(bj_mapInitialPlayableArea, null, function Enter)
        endif
        call DestroyGroup(g)
        set g = null
    endfunction
As we can see, during scripts initialization, all preplaced items will be enumerated and have unique indexes assigned to them.
Consequently, one can expect that once ItemIndexer initialization is done, he or she can take advantage of those indexes in their own snippet initializer.

In my plugin, I'd expect ItemIndexer to always be initialized before my plugin lib. However, if my initializer takes form of:
JASS:
private function OnEnumItems takes nothing returns nothing
   call BJDebugMsg("Enumerating item with index: " + I2S(GetIndexItem(GetEnumItem())))
endfunction

private module MyItemIndexerPluginModule
    private static method onInit takes nothing returns nothing
        call EnumItemsInRect(bj_mapInitialPlayableArea, null, function OnEnumItems)
    endmethod
endmodule
This won't be true.

One would expect to have a log with unique index displayed for each preplaced item. Unfortunately, due to initialization order, module initializer will be prioritized over function initializer, thus plugin library becomes initialized before ItemIndexer ever gets change to. My log would dump a bunch of zeroes rather than expected non-zero integers.
 
Level 5
Joined
Mar 15, 2017
Messages
96
It's because of jasshelper's fked up initialization process. It's not as simple as let's say in Wurst, where all initialization ends up being placed in init block.

You need to know the initialization order to create secure code. For system codes, it's preferable to use module initializers to force initialization as quickly as possible.
Otherwise, any other system that would requires / uses / needs yours and uses module initializer rather than function initializer could experience unexpected behavior.

Let's use library MyItemIndexerPlugin requires ItemIndexer as an example and remind ourselves of ItemIndexer's initializer content:
JASS:
    private function Init takes nothing returns nothing
        local group g = CreateGroup()
        set ht = HashTable.create()
        call RegisterItemEnterEvent(function Enter)
        call RegisterItemLeaveEvent(function Leave)
        if (isIgnoredPreplaced()) then
            call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
            call ForGroup(g, function indexOnInventory)
            call EnumItemsInRect(bj_mapInitialPlayableArea, null, function Enter)
        endif
        call DestroyGroup(g)
        set g = null
    endfunction
As we can see, during scripts initialization, all preplaced items will be enumerated and have unique indexes assigned to them.
Consequently, one can expect that once ItemIndexer initialization is done, he or she can take advantage of those indexes in their own snippet initializer.

In my plugin, I'd expect ItemIndexer to always be initialized before my plugin lib. However, if my initializer takes form of:
JASS:
private function OnEnumItems takes nothing returns nothing
   call BJDebugMsg("Enumerating item with index: " + I2S(GetIndexItem(GetEnumItem())))
endfunction

private module MyItemIndexerPluginModule
    private static method onInit takes nothing returns nothing
        call EnumItemsInRect(bj_mapInitialPlayableArea, null, function OnEnumItems)
    endmethod
endmodule
This won't be true.

One would expect to have a log with unique index displayed for each preplaced item. Unfortunately, due to initialization order, module initializer will be prioritized over function initializer, thus plugin library becomes initialized before ItemIndexer ever gets change to. My log would dump a bunch of zeroes rather than expected non-zero integers.
Oh thanks for the info!

Edit: UPDATED v1.2.1!
Removed a lot of stupidity
Removed ItemEvent requirement(I abandoned that library)
"USE_USER_DATA" option has returned!!
Added new functions: "LockItem" and "UnlockItem"
Added "ExtensionMethods" as an optional requirement
 
Last edited:
The good thing about registering the death event for every item is that you can deindex them instantly when their removed. The problem is when using a global trigger you are leaking an event handle for each item that get's removed (which is what your code does). The leakless option would be to create a new trigger per item and destroy it afterwards, however that means you are creating an extra three handles per item that exists. Neither of the options are particularly desirable. This is probably why Vexorian's ItemDex does the opposite, it's not so important to have the item id recycled instantly as you likely won't reach the maximum array limit. This of course doesn't work if you need to detect the instant removal of an item, though.

Seeing that this version leaks and how the alternative methods are clunky, along with what @Bannar said, I think it should be graveyarded. There are better versions of this already graveyarded.
 
Level 5
Joined
Mar 15, 2017
Messages
96
The good thing about registering the death event for every item is that you can deindex them instantly when their removed. The problem is when using a global trigger you are leaking an event handle for each item that get's removed (which is what your code does). The leakless option would be to create a new trigger per item and destroy it afterwards, however that means you are creating an extra three handles per item that exists. Neither of the options are particularly desirable. This is probably why Vexorian's ItemDex does the opposite, it's not so important to have the item id recycled instantly as you likely won't reach the maximum array limit. This of course doesn't work if you need to detect the instant removal of an item, though.

Seeing that this version leaks and how the alternative methods are clunky, along with what @Bannar said, I think it should be graveyarded. There are better versions of this already graveyarded.
Yeah, graveyard this.At least I learned something about it. :D
 
Top