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

Item Indexer v1.4.0 [GUI friendly]

JASS:
/********************************Item Indexer v1.4.0 by NightSkyAurora********************************
    WHAT IT DOES:
        - Assigns a unique custom value (ItemUserData in JASS) to all the items in the game
        - Creates an event that fires when an item is created/sold/destroyed/removed from the game
        - Instantly removes items that are not removed properly when used such as Tome of Experience

    IMPORTING THE SYSTEM:
        1. file -> preferences -> general -> check outomaticly create unknown varibles when pasting trigger data.
        2. Copy the 'Item Indexer' trigger folder and paste it in your map
        3. Delete the 'variable creator' trigger

    USING THE SYSTEM
        If you have the item but wants to get its id, get the custom value of that item
        If you have the id but wants to get the item it referes to, use IDexItems[id] to get the item

    CONS:
        - The item's custom value becomes unusible for other purposes
        - Item Id cannot always be accessed immediately without calling IndexItem() manually

    ////////////////////////////   API   /////////////////////////////
    //////////////////////////////////////////////////////////////////
   
    function IndexItem takes item itm returns integer
        Indexes a selected item and returns it's new id
   
    function IndexEnumItem takes nothing returns nothing
        Indexes a group of items such as items in playable map area
   
    function IndexItemOnTheGround takes real x, real y returns integer
        Finds items at a nearby point and index them
        returns id of last indexed item
       
    //////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////
   
    IMPORTANT!!
    The following functions doesn't guarentee imidiate access to an item's id
    If the id is needed immediately, please use the functions as explained in the api
   
        CreateItem
        CreateItemLoc | GUI Item - Create
        UnitAddItemByIdSwapped | GUI Hero - Create Item For Hero
        UnitAddItemById
        UnitAddItemToSlotById
        UnitDropItem | Items dropped by dying units
        WidgetDropItem | Items dropped by dying destructables
   
******************************************************************************************************/

