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

Inventory Code

Level 19
Joined
Mar 18, 2012
Messages
1,716
Inventory source code

JASS:
// Inventory main code. Written by BPower. Version 1.1                                           
library Inventory uses InventoryUserSetup, InventoryCore

//===================================================================================
// Inventory events. 
// When an event trigger fires these values allow
// the action code to determine which event was dispatched.
// The functions listed below can be used to get information about the event.
//===================================================================================

    globals
        //=========================================
        // Available Event Ids.
        //=========================================
                         // RegisterInventoryEvent(eventId, code)
        constant integer EVENT_INVENTORY_UNIT_EQUIP_ITEM   = 0
        constant integer EVENT_INVENTORY_UNIT_UNEQUIP_ITEM = 1
        constant integer EVENT_INVENTORY_UNIT_PICKUP_ITEM  = 2
        constant integer EVENT_INVENTORY_UNIT_DROP_ITEM    = 3
        constant integer EVENT_INVENTORY_UNIT_PAWN_ITEM    = 4
        constant integer EVENT_INVENTORY_UNIT_BUY_ITEM     = 5// ( Not working yet )    
        constant integer EVENT_INVENTORY_UNIT_TRADE_ITEM   = 6// ( Not working yet ) 
        // 
        constant integer MAX_INVENTORY_EVENTS              = 7
    endglobals
    
    globals
        //=======================================================
        // Event Variables
        //=======================================================
        // These variables update before an event fires.
        // Get their information through the set of functions below.
        private integer eventId     = -1
        private item    eventItem   = null
        private integer eventIndex  = 0
        private unit    eventTarget = null
        //
        private trigger array events
    endglobals

    //=========================================
    // Inventory trigger interface.
    //=========================================
    // Returns the trigger handle for the specified event id,
    // in order to use it with the native trigger interface.
    // Use this function very carefully.
    function GetInventoryTrigger takes integer eventId returns trigger
        return events[eventId]
    endfunction
    
    //=======================================================
    // Trigger Inventory Event API.
    //=======================================================
    // These functions only return their proper information when called
    // from within a registered trigger action function.
    // For incorrect usage they will either return "null", "-1" or "0".
    
    // Returns the triggering Inventory instance.
    constant function GetTriggerInventory takes nothing returns Inventory
        return eventIndex
    endfunction
    
    // Returns the interacting item.
    constant function Inventory_GetTriggerItem takes nothing returns item
        return eventItem
    endfunction
    
    // Returns the interacting unit handle.
    // Can be null for some event ids.
    constant function Inventory_GetEventTargetUnit takes nothing returns unit
        return eventTarget
    endfunction
    
    // Returns the most recent event id.
    constant function Inventory_GetTriggerEventId takes nothing returns integer
        return eventId
    endfunction
    
    // Returns the inventory owning unit handle.
    constant function Inventory_GetTriggerUnit takes nothing returns unit 
        return GetTriggerInventory().source
    endfunction
    
    // Registers code to available inventory events. 
    // Condition function not have to return a boolean, as the function outsmarts PJASS. 
    function RegisterInventoryEvent takes integer eventId, code func returns nothing
        local boolean PJASS = false
        call TriggerAddCondition(events[eventId], Condition(func))
        //
        debug call ThrowError(events[eventId] == null, "Inventory", "RegisterInventoryEvent", "eventId", 0, "Invalid event id [" + I2S(eventId) + "]!")
    endfunction
    
    //=======================================================
    // Event Trigger Evaluation.
    //=======================================================
    
    // Fires inventory events while providing recursion safety.
    private function FireEvent takes integer id, integer instance, item object, unit target returns nothing
        // Save previous data.
        local integer prevIndex  = eventIndex
        local integer prevId     = eventId
        local item    prevItem   = eventItem
        local unit    prevTarget = eventTarget
        // Set current data and fire.
        set eventId     = id
        set eventIndex  = instance
        set eventItem   = object
        set eventTarget = target
        if IsTriggerEnabled(events[id]) then// TriggerEvaluate does also force disabled trigger to fire.
            call TriggerEvaluate(events[id])
        endif
        // Restore previous data.
        set eventIndex  = prevIndex
        set eventItem   = prevItem
        set eventTarget = prevTarget
        set eventId     = prevId
        // Prevent handle leaks.
        set prevTarget  = null
        set prevItem    = null
    endfunction
    
    //=======================================================
    // Event Trigger Initialization.
    //=======================================================
    
    // Internal process which is called as soon as possible via module initializer.
    private function InitEventTriggers takes nothing returns nothing
        local integer index = 0
        loop
            exitwhen (index == MAX_INVENTORY_EVENTS)
            set events[index] = CreateTrigger()
            set index = index + 1
        endloop
    endfunction
    
    //=======================================================
    // Internal On Create Event Trigger.
    //=======================================================
    // This event trigger fires when a new inventory instance is created
    // and executes code in all structs using the InventoryStruct module.  
    // Use GetTriggerInventory() to get the most recent created instance.
    globals
        private constant trigger ON_CREATE = CreateTrigger()
    endglobals
    
    private function FireOnCreate takes integer index returns nothing
        local integer prev = eventIndex
        set eventIndex = index
        call TriggerEvaluate(ON_CREATE)
        set eventIndex = prev
    endfunction
    
    //=======================================================
    // Module InventoryStruct.
    //=======================================================
    // Private interface for structs which should run code when an Inventory is created.
    // Those structs must have a "static method onCreate takes Inventory new returns thistype".
    
    module InventoryStruct
        static method onCreateBefore takes nothing returns boolean
            local Inventory created = GetTriggerInventory()
            local boolean autostart = thistype.typeid != GetTradeTypeId() and thistype.typeid != GetMerchantTypeId()
            local thistype this = thistype.onCreate(created)
            if (0 != this) then
                call created.addWindowOfType(thistype.typeid, this, autostart)
            endif
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(ON_CREATE, Condition(function thistype.onCreateBefore))
        endmethod  
    endmodule
    
//===================================================================================
// Inventory instance referencing. 
// As an inventory belongs to a unit handle, you can get an instance
// by calling function GetUnitInventory(unit). 
//===================================================================================
    
    // Returns the Inventory instance for a unit.
    // Returns 0 on incorrect usage.
    function GetUnitInventory takes unit source returns Inventory
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(source))
    endfunction
    
