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

[Lua] ItemHook: Create & Remove

This is a Lua resource that makes it easy to add executive Code when an item of itemCode is created or removed.
Lua:
do
    --[[
    ItemHook 1.3 by Tasyen
    requires: Global Initialization 3.0.0.1 by Bribe https://www.hiveworkshop.com/threads/global-initialization.317099/
    
    Allows to setup a function that is executed when an item of itemCode enters(created) or leaves(destroyed) the game.

    Create: preplaced, code created, creep-drops, itempool and buy items. Items created by RestoreUnit are not catched by this system.
    Destroy: sold, consumed(charges), damage or RemoveItem

    overwrites: CreateItem, UnitAddItemToSlotById, UnitAddItemById, PlaceRandomItem
    
    //How to use?
    Write in a script below ItemHook the wanted actions that should happen when an item of itemcode is created or destroyed
    When you have CreateFourCCTable in your map above ItemHook, then you can skip FourCC in the [key].

    ItemHook.Create[FourCC('bgst')] = function(item, source, hero)
        --item the item created
        --source the unit having sold the item or nil
        --hero the unit the item was created for/buyed it or nil, does not work for the GUI-Hero-Item-Create-Actions.
        action
        action
    end   

    ItemHook.Destroy[FourCC('bgst')] = function(item)
        action
    end


    if you need a general action unrelevant to itemCode you might alter the system code inside
    function ItemHook.ItemDestroyed(item, itemCode)
    function ItemHook.ItemCreated(item, source, hero)

    item is the created/sold item. source is nil or the unit selling the item.

    
    --]]
    ItemHook = {}
    if CreateFourCCTable then
        ItemHook.Create = CreateFourCCTable()
        ItemHook.Destroy = CreateFourCCTable()
    else
        ItemHook.Create = {}
        ItemHook.Destroy = {}
    end

    local function TriggerActionDestroyed()
        local trigger = GetTriggeringTrigger()
        ItemHook.ItemDestroyed(ItemHook[trigger].Item, ItemHook[trigger].ItemCode)
        --cleanup
        TriggerRemoveCondition(trigger, ItemHook[trigger].Action)
        DestroyTrigger(trigger)
        ItemHook[trigger].ItemCode = nil
        ItemHook[trigger].Item = nil
        ItemHook[trigger].Action = nil
        ItemHook[trigger].Event = nil
        ItemHook[trigger] = nil
    end

    function ItemHook.ItemCreated(item, source, hero)
        --source is nil outside of buy item Event
        --hero is a unit when the item was created for /bought by an unit
        local itemCode = GetItemTypeId(item)
        local trigger = CreateTrigger()
        --safe itemData, that is done cause inside TriggerRegisterDeathEvent reading the item was not really doable in a clean way.
        ItemHook[trigger] = {}
        ItemHook[trigger].Event = TriggerRegisterDeathEvent(trigger, item)
        ItemHook[trigger].Action = TriggerAddCondition(trigger, Condition(TriggerActionDestroyed))
        ItemHook[trigger].ItemCode = itemCode
        ItemHook[trigger].Item = item
        trigger = nil
        if ItemHook.Create[itemCode] then ItemHook.Create[itemCode](item, source, hero) end
       
	    return item
    end
    function ItemHook.ItemDestroyed(item, itemCode)
        if ItemHook.Destroy[itemCode] then ItemHook.Destroy[itemCode](item) end
    end

    local realCreateItem = CreateItem
    function CreateItem(itemid, x, y)
        return ItemHook.ItemCreated(realCreateItem(itemid, x, y))
    end

    local realUnitAddItemToSlotById = UnitAddItemToSlotById
    function UnitAddItemToSlotById(whichUnit, itemId, itemSlot)
        local value = realUnitAddItemToSlotById(whichUnit, itemId, itemSlot)
        ItemHook.ItemCreated(UnitItemInSlot(whichUnit, itemSlot), nil, whichUnit)
        return value
    end

    local realUnitAddItemById = UnitAddItemById
    function UnitAddItemById(whichUnit, itemId)
	    return ItemHook.ItemCreated(UnitAddItemById(whichUnit, itemId), nil, whichUnit)
    end

    local realPlaceRandomItem = PlaceRandomItem
    function PlaceRandomItem(whichItemPool, x, y)
	    return ItemHook.ItemCreated(realPlaceRandomItem(whichItemPool, x, y))
    end

    --handled by TriggerRegisterDeathEvent
    --local realRemoveItem = RemoveItem
    --function RemoveItem(item)
        --ItemHook.ItemDestroyed(item, GetItemTypeId(item))
	    --realRemoveItem(item)
    --end

    OnTrigInit(function()
        ItemHook[1] = {}
        ItemHook[1].Trigger = CreateTrigger()
        ItemHook[2] = {}
        ItemHook[2].Trigger = CreateTrigger()
        TriggerRegisterAnyUnitEventBJ(ItemHook[1].Trigger, EVENT_PLAYER_UNIT_SELL_ITEM)
        TriggerRegisterAnyUnitEventBJ(ItemHook[2].Trigger, EVENT_PLAYER_UNIT_PAWN_ITEM)
        
        ItemHook[1].TriggerAction = TriggerAddAction(ItemHook[1].Trigger, function()
            ItemHook.ItemCreated(GetSoldItem(), GetSellingUnit(), GetBuyingUnit())
        end)
        ItemHook[2].TriggerAction = TriggerAddAction(ItemHook[2].Trigger, function()
            ItemHook.ItemDestroyed(GetSoldItem(), GetItemTypeId(GetSoldItem()))
        end)
    end)
    