library ItemIndexer initializer Init
    globals
        private integer InstCount = 0
        private hashtable hash = InitHashtable()
        private trigger array TrigDeindexer
        private integer array recycle
    endglobals
   
    //De-index items that are destroyed
    private function DeindexDestroyed takes nothing returns nothing
        local trigger t = GetTriggeringTrigger()
        local integer i = LoadInteger(hash, GetHandleId(t), 0)
        call FlushChildHashtable(hash, GetHandleId(t))
        call DisableTrigger(t)
        set udg_IDex = i
        set udg_ItemEvent = 2.00
        set udg_ItemEvent = 0.00
        call TriggerSleepAction(1)
        set udg_IDex = i
        set udg_ItemEvent = 4.00
        set udg_ItemEvent = 0.00
        call SetWidgetLife(udg_IDexItems[i], 1)
        call RemoveItem( udg_IDexItems[i] )
        set udg_IDexItems[i] = null
        set TrigDeindexer[i] = null
        set recycle[i] = recycle[0]
        set recycle[0] = i
        call DestroyTrigger(t)
    endfunction
   
    //De-index items that are sold
    private function DeindexSold takes nothing returns nothing
        local integer i = GetItemUserData(GetSoldItem())
        set udg_IDex = i
        set udg_ItemEvent = 3.00
        set udg_ItemEvent = 0.00
        set udg_IDexItems[i] = null
        set recycle[i] = recycle[0]
        set recycle[0] = i
        //clean and destroy the item's destruction trigger
        call FlushChildHashtable(hash, GetHandleId(TrigDeindexer[i]))
        call DestroyTrigger(TrigDeindexer[i])
        set TrigDeindexer[i] = null
    endfunction
   
    //This function ultimately assigns a unique custom value to each item and returns its id
    function IndexItem takes item itm returns integer
        local integer i = GetItemUserData(itm)
        if not udg_IDexIsIndexed[i] and itm != null then
            if recycle[0] == 0 then
                set InstCount = InstCount + 1
                set i = InstCount
            else
                set i = recycle[0]
                set recycle[0] = recycle[i]
            endif
            set udg_IDexItems[i] = itm
            call SetItemUserData(itm, i)
            set udg_IDexIsIndexed[i] = true
            //create a trigger that detects when the item is destroyed
            set TrigDeindexer[i] = CreateTrigger()
            call TriggerRegisterDeathEvent(TrigDeindexer[i], itm)
            call TriggerAddAction(TrigDeindexer[i], function DeindexDestroyed)
            call SaveInteger(hash, GetHandleId(TrigDeindexer[i]), 0, i)
            set udg_IDex = i
            set udg_ItemEvent = 1.00
            set udg_ItemEvent = 0.00
        endif
        return i
    endfunction

    //Index each enum item
    function IndexEnumItem takes nothing returns nothing
        call IndexItem(GetEnumItem())
    endfunction
   
    //Index items near a target point
    function IndexItemOnTheGround takes real x, real y returns integer
        local rect r = Rect(x-256.00, y-256.00, x+256.00, y+256.00)
        call EnumItemsInRect(r, null, function IndexEnumItem)
        set r=null
        return udg_IDex // returns last indexed item's id
    endfunction

    //Searches the saved locations for any items and request enum items to be indexed
    private function LoadItemLoc takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer h_id = GetHandleId(t)
        local real x = LoadReal(hash, h_id, 0)
        local real y = LoadReal(hash, h_id, 1)
        local rect r = Rect(x-256.00, y-256.00, x+256.00, y+256.00)
        call EnumItemsInRect(r, null, function IndexEnumItem)
        call FlushChildHashtable(hash, h_id)
        call DestroyTimer(t)
        set t=null
        set r=null
    endfunction

    //Saves the location of an item and wait 0.00 seconds for the item to be created
    private function SaveItemLoc takes real x, real y returns nothing
        local timer t = CreateTimer()
        local integer h_id = GetHandleId(t)
        call SaveReal(hash, h_id, 0, x)
        call SaveReal(hash, h_id, 1, y)
        call TimerStart(t, 0.00, false, function LoadItemLoc)
        set t=null
    endfunction
   
    //The following functions hooks all functions that can create an item to index it
    private function CreateItemHook takes integer itemId, real x, real y returns nothing
        call SaveItemLoc(x, y)
    endfunction

    private function CreateItemLocHook takes integer itemId, location loc returns nothing
        call SaveItemLoc(GetLocationX(loc), GetLocationY(loc))
    endfunction

    private function UnitAddItemByIdSwappedHook takes integer itemId, unit u returns nothing
        if UnitInventoryCount(u) == UnitInventorySize(u) then
            call SaveItemLoc(GetUnitX(u), GetUnitY(u))
        endif
    endfunction

    private function UnitAddItemByIdHook takes unit u, integer itemId returns nothing
        if UnitInventoryCount(u) == UnitInventorySize(u) then
            call SaveItemLoc(GetUnitX(u), GetUnitY(u))
        endif
    endfunction

    private function UnitAddItemToSlotByIdHook takes unit u, integer i, integer slot returns nothing
        call SaveItemLoc(GetUnitX(u), GetUnitY(u))
    endfunction

    //Index items that is dropped when a unit dies
    private function UnitDropItemHook takes unit inUnit, integer inItemID returns nothing
        call SaveItemLoc(GetUnitX(inUnit), GetUnitY(inUnit))
    endfunction

    //Index items that is dropped when a widget dies
    private function WidgetDropItemHook takes widget inWidget, integer inItemID returns nothing
        call SaveItemLoc(GetWidgetX(inWidget), GetWidgetY(inWidget))
    endfunction

    //Index items in inventories that were given to units via the map editor
    private function IndexInventories takes nothing returns nothing
        local integer i = 0
        local unit u = GetEnumUnit()
        if UnitInventorySize(u) > 0 then
            loop
                call IndexItem(UnitItemInSlot(u, i))
                set i = i + 1
                exitwhen i >= bj_MAX_INVENTORY
            endloop
        endif
        set u=null
    endfunction

    //Index items that are sold to a unit
    private function IndexOnCreate takes nothing returns nothing
        call IndexItem(GetManipulatedItem())
    endfunction
   
    //Index items that are sold to the ground
    private function IndexOnBuy takes nothing returns boolean
        local real x = GetUnitX(GetOrderedUnit())
        local real y = GetUnitY(GetOrderedUnit())
        local item i = CreateItem(GetIssuedOrderId(), x, y)
        if i != null then
            call RemoveItem(i)
            call SaveItemLoc(x, y)
        endif
        set i = null
        return false
    endfunction

    private function Init takes nothing returns nothing
        local trigger TrigIndexOnCreate = CreateTrigger()
        local trigger TrigIndexOnBuy = CreateTrigger()
        local trigger TrigItemDeindexer = CreateTrigger()
        local rect rt = GetPlayableMapRect()
        local group g = GetUnitsInRectAll(rt)
       
        hook CreateItem CreateItemHook
        hook CreateItemLoc CreateItemLocHook
        hook UnitAddItemByIdSwapped UnitAddItemByIdSwappedHook
        hook UnitAddItemById UnitAddItemByIdHook
        hook UnitAddItemToSlotById UnitAddItemToSlotByIdHook
        hook UnitDropItem UnitDropItemHook
        hook WidgetDropItem WidgetDropItemHook
       
        call TriggerRegisterAnyUnitEventBJ(TrigIndexOnCreate, EVENT_PLAYER_UNIT_PICKUP_ITEM )
        call TriggerAddAction(TrigIndexOnCreate, function IndexOnCreate)
        call TriggerRegisterAnyUnitEventBJ(TrigIndexOnBuy, EVENT_PLAYER_UNIT_ISSUED_ORDER )
        call TriggerAddAction(TrigIndexOnBuy, function IndexOnBuy)
        call TriggerRegisterAnyUnitEventBJ( TrigItemDeindexer, EVENT_PLAYER_UNIT_PAWN_ITEM )
        call TriggerAddAction(TrigItemDeindexer, function DeindexSold)  
        call EnumItemsInRect(rt, null, function IndexEnumItem )
        call ForGroup(g, function IndexInventories)
        set rt = null
        call DestroyGroup(g)
    endfunction
endlibrary



-Give one second change before removing items to display death animation
-Added an event to differentiate between destroyed and removed item


-Removed a leak


-Some small code improvements


-Removed some leaks
-Optimized some code to improve performance
-Improved documentation


-Each item now has its own trigger
-The system can distinguish between sold and destroyed items more clearly
-Some minor performance improvements


-Huge code optimizations. Credits to ZiBitheWand3r3r once again...
-The whole system is now in one trigger. No more header code
-Fixed on-index event not working


-Got hooked functions working (thanks ZiBitheWand3r3r)
-Organized code to improve performance


-Uses a different index approach as requested by Reventhous
-Converted the main trigger to custom text


-Index items that are created for hero
-Index items that are dropped by natural creeps
-Index items that are given to units via the map editor
-Merged all the triggers into one trigger
-Uses hash table to find the destroyed item quicker
-Added an "on index" event
-int IDex can be used to get the id of the last created item


-Fixed a bug where the event is fired twice when items are sold


uploaded