//===================================================================================
// Inventory interface sounds. 
// These sound files run during various Inventory interface actions.
// For example when the backpack is full and therefore an item can't be picked up.  
//===================================================================================
    
    //=======================================================
    // Sound File Registration.
    //=======================================================
    // Which sound file should be played is race based determined.
    // Each sound file can and will be accessed via UISound.searchSoundFile(fileName)
    
    // This function is called via module initializer on map initialization.
    private function InitSoundFiles takes nothing returns nothing
        call UISound.allocateIndex("Abilities\\Spells\\Items\\ResourceItems\\ReceiveGold.wav", 589, 10, .54)
        call UISound.allocateIndex("Sound\\Interface\\QuestActivateWhat1.wav", 539, 10, .539)
        call UISound.allocateIndex("Sound\\Interface\\PickUpItem.wav", 174, 10, .174)
        call UISound.allocateIndex("Sound\\Interface\\HeroDropItem1.wav", 486, 10, .54)
        //
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntInventoryFull1.wav", 1567, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntNoLumber1.wav", 1602, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Orc\\GruntNoGold1.wav", 1498, 10, 1.)
        //
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightInventoryFull1.wav", 1498, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightNoGold1.wav", 1486, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Human\\KnightNoLumber1.wav", 1863, 10, 1.)
        //
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaInventoryFull1.wav", 2106, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaNoGold1.wav", 1808, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Naga\\NagaNoLumber1.wav", 1576, 10, 1.)
        //
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Nightelf\\SentinelInventoryFull1.wav", 1498, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\NightElf\\SentinelNoGold1.wav", 1323, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\NightElf\\SentinelNoLumber1.wav", 1501, 10, 1.)
        //
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerNoGold1.wav", 1805,  10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerNoLumber1.wav", 1904, 10, 1.)
        call UISound.allocateIndex("Sound\\Interface\\Warning\\Undead\\NecromancerInventoryFull1.wav", 1521, 10, 1.)
    endfunction
    
//===================================================================================
// Special API. 
// ( Place holder. No special API is defined yet )
//===================================================================================
    
    // NO CONTENT.
    
//===================================================================================
// InventoryCell code. 
// Handles and stores information for each 
// interactable cell within an Inventory instance. 
//===================================================================================
    
    // The textmacro is located at the very bottom of the Inventory library.
    //! runtextmacro INVENTORY_CELL_CODE("")
    
//===================================================================================
// Interface Inventory. 
// Inventory extends UIScreen and therefore doesn't have own interactable cells.
// It provides the common object orientated API for Inventory instances 
// and serves as main screen for the entire in-game user interface surface.
//===================================================================================

    struct Inventory extends UIScreen      
    
        //====================================
        // Struct members.
        //====================================
        
        readonly unit    source
        //
        readonly unit    dummy
        readonly real    dummyY
        
        readonly unit    shop
        
                         // AddUnitAnimationProperties()
        readonly string  animation
        
        // The global user setup defines a default font for all instances, 
        // you can change it at any time via this.setFont(font).
        private integer fontType             
        method getFont takes nothing returns integer
            return fontType
        endmethod
        method setFont takes integer newFont returns nothing
            set fontType = newFont
        endmethod
        