end

changeLog:
1.3)
Only one global, TriggerCondition for the Item Death EventTrigger, Removed Create After started​
1.2)
Updated Global Initialization usage, Added a Overwrites Line​
1.1)
uses now Global Initialization. => pushed much out of the Lua root.​
Added an 3 Argument for Created functions, it Points to the unit the item was created for or inside a buy item event.​
 
Last edited:
Yeah such a organized gameStarted point reduces the amount of such TimerStarts, with that the chance of that mentioned Problem. Didn't checkout Global Initialization, yet.

Edit: Updated the main post, the System now uses Global Initialization, with that trigger/event creation was pushed to a later Point.
Also added a 3. Argument to the Created Actions pointing to the unit an item was created/bought for, if such one exists.
 
Last edited:
Yes, that is how I do it in another resource which handles Units but is not uploaded (cause some Unit-creation Actions are sliping the detection, (pocket) factory (nor that spawing), Lava Spawn spliting).
Here is the hook for Restore Unit:
Lua:
    local realRestoreUnit = RestoreUnit
    function RestoreUnit(cache, missionKey, key, forWhichPlayer, x, y, facing)
        local unit = realRestoreUnit(cache, missionKey, key, forWhichPlayer, x, y, facing)
        if unit then
            --UnitCreate.action(unit)
            --check restored units inventory
            if ItemCreate.action then
                --execute for each restored item an itemCreate action
                local index = 0
                repeat
                    local item = UnitItemInSlot(unit, index)
                    if item then
                        ItemCreate.action(item)
                    end
                    index = index + 1
                until index == bj_MAX_INVENTORY
            end
        end
        return unit
    end
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I am giving this a more thorough look now, and I have the following questions/feedback:

1) why did you remove the RemoveItem hook? That makes this system break if RemoveItem is actually called on an item (e.g. via ItemCleanup).
2) could you get away with reducing the number of global variables down to 1? I think this would be definitely possible with a slight tweak to your API.
3) triggeractions, when removed, crash the thread (or at least they did in JASS) if the thread is within said triggeraction. It is preferred to use TriggerConditions for this reason (among others).
4) To make your events cleaner, I would suggest not requiring users to implement "FourCC" on their own, and just allow them to pass the string (or either, since this is Lua you can just check if the type of arg passed is an integer or a string, then process the FourCC if it's a string).
 
Updated to V1.3
The Item Death Event Trigger now uses a TriggerCondition instead of TriggerAction
All System globals are now a child of ItemHook, only one Global.
Removed ItemCreatedAfter

Action functions are now set this way:
ItemHook.Create[FourCC('bgst')] = function(item, source, hero) end
ItemHook.Destroy[FourCC('bgst')] = function(item) end

Can use CreateFourCCTable to avoid having to write FourCC as [key] for ItemHook.Create & ItemHook.Destroy

why did you remove the RemoveItem hook? That makes this system break if RemoveItem is actually called on an item (e.g. via ItemCleanup).
Because the Item Death event handles it.
 
Top