Huge credits to ZiBitheWand3r3r for all your support!
Contents

Item Indexer v1.4.0 (Map)

Reviews
ZiBitheWand3r3r
This System deprecates [System] Item Cleanup in my opinion. Also this thread can be updated Resources That Have Yet To Be Coded All possible ways to create items in game are supported by this library. Including GUI actions: Hero - Create...
Level 5
Joined
May 2, 2015
Messages
109
Nice work!

-use ["jass"]["/jass"] without " " for the trigger code. :)
-why "Game - Display to (All players) the text: abc" and "Game - Display to (All players) the text: def" are in your code?
-in this line, if GetItemUserData(itm) == 0 then.
I think you should replace it with this if not udg_IDexIsIndexed[udg_IDexNext] then line.
udg_IDexIsIndexed[udg_IDexNext] = true after the item gets its index.

EDIT:
In my opinion, this
JASS:
function IndexItem takes item itm returns nothing
    if GetItemUserData(itm) == 0 then
        set udg_IDexItems[udg_IDexNext] = itm
        call SetItemUserData(itm, udg_IDexNext)
        call TriggerRegisterDeathEvent(gg_trg_Item_DeIndexer, itm)
        loop
            exitwhen udg_IDexItems[udg_IDexNext] == null
            set udg_IDexNext = udg_IDexNext + 1
        endloop
    endif
endfunction

should be like this

JASS:
function IndexItem takes item itm returns nothing
    local integer i = GetItemUserData(itm)

    if not udg_IDexIsIndexed[udg_IDexNext] then

        if udg_IDexRecycle[0] == 0 then
            set udg_IDexInstanceCount = udg_IDexInstanceCount + 1
            set i = udg_IDexInstanceCount
        else
            set i = udg_IDexRecycle[0]
            set udg_IDexRecycle[0] = udg_IDexRecycle[i]
        endif

        set udg_IDexItems[i] = itm
        call SetItemUserData(itm, i)
        set udg_IDexIsIndexed[i] = true

        call TriggerRegisterDeathEvent(gg_trg_Item_DeIndexer, itm)
    endif
endfunction

then add this code in Deindexer
JASS:
    set udg_IDexRecycle[itemIndex] = udg_IDexRecycle[0]
    set udg_IDexRecycle[0] = itemIndex

It just my opinion :)
 
Last edited:
-use ["jass"]["/jass"] without " " for the trigger code. :)
Thanks, I will in next update
-why "Game - Display to (All players) the text: abc" and "Game - Display to (All players) the text: def" are in your code?
It is some testing code I forgot to remove, obvously :ao:

I tried the code that you provided but it didn't work properly. To be honest, by reading your code, I don't fully understand how that algorithm works.
I've also noticed this:
JASS:
if not udg_IDexIsIndexed[udg_IDexNext] then[\jass]
In my system, udg_IDexNext will ALWAYS point to an open index (which is the next id that will be given when an item is indexed) Won't this always return true?
 
Level 5
Joined
May 2, 2015
Messages
109
I tried the code that you provided but it didn't work properly
My mistake, It suppose to be like this :)
JASS:
function IndexItem takes item itm returns nothing
    local integer i = GetItemUserData(itm)

    if not udg_IDexIsIndexed[i] then

        if udg_IDexRecycle[0] == 0 then
            set udg_IDexInstanceCount = udg_IDexInstanceCount + 1
            set i = udg_IDexInstanceCount
        else
            set i = udg_IDexRecycle[0]
            set udg_IDexRecycle[0] = udg_IDexRecycle[i]
        endif

        set udg_IDexItems[i] = itm
        call SetItemUserData(itm, i)
        set udg_IDexIsIndexed[i] = true

        call TriggerRegisterDeathEvent(gg_trg_Item_DeIndexer, itm)
    endif
endfunction

I've also noticed this:
Ofcourse it will return true, but after this set udg_IDexIsIndexed[i] = true line do its work, it will return false and that will avoid multiple index for one item.

To be honest, by reading your code, I don't fully understand how that algorithm works.
Well, I think it will take hours for me to explain this. I'll try make it short.

About udg_IDexIsIndexed
using if GetItemUserData(itm) == 0 then might cause conflict, because user has the independence to use call SetItemUserData(itm, i)
So, boolean will avoid that problem.

I'll explain the "recycle" things later, when I have the time.

EDIT: Here, read this
Introduction to Collections: Index Array, Linked List, Stack, Queue, Dequeue
 
Last edited:
Level 5
Joined
May 2, 2015
Messages
109
-from GUI/JASS, now this system is vJass (the globals is vJass). Intentional?
-the Init trigger, why GUI form while JASS form is better
-indent your code
-can you post your update summary in the reply.

I've downloaded your map and what I get is v1.0b, not the latest. And, I tried to replace your code with my code that I mentioned and it works fine.
 
Level 5
Joined
May 2, 2015
Messages
109
Oh my goodness I can't believe I just created a vJass system
Not entirely. I recommend you to go back to easy GUI/JASS. (just remove the globals)

But, if you really want your code to be in vJass, you will need to learn more about it. You can do the learning by reading the jasshelpermanual.html in jasshelper directory or just find vJass tutorials in HIVE or wc3c.

Anyway,

-when it come to JASS, this
JASS:
function Trig_Item_Indexer_Actions takes nothing returns nothing
    call ExecuteFunc("InitializeItemIndexer")
endfunction
code shouldn't. Just directly use InitializeItemIndexer as action function.

-your code looks empty and dead, some //documentation will fix that

-set TrigItemDeindexer = CreateTrigger()
why TrigItemDeindexer is global, not local.