//===================================================================================
// Text messages and effects for Inventories.
// The following methods help to create a good
// ambience when using the Inventory interface in-game.
//===================================================================================
        
        //=============================================
        // Inventory sounds. ( Run for a local player)
        //=============================================
        // Based on race handle ids.
        // Reference: 1 - Human, 2 - Orc, 3 - Undead, 4 - Nightelf, 5 - Demon, 7 - Other, 11 - Naga
        //
        // API: .iHaveNoRoom(), .notEnoughLumber(), .notEnoughGold()
         
        // Runs for an insufficient backpack space.
        private method iHaveNoRoom takes nothing returns nothing
            local integer id = GetHandleId(GetUnitRace(source))
            if     (id == 1) or ((id != 11) and (id > 5)) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightInventoryFull1.wav", 10, 1.498)
            elseif (id == 2) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntInventoryFull1.wav", 10, 1.567)
            elseif (id == 3) or (id == 5) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerInventoryFull1.wav", 10, 1.521)
            elseif (id == 4) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelInventoryFull1.wav", 10, 1.498)
            elseif (id == 11) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaInventoryFull1.wav", 10, 2.106)
            endif
        endmethod
        
        // Runs for an insufficient lumber resource state.
        private method notEnoughLumber takes nothing returns nothing
            local integer id = GetHandleId(GetUnitRace(source))
            if     (id == 1) or ((id != 11) and (id > 5)) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightNoLumber1.wav", 10, 1.863)
            elseif (id == 2) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntNoLumber1.wav", 10, 1.602)
            elseif (id == 3) or (id == 5) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerNoLumber1.wav", 10, 1.904)
            elseif (id == 4) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelNoLumber1.wav", 10, 1.501)
            elseif (id == 11) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaNoLumber1.wav", 10, 1.575)
            endif
        endmethod
        
        // Runs for an insufficient gold resource state.
        private method notEnoughGold takes nothing returns nothing
            local integer id = GetHandleId(GetUnitRace(source))
            if     (id == 1) or ((id != 11) and (id > 5)) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Human\\KnightNoGold1.wav", 10, 1.486)
            elseif (id == 2) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Orc\\GruntNoGold1.wav", 10, 1.1498)
            elseif (id == 3) or (id == 5) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Undead\\NecromancerNoGold1.wav", 10, 1.904)
            elseif (id == 4) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Nightelf\\SentinelNoGold1.wav", 10, 1.323)
            elseif (id == 11) then
                call audio.searchSoundFile("Sound\\Interface\\Warning\\Naga\\NagaNoGold1.wav", 10, 1.808)
            endif
        endmethod
        
        //====================================
        // Custom Player Text Message.
        //====================================
        // Similar to a player text message, but it doesn't delete the current chat on screen.
        //
        // API: .timedMsg(msg, duration), .errorMsg(msg)
        
        private real      spam
        private real      lastMsgTime
        private textsplat lastMsgObject
        
        // Called upon hiding an inventory interface or when a new message is generated.
        private method hideLastMsg takes nothing returns nothing
            if (UI_GetElapsedTime() < lastMsgTime) then
                call SetTextSplatColor(lastMsgObject, 0, 0, 0, 0)
                call SetTextSplatLifespan(lastMsgObject, 0.)
            endif            
        endmethod
        
        method timedMsg takes string msg, real duration returns nothing
            local real elapsed = UI_GetElapsedTime()
            local ARGB color = Inventory_GetMsgFontColor()
            local textsplat t
            if (elapsed >= spam) then
                call hideLastMsg()// Let the previous text fade out invisible.
                //
                set spam = elapsed + .05// Spam protection.
                set lastMsgTime = elapsed + duration
                //
                set t = CreateTextSplat(fontType)
                call SetTextSplatFadepoint(t, duration - 1.)
                call SetTextSplatLifespan(t, duration)
                call SetTextSplatPermanent(t, false)
                call SetTextSplatText(t, msg, INVENTORY_FONT_SIZE)  
                call SetTextSplatVisibility(t, enabled and GetLocalClient() == user)
                call SetTextSplatColor(t, color.red, color.green, color.blue, color.alpha)
                                                                        // Y position determined by try and error.
                call SetTextSplatPos(t, centerX - t.width*.5, originY + height*.036, 0.)
                set lastMsgObject = t
            endif             
        endmethod
        
        method errorMsg takes string msg returns boolean
            call timedMsg(msg, 2.)
            call audio.error()
            return false
        endmethod
        
        //====================================
        // Custom Player Text Box & Message.
        //====================================
        // This should definitly be outsourced to the UIPackage, but
        // currently the API is not methodologically sound enough to work in any UI.
        // The textbox is a bit more hardcoded than the rest. Get over it!
        //
        // API: .createTextBox(msg, posX, posY, width), .releaseTextBox()
        
        private textsplat splat
        private UIBorder  box
        private timer     tmr
        
        method releaseTextBox takes nothing returns nothing
            local integer dex = 0
            if (tmr != null) then
                call ReleaseTimer(tmr)
                call box.clear()
                set tmr = null
                call splat.unlock()
            endif
        endmethod
       
        private static method fadeBox takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local textsplat t = splat
            local real val 
            if (t.age >= t.fadepoint) then
                set val = 1. - (t.age - t.fadepoint)/(t.lifespan - t.fadepoint) 
                if (val > 0.) then
                    call box.setColor(255, 255, 255, R2I(255*val))
                else
                    call releaseTextBox()
                endif
            endif
        endmethod  
        
        method createTextBox takes string msg, real posX, real posY, real width returns nothing
            local integer dex = 1
            local ARGB color  = Inventory_GetMsgFontColor()
            local textsplat t = CreateTextSplat(fontType)
            local integer end
            // Free previous timer, textsplat and box.
            call releaseTextBox()
            // Prepare the text.
            call WordWrap.create(msg, width, false)
            set end = WordWrap.getCount()
            set msg = WordWrap.getLine(0)
            loop
                exitwhen (dex >= end)
                set msg = msg + "\n" + WordWrap.getLine(dex)
                set dex = dex + 1
            endloop
            // TextSplat API.
            call SetTextSplatColor(t, color.red, color.green, color.blue, color.alpha)
            call SetTextSplatText(t, msg, 4.146479*1.5*scale)
            call SetTextSplatPos(t, posX - t.width*.5, posY - t.height*.5, 0.) 
            call SetTextSplatVisibility(t, GetLocalClient() == user)
            call SetTextSplatPermanent(t, false)
            call SetTextSplatFadepoint(t, 2.)
            call SetTextSplatLifespan(t, 4.)
            call t.lock()// Prevent double free.
            set splat = t
            // Create a box with extra 32 units in both dimensions.
            call Inventory_SetTooltipBoxBorderFields(source)
            call box.construct(posX, posY, t.width + tileSize*.5, t.height + tileSize*.5, .65*scale).show(enabled and GetLocalClient() == user)
            set tmr = NewTimerEx(this)
            call TimerStart(tmr, .031250000, true, function thistype.fadeBox)
        endmethod
        
        //====================================
        // Custom Item Tooltip.
        //====================================
        // The tooltip is looked up in library CustomItemTooltip
        // and displayed line by line in a multiboard for a local player.
        //
        // API: .showItemTooltip(item)
        
        private method displayTooltip takes item i returns nothing
            local CustomItem id = GetHandleId(i)
            local string  text = id.getName()
            local integer gold = id.getGoldCost()
            local integer wood = id.getWoodCost()
            local integer end  = GetItemTooltipSize(id)
            local integer dex  = end
            local string  icon
            local UIBoard mb
            // Check for item charges.
            if (GetItemCharges(i) != 0) then
                set text = text + " [" + I2S(GetItemCharges(i)) + "]"
            endif
            //
            // Check for item resource costs. 
            if (gold != 0) and (wood != 0) then
                set dex = dex + 3
            elseif (wood != 0) or (gold != 0) then
                set dex = dex + 2
            endif
            //
            // Create the board with dex rows.
            set mb  = board.new(dex, 1)
            set dex = 0
            call MultiboardSetItemsWidth(mb.board, .16)
            call MultiboardSetTitleText(mb.board, text)
            //
            loop
                // Import the tooltip for "id" line by line.
                exitwhen (dex == end)
                set text = GetItemTooltipFragment(id, dex)
                set icon = GetItemTooltipFragmentIcon(id, dex)
                call mb.setText(dex, 0, text)
                call mb.setIcon(dex, 0, icon)
                call mb.setStyle(dex, 0, text != null, icon != null)
                set dex = dex + 1
            endloop
            // 
            // Add extra lines for resource tooltips in the end.
            if (gold != 0) then
                set dex = dex + 1
                call mb.setText(dex, 0, "Sell Value: " + I2S(gold))
                call mb.setIcon(dex, 0, INVENTORY_RESOURCE_GOLD_ICON)
                call mb.setStyle(dex, 0, true, true)                
            endif
            if (wood != 0) then
                set dex = dex + 1
                call mb.setText(dex, 0, "Sell Value: " + I2S(wood))
                call mb.setIcon(dex, 0, INVENTORY_RESOURCE_WOOD_ICON)
                call mb.setStyle(dex, 0, true, true)
            endif
            // 
            // And finally show it to the player.
            call MultiboardMinimize(mb.board, false)
        endmethod
        
        method showItemTooltip takes item i returns boolean
            if (i == null) then
                call board.release()
                return false
            endif
            call displayTooltip(i)
            return true
        endmethod
        
        //====================================
        // Item Fxs & Animation Properties.
        //====================================
        // These informations update when equipping or unequipping an item.
        // Run for the source unit and for the dummy ( if a dummy is set ).
        //
        // API: .addFx(item), .removeFx(item)
        
        private Table effects
        method removeFx takes item i returns nothing
            local integer itemId = GetItemTypeId(i)
            local integer id     = GetCustomItemFxAbilityId(itemId)
            local string  ani    = GetCustomItemAnimationProperty(itemId) 
            //
            // Check for added effect handles.
            if effects.has(itemId) then
                set effects[itemId] = effects[itemId] - 1
                if (effects[itemId] <= 0) then
                    call DestroyEffect(effects.effect[itemId])
                    call effects.handle.remove(itemId)
                    call effects.remove(itemId)
                    // Check for the dummy portrait.
                    if (dummy != null) then
                        call DestroyEffect(effects.effect[-itemId])
                        call effects.handle.remove(-itemId)
                    endif
                endif
            endif
            //
            // Check for added abilities to display a complex fx.
            if effects.has(-itemId) then
                set effects[-itemId] = effects[-itemId] - 1
                if (effects[-itemId] <= 0) then
                    call effects.remove(itemId)
                    call UnitRemoveAbility(source, id)
                    if dummy != null then
                        call UnitRemoveAbility(dummy, id)
                    endif
                endif
            endif
            //
            // Check for the animation properties. 
            // Uses the "real" table field, because I'm running out of integers child keys :)!
            if effects.real.has(itemId) then
                set effects.real[itemId] = effects.real[itemId] - 1
                if (effects.real[itemId] <= 0.5) then
                    call effects.real.remove(itemId)
                    call AddUnitAnimationProperties(source, ani, false)
                    if dummy != null then
                        call AddUnitAnimationProperties(dummy, ani, false)
                    endif
                endif
            endif
        endmethod
        
        method addFx takes item i returns nothing
            local integer itemId = GetItemTypeId(i)
            local integer id     = GetCustomItemFxAbilityId(itemId)
            local string  pos    = GetCustomItemFxPos(itemId)
            local string  ani    = GetCustomItemAnimationProperty(itemId)
            local string  path   = GetCustomItemFxFilePath(itemId)
            //
            // Effects via string path.
            if (path != "") and (path != null) then
                if not effects.has(itemId) then
                    if (GetLocalClient() != user) then
                        set path = ""
                    endif
                    set effects.effect[itemId] = AddSpecialEffectTarget(path, source, pos)
                    if (dummy != null) then
                        set effects.effect[-itemId] = AddSpecialEffectTarget(path, dummy, pos)
                    endif
                endif
                set effects[itemId] = effects[itemId] + 1
            endif
            //
            // Animation properties.
            if (ani != "") and (ani != null) then
                if not effects.real.has(itemId) then
                    set animation = ani
                    call AddUnitAnimationProperties(dummy, ani, true)
                    if (dummy != null) then
                        call AddUnitAnimationProperties(source, ani, true)
                    endif
                endif
                set effects.real[itemId] = effects.real[itemId] + 1
            endif
            //
            // Effects via ability.
            if (id != 0) then
                if not effects.has(-itemId) and UnitAddAbility(source, id) then
                    call UnitMakeAbilityPermanent(source, true, id)
                    if (dummy != null) then
                        call UnitAddAbility(dummy, id)
                        call UnitMakeAbilityPermanent(dummy, true, id)
                    endif
                endif
                set effects[-itemId] = effects[-itemId] + 1
            endif
        endmethod
        