-I forgot to mention this, put set udg_IDexIsIndexed[i] = false before
JASS:
    set udg_IDexRecycle[i] = udg_IDexRecycle[0]
    set udg_IDexRecycle[0] = i

-in function IndexOnDeath takes nothing returns nothing, add set u = null
How this function works? Is it will index when item drops?

-is this
JASS:
        call SaveWidgetHandle(udg_IDexHash, 0, 0, GetTriggerWidget())
        set i = GetItemUserData(LoadItemHandleBJ(0, 0, udg_IDexHash))
really work? I haven't test it yet and haven't done it before.

Actually, there's lot more to say but, thats it for now :)
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
So this index items upon pickup not upon creation? People normally use an Indexer for the "Create/Remove Events" but this does not index them upon creation instead it index them when that item is acquired or bought which is already possible to do using native events. Also, it only deindex when the item is sold not when it is generically removed e.g. using RemoveItem(). Item Indexer has a small discussion here.
 
Level 18
Joined
Nov 21, 2012
Messages
835
Is hashtable the only way to convert widget to item? I'm just currious..

By the way quite smart approach
JASS:
call TriggerRegisterDeathEvent(TrigItemDeindexer, itm)
good job :)

But as Flux said: you have to index items upon creation. It's not Item Indexer w/o this ability.
Im not sure but maybe hook ?
JASS:
hook CreateItem CreateItem_Ex
 
So this index items upon pickup not upon creation? People normally use an Indexer for the "Create/Remove Events" but this does not index them upon creation instead it index them when that item is acquired or bought which is already possible to do using native events. Also, it only deindex when the item is sold not when it is generically removed e.g. using RemoveItem(). Item Indexer has a small discussion here.

I've tested this system when I created it and made sure that you can't create an item without causing it to be indexed. When you use "create item for hero", the "hero aquires item" even will fire. If the hero simply picks up an item that is already indexed, the system won't index it again. There is no way (that I know of) to detect when an item is created at a location, unless you loop through all the items in playable map area that will greatly affect performance. This is why I created those custom functions that DOES index the item when created at a location. It is also de-indexed when you remove it. Just enter the id of the item you wan't removed. It will be removed by a GUI function and will be de-indexed successfully.

Is hashtable the only way to convert widget to item? I'm just currious..

By the way quite smart approach
JASS:
call TriggerRegisterDeathEvent(TrigItemDeindexer, itm)
good job :)

But as Flux said: you have to index items upon creation. It's not Item Indexer w/o this ability.
Im not sure but maybe hook ?
JASS:
hook CreateItem CreateItem_Ex

Its the only conversion process I can think of...
I don't know how to detect items when created at a location. As explained above, a looping trigger can do the job but there might be a 0.03125 second delay and it will reduce game performance. An alternative function is the only solution I can think off :/

btw... what does "hook" do??????
 
Level 13
Joined
Nov 7, 2014
Messages
571
Sorry I missed the header file, so it seems you typecast (using hashtable) the item to a widget and it runs the unit death event to detect item death? That's a cool and hacky approach.

JASS:
// from common.j
type widget extends agent // an interactive game object with life
type item extends widget

item extends widget, i.e it can be passed to functions that take a widget (think of the GetHandleId function, it can take any handle type (units, items, widgets, etc.) because all handle types extend handle)
native TriggerRegisterDeathEvent takes trigger whichTrigger, widget whichWidget returns event

The TriggerRegisterDeathEvent function takes a widget, i.e it can be passed a widget or any type that extends widget (unit, item, destructable).

The typecast in the function ItemDeindexer is actually a downcast, from widget to item, and its needed because there is no GetTrigger|DyingItem, but there are GetTriggerUnit(), GetTriggerDestructable and GetTriggerWidget.
JASS:
call SaveWidgetHandle(udg_IDexHash, 0, 0, GetTriggerWidget())
... LoadItemHandleBJ(0, 0, udg_IDexHash)) // could use LoadItemHandle?

So nothing really "hacky" in my opinion.
The funny/strange thing is that TriggerRegisterDeathEvent's action function gets called by RemoveItem =)

Its the only conversion process I can think of...
leandrotp's Typecast library would probably work (it doesn't come with a widget to item typecasting function but it should be easy to write one).
 
Hooks looks pretty awesome :D thanks!

Ok so I guess the idea is to "detect" when the CreateItemLoc() is called and then just indexed the item that is returned by that function? Ok so I added this:
JASS:
hook CreateItemLoc CreateItemLocEx
and altered my function to look like this:
JASS:
function CreateItemLocEx takes integer itemid, location loc returns item
    call IndexItem(bj_lastCreatedItem)
    call BJDebugMsg("function was hooked!")
    call BJDebugMsg(I2S(GetItemUserData(bj_lastCreatedItem)))
    return bj_lastCreatedItem
endfunction

But bj_lastCreatedItem didn't return the item that was created by the hooked function. Sooo.. how do I access it?
 
Level 18
Joined
Nov 21, 2012
Messages
835
hook allows you to catch when original function is called as well as parameters passed to that function,
JASS:
function CreateItemLoc takes integer itemId, location loc returns item
    set bj_lastCreatedItem = CreateItem(itemId, GetLocationX(loc), GetLocationY(loc))
    return bj_lastCreatedItem
endfunction
so you have access to integer itemid and location loc only, but with 0sec timer you can get bj_lastCreatedItem:
JASS:
function TimerOnExp takes nothing returns nothing
    call IndexItem(bj_lastCreatedItem)