//===================================================================================
// Core API for items and units.
// It's as close as possible to the 
// native API for unit and item objects.
//===================================================================================
        
        //====================================
        // Cell Selection.
        //====================================
        // Basically always called when clicking a trackable in the entire interface.
        
        readonly InventoryCell selected
        readonly destructable  selector        
        method deselect takes nothing returns nothing
            if (selector != null) then
                call RemoveDestructable(selector)
                set selector = null
            endif
            set selected = 0
        endmethod
        
        method select takes InventoryCell cell returns nothing
            local UIButton temp = cell.getButton()
            call deselect()
            set selected = cell
            set selector = CreateDestructableZ(INVENTORY_CELL_SELECTOR_ID, temp.x, temp.y, temp.z, temp.facing, temp.scale*INVENTORY_CELL_SELECTOR_SCALE*scale, 0)
            call ShowDestructable(selector, enabled and GetLocalClient() == user)
            //
            debug call ThrowWarning(not cell.exists, "Inventory", "select", "cell", cell, "Attempt to select invalid cell instance!")
        endmethod
        
        //====================================
        // Sync With The Native Inventory.
        //====================================
        // Doesn't have much meaning so far as the native to custom inventory support is very low implemented.
        
        private boolean syncReady
        method syncWithNative takes nothing returns nothing
            local UIWindow window = getWindowOfType(GetNativeInventoryTypeId())
            local integer  size   = IMinBJ(UnitInventorySize(source), window.getButtonCount())
            local integer  index  = 0
            local item     temp   
            local InventoryCell cell
            //
            set syncReady = false
            loop 
                exitwhen (index == size)
                set temp = UnitItemInSlot(source, index)
                set cell = window.getButton(index).data
                set cell.enabled = IsCustomItem(temp) or (temp == null)
                if (temp == null) then
                    call cell.clear()
                else
                    call cell.moveItem(temp, enabled)
                endif
                set index = index + 1
            endloop
            set temp      = null
            set syncReady = true
        endmethod

        //====================================
        // Shop Detection.
        //====================================
        
        private method detectShops takes nothing returns nothing
            local real dist = INVENTORY_MAXIMUM_SHOP_RANGE*INVENTORY_MAXIMUM_SHOP_RANGE + 1.
            local real posX = GetUnitX(source)
            local real posY = GetUnitY(source)
            local real tx
            local real ty
            local real td 
            local unit u 
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, posX, posY, INVENTORY_MAXIMUM_SHOP_RANGE, null)
            loop
                set u = FirstOfGroup(bj_lastCreatedGroup)
                exitwhen u == null
                call GroupRemoveUnit(bj_lastCreatedGroup, u)
                if IsUnitAlly(u, user) and (GetUnitAbilityLevel(u, 'Apit') != 0) then
                    set tx = posX - GetUnitX(u)
                    set ty = posY - GetUnitY(u)
                    set td = tx*tx + ty*ty
                    if (td < dist) then
                        set shop = u
                    endif
                endif
            endloop
            if (shop != null) then
                call getWindowOfType(GetMerchantTypeId()).show(true)
            endif
        endmethod
        
        
        //====================================
        // On Show.
        //====================================
        // Inventory is a child struct of UIScreen, therefore it executes an onShow stub method
        // each time instance.show(boolean) is called.
        
        
        
        private method onShow takes boolean flag returns nothing
            set shop = null
            if (flag) then
               call syncWithNative()
                if hasWindowOfType(GetMerchantTypeId()) then
                    call detectShops()
                endif
            else
                call releaseTextBox()
                call hideLastMsg()
            endif
        endmethod
        
        //====================================
        // Searching Cells Of Struct Type.
        //====================================
        // This function helps us to find empty cells for an item 
        // inside a specific struct interface.
        
        // Returns 0 if no matching cell was found.
        private method searchCell takes integer structId, item i returns InventoryCell
            local UIWindow window = getWindowOfType(structId)
            local integer  end = window.getButtonCount()
            local integer  dex = 0 
            local InventoryCell cell  
            loop
                exitwhen (dex == end)
                set cell = window.getButton(dex).data
                if (cell.enabled) and (cell.getItem() == null) and (cell.matchesItem(i)) then
                    return cell
                endif
                set dex = dex + 1
            endloop
            return 0
        endmethod
        
        // This function does the same as searchCell, but only for the equipment interface.
        // It has a much, much better performance, which is essential for equipment related API ( see below ).
        //
        private Table equipment                              // For "true" the function considers cells which are not empty.             
        private method searchEquipCell takes item whichItem, boolean forceAction returns InventoryCell
            local integer pos = GetCustomItemClass(whichItem) + 1
            local integer end = equipment[-pos]
            local integer dex = 0
            local integer forced = 0
            local InventoryCell cell
            //*
            set pos = pos*JASS_MAX_ARRAY_SIZE
            loop
                exitwhen (dex == end)
                set cell = equipment[pos + dex]
                if (cell.enabled) then
                    if (cell.getItem() == null) then
                        return cell
                    endif
                    set forced = cell
                endif
                set dex = dex + 1
            endloop
            if (forceAction) then
                return forced
            endif
            return 0
        endmethod
        
        // Internal function wrapper to search an suit-able cell for an item. 
        private method searchCellForItem takes UIWindow structId, item i, boolean forceAction returns InventoryCell
            debug call ThrowWarning(i == null, "Inventory", "searchCellForItem", "object", this, "Invalid item handle ( null )!")
            //
            if not hasWindowOfType(structId) then
                return 0
            // Equipment has an individual lookup algorithm for better performance. 
            elseif (structId == GetEquipmentTypeId()) then
                return searchEquipCell(i, forceAction)
            endif
            return searchCell(structId, i)
        endmethod
        
        //====================================
        // Swapping Cell Content.
        //====================================
        // Works only for cells of the same struct.
        
        method swapCellContents takes InventoryCell a, InventoryCell b returns boolean
            if (a.getTypeId() == b.getTypeId()) and (b.enabled) and (a.enabled) then
                if ((a.getItem() == null) or b.matchesItem(a.getItem())) and ((b.getItem() == null) or a.matchesItem(b.getItem())) then
                    call a.swap(b, enabled)
                    return true
                endif
            endif
            //
            debug call ThrowWarning(not a.exists, "Inventory", "swapCellContents", "a", a, "Attempt to swap to an invalid cell instance!")
            debug call ThrowWarning(not b.exists, "Inventory", "swapCellContents", "b", b, "Attempt to swap to an invalid cell instance!")
            return false
        endmethod
        
        //====================================
        // Merging Item Charges.
        //====================================
        // REQUIRES AN UPDATE.
            
        private method checkStructItemCharges takes item whichItem, integer structId returns boolean
            local CustomItem    object  = GetHandleId(whichItem)
            local InventoryCell source  = object.getCell()
            local UIWindow      window  = getWindowOfType(structId)
            local integer       size    = window.getButtonCount()
            local integer       index   = 0
            local InventoryCell cell
            if (GetItemType(whichItem) != ITEM_TYPE_CHARGED) or not (source.exists) then
                return false
            endif
            loop
                exitwhen (index == size) 
                set cell = window.getButton(index).data
                if (cell.data.getItemId() == GetItemTypeId(whichItem)) and (source != cell) and (cell.enabled) then
                    exitwhen cell.data.mergeCharges(whichItem)
                endif
                set index = index + 1
            endloop
            
            return not (object.exists)
        endmethod
        
        //===============================
        // Equipment realted API. 
        //===============================
        
        // For a specific item handle.
        method unitHasItemEquipped takes item whichItem returns boolean
            local integer pos = GetCustomItemClass(whichItem) + 1
            local integer end = equipment[-pos]
            local integer dex = 0
            set pos = pos*JASS_MAX_ARRAY_SIZE
            loop
                exitwhen (dex == end) 
                if (InventoryCell(equipment[pos + dex]).getItem() == whichItem) then
                    return true
                endif
                set dex = dex + 1
            endloop
            return false
        endmethod
        
        // For an item of type id.
        method unitHasItemIdEquipped takes integer itemId returns boolean
            local integer pos = GetCustomItemIdClass(itemId) + 1
            local integer end = equipment[-pos]
            local integer dex = 0
            set pos = pos*JASS_MAX_ARRAY_SIZE
            loop
                exitwhen (dex == end) 
                if (InventoryCell(equipment[pos + dex]).data.getItemId() == itemId) then
                    return true
                endif
                set dex = dex + 1
            endloop
            return false
        endmethod
        
        //===========================================
        // Mimic Native Unit Item API & Specific API
        //===========================================
        //
            // Any interface related.
        // .unitRemoveItem(item) - UnitRemoveItem(unit, item)
        
            // Equipment related. 
        // .unitUnequipItemToSlot(item, slot)
        // .method unitEquipItemToSlot(item, slot, forceAction)
        // .method unitEquipItem(item)
        
            // Backpack related.
        // .method unitAddItemToSlot(item, slot)
        // .method unitAddItem(item)
        
            // Merchant related.
        // .method unitPawnItem(item, shop)
        // .method unitBuyItem(item, shop)             ( Not implemented yet )
        
            // Player player interaction related.
        // .method unitTradeItem(item, otherInventory) ( Not implemented yet )

            // Item related 
        // .method mergeItems(item1, item2) 
        // .method socketItem(item, gem)  ( Not implemented yet )
        // .method unsocketItem(item)     ( Not implemented yet )
        
        method unitRemoveItem takes item whichItem returns boolean
            local CustomItem object  = GetHandleId(whichItem)
            local InventoryCell cell = object.getCell()
            
            // Check cell and item owner.
            if (cell.exists) and ((object.getOwner()) == source) then
                // Check if the item is equipped.
                if (cell.getTypeId() == GetEquipmentTypeId()) then
                    // Check for twohand item.
                    if IsCustomItemTwohand(whichItem) then
                        call InventoryCell.removeShadow(whichItem)
                    endif
                    call FireEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, this, whichItem, null)
                endif
                call cell.clear()
                //
                // Place and fire.
                call object.placeInMap(GetUnitX(source), GetUnitY(source))
                call FireEvent(EVENT_INVENTORY_UNIT_DROP_ITEM, this, whichItem, null)
                return true
            endif
            return false
        endmethod
    
        method unitUnequipItemToSlot takes item whichItem, InventoryCell slot returns boolean
            local CustomItem object  = GetHandleId(whichItem)
            local InventoryCell cell = object.getCell()

            // Invalid operation.
            if ((object.getOwner()) != source) or (cell.getTypeId() != GetEquipmentTypeId()) or (whichItem == null) then
                return false
            endif
            // slot == 0, Try again.
            if (slot == 0) then
                set slot = searchCellForItem(GetBackpackTypeId(), whichItem, false)
                if (slot == 0) then
                    return unitRemoveItem(whichItem)
                endif
            endif
            // Check if it's a twohand item.
            if IsCustomItemTwohand(whichItem) then
                call InventoryCell.removeShadow(whichItem)
            endif
            // Move and fire.
            call slot.moveItem(whichItem, enabled)
            call FireEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, this, whichItem, null)
            return true
        endmethod
        
        // Equips items to the unit.  
        method unitEquipItemToSlot takes item whichItem, InventoryCell slot, boolean forceAction returns boolean
            local InventoryCell cell 
            local CustomItem object = GetHandleId(whichItem)

            // Invalid operation.
            if (slot.getTypeId() != GetEquipmentTypeId()) or (object.getOwner() != source) then
                debug call ThrowWarning((object.getOwner() != source), "Inventory", "unitEquipItemToSlot", "owner", this, "Can't equip an item of different owners!")
                return false
            //
            // Missing requirements.
            elseif not equipment.has(-(GetCustomItemClass(whichItem) + 1)) then
                return errorMsg(GetUnitName(source) + ": I can not wear " + object.getName() + "!")
            //
            // Invalid cell.
            elseif not slot.matchesItem(whichItem) then
                return errorMsg(object.getName() + " does not fit in a " + slot.class.name  + " slot!")
            endif
        
            // Check if it's a twohand item.
            if (IsCustomItemTwohand(whichItem)) then
                set slot.enabled = false
                set cell = searchCellForItem(GetEquipmentTypeId(), whichItem, forceAction)
                set slot.enabled = true
                if (cell == 0) then
                    return false
                endif
                // Check cell content.
                if (cell.getItem() != null) then
                    call unitUnequipItemToSlot(cell.getItem(), searchCellForItem(GetBackpackTypeId(), cell.getItem(), false))
                endif
                call cell.addShadow(whichItem, enabled)
            endif
            
            set cell = object.getCell()// Get the source cell.
            call unitUnequipItemToSlot(slot.getItem(), cell)
            // Move and fire.
            call slot.moveItem(whichItem, enabled)
            call FireEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM, this, whichItem, null)
            return true
        endmethod
        
        // Does not force the equip process.
        method unitEquipItem takes item whichItem returns boolean
            return unitEquipItemToSlot(whichItem, searchCellForItem(GetEquipmentTypeId(), whichItem, false), false)
        endmethod

        // Only accepts Backpack cells as slot.
        method unitAddItemToSlot takes item whichItem, InventoryCell slot returns boolean
            local thistype   temp
            local CustomItem object
            
            // Invalid operation. Only happens if you screwed up something.
            if (slot.getTypeId() != GetBackpackTypeId()) then
                return false
            endif
            
            // Check item.
            if (IsCustomItem(whichItem)) and not IsItemPurchasable(whichItem) then
                // Check slot.
                if (slot.enabled) and (slot.getItem() == null) and slot.matchesItem(whichItem) then
                    //
                    // Prepare item. 
                    // Analogous to UnitAddItem this method must work,
                    // if the owner is not this.source.
                    set object = GetHandleId(whichItem)
                    set temp   = GetUnitInventory(object.getOwner())
                    if (temp != 0) and (object.getCell() != 0) then
                        call temp.unitRemoveItem(whichItem)// Fires events for temp.
                    endif
                    // Move and fire.
                    call slot.moveItem(whichItem, enabled)
                    call FireEvent(EVENT_INVENTORY_UNIT_PICKUP_ITEM, this, whichItem, source)
                    return true
                endif
            endif
            //
            debug call ThrowWarning(not IsCustomItem(whichItem), "Inventory", "unitAddItemToSlot", "whichItem", this, GetItemName(whichItem) + " is not registered as CustomItem!")
            return false
        endmethod

        // Returns false, if no backpack cell is available.
        method unitAddItem takes item whichItem returns boolean
            return unitAddItemToSlot(whichItem, searchCellForItem(GetBackpackTypeId(), whichItem, false))
        endmethod

        // UNDER CONSTRUCTION.
        method unitPawnItem takes item whichItem, unit shop returns boolean
            local CustomItem object = GetHandleId(whichItem)
            // Check pawn condition.
            if (object.isItemPawnPossible(source, shop)) then
                // Fire other events.
                call unitRemoveItem(whichItem)
                // Pawn item and fire.
                call object.pawnItem(source)
                call FireEvent(EVENT_INVENTORY_UNIT_PAWN_ITEM, this, whichItem, shop)
                // No one took the item within the event. Remove it.
                if (object.getOwner() == null) then
                    call RemoveItem(whichItem)
                endif
                return true
            endif
            return false
        endmethod
        
        // UNDER CONSTRUCTION.
        method unitBuyItem takes item whichItem, unit shop returns boolean
            local CustomItem object = GetHandleId(whichItem)
            // Check buy condition.
            if (object.isItemBuyPossible(source, shop)) then
                call FireEvent(EVENT_INVENTORY_UNIT_BUY_ITEM, this, whichItem, shop)
                call unitAddItem(whichItem)
                return true
            endif
            return false
        endmethod

        // UNDER CONSTRUCTION.
        method unitTradeItem takes item whichItem, thistype partner returns boolean
            return false
        endmethod
        
        // UNDER CONSTRUCTION.
        method socketItem takes item whichItem, item gem returns boolean
            return false
        endmethod
        
        // UNDER CONSTRUCTION.
        method unsocketItem takes item whichItem returns boolean
            return false
        endmethod
        
        method mergeItems takes item target, item source returns boolean
            if (GetItemType(target) == ITEM_TYPE_CHARGED) and (GetItemTypeId(source) == GetItemTypeId(target)) then
                call CustomItem[target].mergeCharges(source)
                return true
            endif
            return false
        endmethod
        
        //===========================================
        // Response To Native Unit Item Events.
        //===========================================
        // In order to maintain a proper order of event evaluation,
        // each native item event is catched in library CustomItem
        // and afterwards passed to library Inventory.
        
        // Sync the custom with the native inventory. Not very much supported feature.
        private static method sync takes nothing returns nothing
            static if LIBRARY_TimerUtilsEx then
                call thistype(ReleaseTimer(GetExpiredTimer())).syncWithNative()
            else
                local thistype this = GetTimerData(GetExpiredTimer())
                call ReleaseTimer(GetExpiredTimer())
                call syncWithNative()
            endif
        endmethod
        
        // EVENT_PLAYER_UNIT_PICKUP_ITEM.
        static method onPickUpItem takes nothing returns nothing
            local item picked = GetManipulatedItem()
            local thistype this = GetUnitInventory(GetTriggerUnit())
            if (exists) then
                if IsCustomItem(picked) and not IsItemPurchasable(picked) then
                    if hasWindowOfType(GetBackpackTypeId())  then
                        if not unitAddItem(picked) then
                            call iHaveNoRoom()
                            call UnitRemoveItem(source, picked)
                        endif
                    elseif hasWindowOfType(GetEquipmentTypeId()) then
                        if not unitEquipItem(picked) then
                            call iHaveNoRoom()
                            call UnitRemoveItem(source, picked)
                        endif
                    endif
                elseif (syncReady) then
                    call syncWithNative()
                endif
            endif
            set picked = null
        endmethod

        // EVENT_PLAYER_UNIT_DROP_ITEM.
        static method onDropItem takes nothing returns nothing
            local thistype this = GetUnitInventory(GetTriggerUnit())
            if (exists) and (syncReady) then
                set syncReady = false
                call TimerStart(NewTimerEx(this), 0, false, function thistype.sync)
            endif
        endmethod

        // EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER.
        static method onTargetOrder takes nothing returns nothing
            local thistype this = GetUnitInventory(GetTriggerUnit())
            if (exists) and (syncReady) then
                set syncReady = false
                call TimerStart(NewTimerEx(this), 0, false, function thistype.sync)
            endif
        endmethod

        //=============================================
        // Internal Response To Custom Events.
        //=============================================
        
        private static method equip takes nothing returns nothing
            local thistype this = GetTriggerInventory()
            local item     eventItem = Inventory_GetTriggerItem() 
            call AddUnitItemAffixes(source, eventItem)
            call addFx(eventItem)
            set eventItem = null
        endmethod
        
        private static method unequip takes nothing returns nothing
            local thistype this = GetTriggerInventory()
            local item     eventItem = Inventory_GetTriggerItem() 
            call RemoveUnitItemAffixes(source, eventItem)
            call removeFx(eventItem)
            set eventItem = null
        endmethod
        
        //=============================================
        // Inventory Creator, Destructor & Related API.
        //=============================================
        
        // Must be called from each interface struct, which should have interactable cells.
        method initInventoryCells takes UIWindow window returns nothing
            local integer  index = 0
            local integer  size  = window.getButtonCount()
            loop
                exitwhen (index == size)
                call InventoryCell.create(window.getButton(index))
                set index = index + 1
            endloop
        endmethod
    
        // Re-structures the equipment interface.
        // Boosts the lookup time drastically for useful Equipment related API.
        // JASS_MAX_ARRAY_SIZE is used as offset between two classes.
        private static integer tempInventory = 0
        private static method structure takes nothing returns nothing
            local thistype      this = thistype.tempInventory
            local integer       index 
            local integer       child
            local integer       size  
            local integer       space 
            local UIWindow      window      
            local ItemClass     class
            local InventoryCell cell
            if not (hasWindowOfType(GetEquipmentTypeId())) then
                return
            endif
            set index     = 0
            set equipment = Table.create()
            set window    = getWindowOfType(GetEquipmentTypeId())
            set size      = window.getButtonCount()
            loop 
                exitwhen (index == size)
                set cell  = window.getButton(index).data
                set class = cell.class
                loop
                    set child = class + 1
                    set space = equipment[-child]
                    set equipment[child*JASS_MAX_ARRAY_SIZE + space] = cell
                    set equipment[-child] = space + 1 
                    set class = class.parent
                    exitwhen class.root
                endloop
                set index = index + 1
            endloop
        endmethod

        // Users should not use these methods, because only the dedicated wrapper function check for misuse.
        // Go with CreateInventory(), DestroyInventory(), AddUnitInventoryDummy(). I also do so.
        method createDummyPortrait takes integer dummyId, real extraY returns nothing
            debug local string error = "Failed to create a dummy for " + GetUnitName(source) + " from unit type id ['" + A2S(dummyId) + "']!"
            //
            local boolean prev = ToogleUnitIndexer(false)
            local unit    temp = CreateUnit(user, dummyId, WorldBounds.maxX, WorldBounds.maxY, 1.)
            call ToogleUnitIndexer(prev)
            call SetUnitFacing(temp, 0.)
            call PauseUnit(temp, true)
            call ShowUnit(temp, false)
            call UnitAddAbility(temp, 'Amrf')
            call UnitAddAbility(temp, 'Aloc')
            call UnitAddAbility(temp, 'Abun')
            if (GetLocalClient() != user) then
                call SetUnitVertexColor(temp, 0, 0, 0, 0)
            endif
            set dummyY = extraY
            set dummy  = temp
            set temp   = null   
            //
            debug call ThrowWarning((GetUnitTypeId(dummy) == 0), "Inventory", "createDummyPortrait", "dummy", this, error)
        endmethod
        
        // Actual creator function. Use function wrapper CreateInventory().
        static method construct takes unit sourceUnit, real originX, real originY, real width, real height returns thistype
            local thistype this = thistype.create(GetOwningPlayer(sourceUnit), originX, originY, width, height)
            set source          = sourceUnit
            set fontType        = Inventory_GetDefaultFont()
            set syncReady       = true
            set box             = UIBorder.create()
            set effects         = Table.create()
            //
            static if LIBRARY_ItemPower then
                if not (UnitUsesItemPower(sourceUnit)) then
                    call CreateUnitItemPower(sourceUnit)
                endif
            endif
            //
            // Reference the inventory instance.
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(sourceUnit), this) 
            //
            // Init all interfaces for Inventory.
            call FireOnCreate(this)   
            //
            // Restructure all equipment cells. ForForce to avoid hitting an OP limit.
            set tempInventory = this
            call ForForce(bj_FORCE_PLAYER[0], function thistype.structure)
            return this
        endmethod
        
        // onDestroy has an inappropriate timing for Inventory,
        // because it runs before the window destructor.
        //
        // The inventory instance is reference on the unit handle id.
        // We release the handle last, so all windows can be destroyed properly.
        method pastDestroy takes nothing returns nothing
            call RemoveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(source))
            call effects.destroy()
            call equipment.destroy()
            call RemoveUnit(dummy)
            call box.destroy()
            call deselect()
            set dummy  = null
            set source = null
        endmethod
        
        private static method init takes nothing returns nothing
            call InitEventTriggers()
            call InitSoundFiles()
            call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM, function thistype.equip)
            call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, function thistype.unequip)
        endmethod
        implement UIInit
        
    endstruct
    