endfunction
function CreateItemLoc_Ex takes integer itemId, location loc returns nothing
    call TimerStart(g_timerItems, 0.00, false, function TimerOnExp)
endfunction
I guess you have to hook all possible BJ and natives like UnitAddItemByIdSwapped CreateItem UnitAddItemById UnitAddItemToSlotById something like move rect to x, y and enum items searching for non-indexed one. Also loop thru target unit's inventory to find new item.
 
Thanks for the submission, a quick few notes on what I noticed with the system
V1.2:
- InitTrig_Item_Indexer, Trig_Item_Indexer_Actions and InitializeItemIndexer could all be combined
- you use "local rect rt = GetPlayableMapRect()" and then later "set rt = GetPlayableMapRect()" in your init trigger, don't need this second one, also this could be replaced with "bj_mapInitialPlayableArea"
- you forgot to actually use group g in your init trigger and instead use GetUnitsInRectAll(rt), speaking of you should use "call GroupEnumUnitsInRect(g, r, filter)" instead
- LoadItemHandle() instead of LoadItemHandleBJ()
- You should implement hooks for the item creation functions to make this a true Item Indexer as others have mentioned
 
Level 18
Joined
Nov 21, 2012
Messages
835
global timer and global rect will not work as you tested
but dynamically created timer and rect will.

JASS:
hook CreateItemLoc CreateItemLocEx                                  //1
hook UnitAddItemByIdSwapped UnitAddItemByIdSwappedEx //2
hook CreateItem CreateItemEx                                            //3
hook UnitAddItemById UnitAddItemByIdEx                            //4
hook UnitAddItemToSlotById UnitAddItemToSlotByIdEx         //5
//-------------------------------------------------------------------
//-------------------------------------------------------------------
//these 2 for demo only
function IsItemIndexed takes item i returns boolean
    return GetItemUserData(i)>0
endfunction
function IndexItem takes item i returns nothing
    call SetItemUserData(i, 1)
endfunction

function EnumItm takes nothing returns nothing
    local item i = GetEnumItem()
    if (not IsItemIndexed(i)) then
        call IndexItem(i)
        call BJDebugMsg("indexing item: " + GetItemName(i))
    endif
    set i=null
endfunction

function SearchInRect takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer h_id = GetHandleId(t)
    local real x = LoadReal(udg_IDexHash, h_id, 1)
    local real y = LoadReal(udg_IDexHash, h_id, 2)
    local rect r = Rect(x-128.00, y-128.00, x+128.00, y+128.00)
    call EnumItemsInRect(r, null, function EnumItm)
    call FlushChildHashtable(udg_IDexHash, h_id)
    call DestroyTimer(t)
    set t=null
    set r=null
endfunction

function FindItem takes real x, real y returns nothing
    local timer t = CreateTimer()
    local integer h_id = GetHandleId(t)
    call SaveReal(udg_IDexHash, h_id, 1, x)
    call SaveReal(udg_IDexHash, h_id, 2, y)
    call TimerStart(t, 0.00, false, function SearchInRect)
    set t=null
endfunction


function CreateItemEx takes integer itemid, real x, real y returns nothing
    call FindItem(x, y)
endfunction
function CreateItemLocEx takes integer itemId, location loc returns nothing
    call FindItem(GetLocationX(loc), GetLocationY(loc))
endfunction
function UnitAddItemByIdSwappedEx takes integer itemId, unit whichHero returns nothing
    call FindItem(GetUnitX(whichHero), GetUnitY(whichHero))
endfunction
function UnitAddItemByIdEx takes unit whichUnit, integer itemId returns nothing
    call FindItem(GetUnitX(whichUnit), GetUnitY(whichUnit))
endfunction
function UnitAddItemToSlotByIdEx takes unit whichUnit, integer itemId, integer itemSlot returns nothing
    call FindItem(GetUnitX(whichUnit), GetUnitY(whichUnit))
endfunction

please be aware that user can make a shop that sells items not to inventory but on the ground
this is my proposal to index such items :
JASS:
function Trig_ord_Conditions takes nothing returns boolean
    local real x = GetUnitX(GetOrderedUnit())
    local real y = GetUnitY(GetOrderedUnit())
    local item i = CreateItem(GetIssuedOrderId(), x, y)
    if i != null then
        call RemoveItem(i)
        call BJDebugMsg("indexing near seller..")
        call FindItem(x, y)
    endif
    return false
endfunction
//===========================================================================
function InitTrig_ord takes nothing returns nothing
    set gg_trg_ord = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_ord, EVENT_PLAYER_UNIT_ISSUED_ORDER )   
    call TriggerAddCondition( gg_trg_ord, Condition( function Trig_ord_Conditions ) )
endfunction

good luck! I really like your system hope to see it ready and approved soon ;)
 

Attachments

  • NightSkyAurora.w3x
    17.3 KB · Views: 122
Level 18
Joined
Nov 21, 2012
Messages
835
Still is work to do ;)

abandom this:
JASS:
call TriggerRegisterAnyUnitEventBJ( TrigIndexOnBuy, EVENT_PLAYER_UNIT_SELL_ITEM )
call TriggerAddAction(TrigIndexOnBuy, function IndexOnBuy)
as 'unit pick up an item' already covers above event
JASS:
call TriggerRegisterAnyUnitEventBJ(TrigIndexOnCreate, EVENT_PLAYER_UNIT_PICKUP_ITEM )
call TriggerAddAction(TrigIndexOnCreate, function IndexOnCreate)
and all hooks should just call SaveItemLoc(x, y) no need to check inventory/slots etc

JASS:
call TriggerRegisterAnyUnitEventBJ(TrigIndexOnDeath, EVENT_PLAYER_UNIT_DEATH )
call TriggerAddAction(TrigIndexOnDeath, function IndexOnDeath)
I guess you're using it for preplaced units who drop items upon death? No need to. Use 2 hooks instead:
hook UnitDropItem UnitDropItemHook and hook WidgetDropItem WidgetDropItemHook
If you look inside how game executes drops upon death you'll see its using "Unit/Widget DropItem". That solution also covers drops from trees/destructables that was not impemented yet in your system.
JASS:
function UnitDropItemHook takes unit inUnit, integer inItemID returns nothing
    call SaveItemLoc(GetUnitX(inUnit), GetUnitY(inUnit))
endfunction
function WidgetDropItemHook takes widget inWidget, integer inItemID returns nothing
    call SaveItemLoc(GetWidgetX(inWidget), GetWidgetY(inWidget))
endfunction

inside IndexEnumItem
JASS:
local item i = GetEnumItem()
call IndexItem(i)
set i=null
into --> call IndexItem(GetEnumItem())

inside IndexInventories you may want to check if UnitInventorySize(u)>0 then before looping thru all inventory slots

JASS:
function IndexOnCreate takes nothing returns nothing
    call IndexItem(bj_lastCreatedItem)
    call IndexItem(GetManipulatedItem())
endfunction
abandom call IndexItem(bj_lastCreatedItem) as it doesnt make sense bj_lastCreatedItem is not nulled after using BJ, so you constantly trying to index already indexed item (in demo map: orb of lightning). IndexOnCreate fires upon picking an item so GetManipulatedItem is enough.

Please remember Shops that can "sell" items on the ground. Your system in its current form will not index such items. my proposal to index those items:
JASS:
function Trig_ord_Conditions takes nothing returns boolean
    local real x = GetUnitX(GetOrderedUnit())
    local real y = GetUnitY(GetOrderedUnit())
    local item i = CreateItem(GetIssuedOrderId(), x, y)
    if i != null then
        call RemoveItem(i)
        call BJDebugMsg("indexing near seller..")
        call SaveItemLoc(x, y)
    endif
    return false
endfunction
//=============================================
function InitTrig_ord takes nothing returns nothing
    set gg_trg_ord = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_ord, EVENT_PLAYER_UNIT_ISSUED_ORDER )  
    call TriggerAddCondition( gg_trg_ord, Condition( function Trig_ord_Conditions ) )
endfunction

And pack whole system into library (no need to split and move part to the header)
If you need example how to do that all let me know. ;)
 
Wow thanks for this useful mod. I applied all suggestions except checking inventory slots. What I basically do is checking if "UnitAddItemToSlotById()" gives an item to an empty slot. If the slot is empty, I don't need to save it's location as it will be already indexed by "IndexOnCreate()", which is a more efficient function. The purpose of that condition is to improve performance by not wasting time on trying to index an item with an expensive method that will be indexed anyway.

P.S abandom -> abandon
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
JASS:
function UnitAddItemToSlotByIdHook takes unit u, integer i, integer slot returns nothing
    if (udg_IDexAutomatic) then
        if (UnitItemInSlotBJ(u, i) == null ) then
            call SaveItemLoc(GetUnitX(u), GetUnitY(u))
        else
            call IndexItem(UnitItemInSlotBJ(u, i))
        endif
    endif
endfunction
there is mistake inside causing to always call SaveItemLoc(GetUnitX(u), GetUnitY(u)) cause i is integer itemID, you should use slot. But event after correction it will not work properly. To test it call twice in "test" trigger call UnitAddItemToSlotById(gg_unit_Hblm_0000, 'gemt', 1)
cause slot won't be occupied in the moment when you caling UnitAddItemToSlotByIdHook. I think it is just fine to use call SaveItemLoc(GetUnitX(u), GetUnitY(u)) without ITE. If slot is empty and unit gets item, then event "unit pick up an item" fires and indexes this item.

edit it was wrong, you were right

Its late, I finish this tommorow, and yeah my english sux :)
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
please tell me what it is for this check if udg_IDexAutomatic then inside hook function? If you want to keep it it is better to move it outside hooks ton not repeat code.

move globals to inside library
function SaveItemLoc --> private function SaveItemLoc
UnitInventorySizeBJ(u) --> UnitInventorySize(u)

JASS:
private function UnitAddItemToSlotByIdHook takes unit u, integer i, integer slot returns nothing
        if (udg_IDexAutomatic) then
            if (UnitItemInSlotBJ(u, i) == null ) then
                call SaveItemLoc(GetUnitX(u), GetUnitY(u))
            else
                call IndexItem(UnitItemInSlotBJ(u, i))
            endif
        endif
    endfunction
-->maybe like this:
JASS:
private function UnitAddItemToSlotByIdHook takes unit u, integer i, integer slot returns nothing
        if (udg_IDexAutomatic) then
            if not (UnitItemInSlot(u, slot)==null) then // if slot is empty then event EVENT_PLAYER_UNIT_PICKUP_ITEM will fire
                call SaveItemLoc(GetUnitX(u), GetUnitY(u)) // so do it only for occupied slot
            endif         
        endif
    endfunction

JASS:
private function IndexInventories takes nothing returns nothing
        local integer i = 0
        local unit u = GetEnumUnit()
        local integer max = UnitInventorySize(u) - 1
        if (max > 0) then
            loop
                exitwhen i > max
                call IndexItem(UnitItemInSlot(u, i))
                set i = i + 1
            endloop
        endif
    endfunction