//===================================================================================
// Wrapper functions for safe Inventory API.
// Please always use these functions below.
//===================================================================================
    
    function CreateInventory takes unit source, real originX, real originY returns Inventory
        local boolean  valid = (GetUnitTypeId(source) != 0) and (GetUnitInventory(source) == 0)
        if (valid) then
            return Inventory.construct(source, originX, originY, 1500, 1500/UI_PERFECT_SCREEN_RATIO)
        endif
        //
        debug call ThrowWarning((GetUnitInventory(source) != 0), "Inventory", "CreateInventory", "", 0, GetUnitName(source) + " has already an inventory!")
        debug call ThrowWarning((GetUnitTypeId(source) == 0),    "Inventory", "CreateInventory", "", 0, "Invalid unit argument ( null )!") 
        return 0
    endfunction
    
    function AddUnitInventoryDummy takes unit source, integer dummyId, real dummyY returns nothing
        local Inventory instance = GetUnitInventory(source)
        local boolean   valid    = (instance.exists) and (instance.dummy == null)
        if (valid) then
            call instance.createDummyPortrait(dummyId, dummyY)
        endif
        //
        debug call ThrowWarning((not instance.exists),           "Inventory", "AddUnitInventoryDummy", "",      0,        GetUnitName(source) + " has no inventory!")
        debug call ThrowWarning((instance.exists and not valid), "Inventory", "AddUnitInventoryDummy", "dummy", instance, "This inventory has already a dummy unit!")
    endfunction
    
    function DestroyInventory takes unit source returns nothing
        local Inventory instance = GetUnitInventory(source)
        if (instance.exists) then
            call instance.destroy()// Hides and destroys all interface windows.
            call instance.pastDestroy()//  
        //
        debug else
            debug call ThrowWarning(true, "Inventory", "DestroyInventory", "", 0, GetUnitName(source) + " has no inventory!")        
        endif
    endfunction
    
//==========================================================================================
// Struct InventoryCell handles each interactable cell in Inventory.
// It's limited to 8910 instances and therefore also limits the amount
// of maximum Inventory instances. More cells can be allocated by
// passing a struct space into the textmacro located at the top of the Inventory library.
// A table allocator could enabled endless cell instances, but I hope you don't need it.
//==========================================================================================
    
//! textmacro INVENTORY_CELL_CODE takes MORE
    struct InventoryCell $MORE$
    
        // Tracks items which need two cells.
        private static Table shadows
    
        // Returns false for invalid cells.
        method operator exists takes nothing returns boolean
            return (icon != 0)
        endmethod
    
        readonly CustomItem data    // Item in this cell. 0 for no item.
        readonly ItemClass  class   // Class of this cell.
        readonly UIButton   icon    // UIButton ( trackable & visual for this cell )
        public   boolean    enabled // Status of this cell.
        
        method getButton takes nothing returns UIButton
            return icon
        endmethod
        
        method hasItem takes nothing returns boolean
            return (data != 0)
        endmethod
        
        method getDescription takes nothing returns string
            return class.name
        endmethod
        
        // CustomItem and InventoryCell are strongly linked with each other.
        // Get a cell by CustomItem index. Returns 0 for no cell.
        static method operator [] takes CustomItem index returns thistype
            return index.getCell()
        endmethod
        
        // Get the struct this cell is located in.
        method getTypeId takes nothing returns integer
            return icon.typeId
        endmethod
        
        // Get the actual item handle. Returns "null" for no item in this cell.
        method getItem takes nothing returns item
            return data.getHandle()
        endmethod
    
        // Called each time something happens in an Inventory.
        method update takes boolean show returns nothing
            local integer itemId = data.getItemId()
            local string  path   = GetCustomItemIconPath(itemId)
            if (data == 0) then
                call icon.remove()
            else
                if (path != "") and (path != null) then
                    call icon.addImage(path, show)
                else
                    call icon.addDest(GetCustomItemIconId(itemId), show)
                endif
                //
                // Reference to this cell in CustomItem.
                call data.setCell(this, getTypeId() != GetNativeInventoryTypeId())
            endif
        endmethod
        
        // Depreciated, but I don't know if still used somewhere. 
        method move takes integer it, boolean show returns nothing
            debug call ThrowWarning(true, "Inventory", "InventoryCell", "move", 0, "Don't use method move!")
            set data = it
            call update(show)
        endmethod
        
        method swap takes thistype cell, boolean show returns nothing
            local integer temp = data
            set data           = cell.data
            set cell.data      = temp 
            call cell.update(show)
            call this.update(show)
        endmethod
    
        static method create takes UIButton node returns thistype
            local thistype this = thistype.allocate()
            set icon      = node
            set data      = 0
            set class     = node.data
            set enabled   = true
            set node.data = this 
            return this
        endmethod

        // Compare item which cell class.
        public method matchesItem takes item i returns boolean
            local CustomItem whichItem = GetHandleId(i)
            local ItemClass leaf       = class
            local ItemClass itemClass  = whichItem.getClass()
            loop
                exitwhen (itemClass == leaf) or (leaf.root)
                set leaf = leaf.parent
            endloop
            return (leaf == itemClass) or (class.root)
        endmethod
        
        // Depreciated, but still here.
        public method matches takes CustomItem id returns boolean
            return matchesItem(id.getHandle())
        endmethod
        
        // For twohand items.
        method addShadow takes item whichItem, boolean flag returns nothing
            local integer itemId = GetItemTypeId(whichItem)
            local string  file   = GetCustomItemIconDISPath(itemId)
            local integer dest   = GetCustomItemIconDISId(itemId)
            local integer id     = GetHandleId(whichItem)

            if (dest == 0) and (file != "") and (file != null) then
                call icon.addImage(file, flag)
            elseif (dest != 0) then
                call icon.addDest(dest, flag)
            elseif (CUSTOM_ITEM_DIS_ICON_ID != 0) then
                call icon.addDest(CUSTOM_ITEM_DIS_ICON_ID, flag)
            elseif (CUSTOM_ITEM_DIS_ICON_PATH != "") and (CUSTOM_ITEM_DIS_ICON_PATH != null) then 
                call icon.addImage(CUSTOM_ITEM_DIS_ICON_PATH, flag)
            endif
            set shadows[id] = this
            set data = id
        endmethod
        
        static method removeShadow takes item whichItem returns nothing
            local thistype this = shadows[GetHandleId(whichItem)]
            if (this != 0) and (exists) then
                call icon.remove()
                set data = 0
                call shadows.remove(GetHandleId(whichItem))
            endif
        endmethod
        
        method clear takes nothing returns nothing
            debug call ThrowError(not exists, "InventoryCell", "clear", "thistype", this, "This instance is not allocated!")
            set data = 0
            call icon.remove()
        endmethod
        
        method moveItem takes item i, boolean show returns nothing
            local CustomItem ci = CustomItem[i]
            local thistype node = ci.getCell()
            debug call ThrowWarning(GetItemTypeId(i) == 0, "InventoryCell", "moveItem", "it", this, "Invalid item handle ( null )!")
            if (node.exists) and (node.getItem() == i) then
                call node.clear()
            endif
            set data = ci
            call update(show)
        endmethod
        
        method destroy takes nothing returns nothing
            if shadows.has(data) then
                call removeShadow(data.getHandle())
            endif
            call clear()
            set enabled = false
            set icon    = 0
            set class   = 0
            call deallocate()
        endmethod
    
        static method onRemove takes thistype this returns nothing
            if (data != 0) then
                call clear()
            endif
        endmethod
        
        private static method onDestroyItem takes nothing returns nothing
            local CustomItem ci = GetTriggerCustomItem()
            local thistype this = ci.getCell()
            if (exists) and (data == ci) then
                call clear()
            endif
        endmethod
        
        private static method init takes nothing returns nothing
            call RegisterCustomItemEvent(EVENT_CUSTOM_ITEM_DESTROY, function thistype.onDestroyItem)
            set shadows = Table.create()
        endmethod
        implement UIInit
    
    endstruct
//! endtextmacro
    
    
endlibrary
Top