-->
JASS:
private function IndexInventories takes nothing returns nothing
        local integer i = 0
        local unit u = GetEnumUnit()
        //local integer max = UnitInventorySize(u) - 1
        //if (max > 0) then
        if UnitInventorySize(u) > 0 then
            loop
                //exitwhen i > max
                call IndexItem(UnitItemInSlot(u, i))
                set i = i + 1
                exitwhen i >= bj_MAX_INVENTORY
            endloop
        endif
        set u=null
    endfunction

private function IndexOnBuy null "i" before return false

I think it is acceptable to keep function InitTrig_Item_Indexer takes nothing returns nothing instead of library ItemIndexer initializer Init.
Because InitTrig_Item_Indexer will run after call InitGlobals() inside function main and it is OK. However if you choose library initializer - keep in mind that this library Init function will run before call InitGlobals() and will mess variables (InitGlobals() will overwrite arrays for 0 and 1: udg_IDexIsIndexed=false, udg_IDexRecycle=0, and will reset udg_IDexInstanceCount=0)
I'm curious if Moderators agree to keep InitTrig_Item_Indexer ?

you may want to place a tree and rock (with item dropped upon death) in demo map to show it works fine, as well as shop selling items on the ground. Also show users that hostile wolf not only drop item succefully indexed upon death but also when changing owner ;)
 
Last edited:
IndexInventories() should only loop through units with inventories.
please tell me what it is for this check if udg_IDexAutomatic then inside hook function? If you want to keep it it is better to move it outside hooks ton not repeat code.
when a new item is created, you cannot immediately get its custom value because of the 0.00 second timer in SaveItemLoc(). I gave users the option to manually index an item so they can have instant access to its custom value
Once again thanks for the mod!!!
 
Level 18
Joined
Nov 21, 2012
Messages
835
when a new item is created, you cannot immediately get its custom value because of the 0.00 second timer in SaveItemLoc(). I gave users the option to manually index an item so they can have instant access to its custom value
good point, but to do that it is enough:
  • Item - Create Orb of Frost at (Center of (Playable map area))
  • Custom script: call IndexItem(bj_lastCreatedItem)
  • Game - Display to (All players) for 60.00 seconds the text: (orb id + (String((Custom value of (Last created item)))))
  • -------- test UnitAddItemByIdSwapped() --------
  • Hero - Create Ring of Superiority and give it to Blood Mage 0015 <gen>
  • Custom script: call IndexItem(bj_lastCreatedItem)
  • Game - Display to (All players) for 60.00 seconds the text: (ring id + (String((Custom value of (Last created item)))))
I guess variable udg_IDexAutomatic can be deleted, it is not necessary.

you didn't tell you completely changed IndexItem :rolleyes:. Now it's using separate trigger for each item (for item death detection). In previous version was 1 trigger with multiple events. I think it is fine. But maybe someone can give us opinion what version is better /more efficient.

please null trigger t inside function IndexItem at the end

in functions UnitAddItemByIdSwappedHook UnitAddItemByIdHook do not use BJ UnitInventorySizeBJ(u) use UnitInventorySize(u) instead, already ask you for that ;)

in function IndexOnBuy null item "i" before "return false"

IndexInventories() should only loop through units with inventories.
of course, but this func is wrong coded
JASS:
private function IndexInventories takes nothing returns nothing
        local integer i = 0
        local unit u = GetEnumUnit()
        local integer max = UnitInventorySize(u) - 1 //what if unit has inventory with "1" slot?, your max == 0, next actions won't run !
        if (max > 0) then
            loop
                exitwhen i > max
                call IndexItem(UnitItemInSlot(u, i))
                set i = i + 1
            endloop
        endif
    endfunction
if you want you can use solution I gave you in previous post, or fix your version for example like that local integer max = UnitInventorySize(u) and exitwhen i == max

I made a test trigger for Jass users: how to get item's id right after creation. Maybe add example for GUI users and Jass in demo map
JASS:
function Trig_jassUser_Actions takes nothing returns nothing
    local item i
    local integer id
    local unit u = gg_unit_Hblm_0015
   
    set i = CreateItem('crys', 100.00, 150.00)
    call IndexItem(i)
    call BJDebugMsg("item id " + I2S(GetItemUserData(i)))

    call TriggerSleepAction(3.00)
   
    set i = UnitAddItemById(u, 'ciri')
    call IndexItem(i)
    call BJDebugMsg("item id " + I2S(GetItemUserData(i)))

    call TriggerSleepAction(3.00)

    if UnitAddItemToSlotById(u, 'rag1', 5) then
        call IndexItem(UnitItemInSlot(u, 5))
        call BJDebugMsg("item id in slot " + I2S(GetItemUserData(UnitItemInSlot(u, 5))))
    else
        set id = IndexItemOnTheGround(GetUnitX(u), GetUnitY(u))
        call BJDebugMsg("item id ground " + I2S(id))
    endif

endfunction
This needs new function IndexItemOnTheGround inside library added after SaveItemLoc
JASS:
function IndexItemOnTheGround takes real x, real y returns integer
            local rect r = Rect(x-256.00, y-256.00, x+256.00, y+256.00)
            call EnumItemsInRect(r, null, function IndexEnumItem)
            set r=null
            return udg_IDex // returns last indexed item's id
        endfunction

also a little more documentation would be nice, I think it is close to final, keep up good job ;)
 

Deleted member 247165

D

Deleted member 247165

Nice work!

-use ["jass"]["/jass"] without " " for the trigger code. :)
-why "Game - Display to (All players) the text: abc" and "Game - Display to (All players) the text: def" are in your code?
-in this line, if GetItemUserData(itm) == 0 then.
I think you should replace it with this if not udg_IDexIsIndexed[udg_IDexNext] then line.
udg_IDexIsIndexed[udg_IDexNext] = true after the item gets its index.

EDIT:
In my opinion, this
JASS:
function IndexItem takes item itm returns nothing
    if GetItemUserData(itm) == 0 then
        set udg_IDexItems[udg_IDexNext] = itm
        call SetItemUserData(itm, udg_IDexNext)
        call TriggerRegisterDeathEvent(gg_trg_Item_DeIndexer, itm)
        loop
            exitwhen udg_IDexItems[udg_IDexNext] == null
            set udg_IDexNext = udg_IDexNext + 1
        endloop
    endif
endfunction

should be like this

JASS:
function IndexItem takes item itm returns nothing
    local integer i = GetItemUserData(itm)

    if not udg_IDexIsIndexed[udg_IDexNext] then

        if udg_IDexRecycle[0] == 0 then
            set udg_IDexInstanceCount = udg_IDexInstanceCount + 1
            set i = udg_IDexInstanceCount
        else
            set i = udg_IDexRecycle[0]
            set udg_IDexRecycle[0] = udg_IDexRecycle[i]
        endif

        set udg_IDexItems[i] = itm
        call SetItemUserData(itm, i)
        set udg_IDexIsIndexed[i] = true

        call TriggerRegisterDeathEvent(gg_trg_Item_DeIndexer, itm)
    endif
endfunction

then add this code in Deindexer
JASS:
    set udg_IDexRecycle[itemIndex] = udg_IDexRecycle[0]
    set udg_IDexRecycle[0] = itemIndex

It just my opinion :)

It is, actually, a lot better this way.
 
Level 18
Joined
Nov 21, 2012
Messages
835
I see you implement trigger array for item death detection. You're destroying/nulling suitable trigger in "OnSold" event. Good.
However you should do the same in DeindexDestroyed:
JASS:
 //De-index items that are destroyed
    private function DeindexDestroyed takes nothing returns nothing
        local integer i = GetItemUserData(LoadItemHandle(hash, GetHandleId(GetTriggeringTrigger()), 0))
        call FlushChildHashtable(hash, GetHandleId(GetTriggeringTrigger())) //  TrigDeindexer[i]
        set udg_IDex = i
        set udg_ItemEvent = 2.00
        set udg_ItemEvent = 0.00
        call DisableTrigger( GetTriggeringTrigger() ) //  TrigDeindexer[i]
        call RemoveItem( udg_IDexItems[i] )
        call EnableTrigger( GetTriggeringTrigger() ) //  TrigDeindexer[i]
        set udg_IDexItems[i] = null
        set recycle[i] = recycle[0]
        set recycle[0] = i
        call DestroyTrigger(GetTriggeringTrigger()) //  TrigDeindexer[i] and .. set TrigDeindexer[i]=null
    endfunction
instead calling 4 times GetTriggeringTrigger()
the rest looks fine for me
 
Level 18
Joined
Nov 21, 2012
Messages
835
This System deprecates [System] Item Cleanup in my opinion. Also this thread can be updated Resources That Have Yet To Be Codedhttps://www.hiveworkshop.com/threads/resources-that-have-yet-to-be-coded.239105/

All possible ways to create items in game are supported by this library. Including GUI actions:
  • Hero - Create ITEM and give it to HERO
  • Item - Create ITEM at POINT
, also items sold by a shop as well as natives/BJ (able to create items) listed in documentation.

It supports item-drop upon death or change owner for units/destructables pre-placed in map.
It auto-cleans items like Tomes that are not removed properly by the game itself.
System offers 3 events for: item creation / item sold / item destroyed

System do not gives immediate access to item's id right after creation (as it uses 0 sec timer to index items in many cases). If item's id is needed immediately user has to index item manually after creation using IndexItem or IndexItemOnTheGround listed in API. This is the only cons system has.
Vote for approval 5/5
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
This System deprecates [System] Item Cleanup in my opinion.
It does not. Item Cleanup does only the item cleaing and doesn't rely on hooks (which are terrible).
If you only want to clean up items, it is far superior than this item indexer.

It can not. The requirement for the Item Indexer we search for in that thread is that it indexes items without a periodic loop and without the use of hooks. And to my knowledge, we haven't found a solution for that yet that doesn't use the memory hack.

Also, this is terrible:
System do not gives immediate access to item's id right after creation (as it uses 0 sec timer to index items in many cases). If item's id is needed immediately user has to index item manually after creation using IndexItem or IndexItemOnTheGround listed in API. This is the only cons system has.
Why not assign an index as soon as you first use GetItemIndex on an item when it hasn't been indexed yet? Having a seperate function for that is inconvenient design.

Oh, besides, "IndexItem" is a terrible name for the primary function as it does not imply having a return value. We use "GetUnitIndex" or "GetUnitId", so it should either be named "GetItemId" or "GetItemIndex".
 
Last edited:

SpasMaster

Hosted Project: SC
Level 23
Joined
Jan 29, 2010
Messages
1,969
My Editor crashes when trying to implement this (even in an empty map).
Deleting the "this map" folder and then trying to test results in a crash.
Copying the "Item Indexer" folder in an empty test map and then trying to test or save results in a crash.

Anyone experiencing this besides me? I use latest Warcraft 3 patch if that matters in any way.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
An intresting way to store data in items are to use call BlzSetItemTooltip(someitem, data) as it's only seen inside the shop. Don't know if it's relevant here but thought it was intresting. Might be useful if you are looking to avoid indexing items. Though you'd have to encode/decode the string rather than just looking up the index.
 
Last edited:
Top