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

[Solved] Bpower Equipment System (The unapproved one)

Status
Not open for further replies.
Level 1
Joined
Jan 23, 2015
Messages
160
@BPower has a hidden gem in the "substandard" category of Spells, and it is this awesome equipment system.


57740-8877ca3b246aa14c4575f734da96838d.jpg


I have tried to use his approved one, but it doesn't support dynamic items (or facilitate wearing 2 rings) I have tried @TriggerHappy's system and it is still the main candidate for use, but I was hoping someone could take a look at this and tell me what about it makes it substandard. I did a bit of tinkering with it and I think it works great! I have some questions:

  • How can I add a sfx (like sword model appears on hand when sword equipped) to an equipable item? I know this can be done thru attachment for ability but Bpower's approved equipment system has the option to add attachment in trigger Solved!

  • Is it possible to do dynamic abilities? By that I mean is it possible to have a random chance of giving 1 of 3 abilities? (Devotion Aura, Storm Bolt, Life Drain. Equipping a certain dynamic item has a chance of giving you 1 of those 3) If so, how do I achieve this? Found a workaround!

  • How can I add an "animation tag" to each item such that the unit equipping it would adopt a certain animation tag? For example, a shield would make a unit use a "Defend" animation tag. Solved!

If you don't have time to look at the map, please take a look at the triggers and tell me what do I need to look into?

Inventory Core System:
JASS:
//! novjass ( disables the JassHelper until the next endnovjass declaration )
//=========================================================================
// Collection of the entire Inventory API
//=========================================================================
//=========================================================================
// Item classes.
// Every custom item requires an item class
// to determine into which slots the item can go.
// The root of every class is ItemClass.ANY ( 1 )
//=========================================================================
// Example:
    set ITEM_CLASS_HAND   = ItemClass.create("Hand")
    set ITEM_CLASS_WEAPON = ITEM_CLASS_HAND.createSubClass("Weapon")
    set ITEM_CLASS_SWORD  = ITEM_CLASS_WEAPON.createSubClass("Sword")
    // • An item of class "sword" can only go in a sword slot.
    // • An item of class "hand" can go in an hand, weapon and sword slot.
    // • An item of class "ANY" can go into any slow.
 
    struct ItemClass extends array
 
        // Fiels you can read.
        readonly thistype parent
        readonly string   name
        // Every item class is based of ANY.
        static constant thistype ANY = 1
 
        // Returns true for this == ANY
        method operator root  takes nothing returns boolean
 
        // Every class is a sub class of ItemClass.ANY.
        static method create  takes string name returns thistype
        // Any -> Hand -> Weapon -> Sword -> Mighty Sword ....
        method createSubClass takes string name returns thistype
//=========================================================================
// Extended item object data.
// These function are ment to call on map init.
//=========================================================================
    //=========================================================================
    // Item registration.
    //=========================================================================
    // Registers an item id as custom item.
    function RegisterCustomItemId takes integer itemId, ItemClass class returns nothing
    //  • Item ids of type ITEM_TYPE_POWERUP are invalid.
    //  • Item ids of type ITEM_TYPE_PURCHASABLE are valid, but remain in the native inventory interface.
    function IsCustomItemId       takes integer itemId returns boolean
    function IsCustomItem         takes item whichItem returns boolean
    // Returns true if a given item is of ItemClass "class".
    function IsCustomItemClass    takes item whichItem, ItemClass class returns boolean
    function GetCustomItemIdClass takes integer itemId returns ItemClass
    function GetCustomItemClass   takes item whichItem returns ItemClass
    //===================================================================================
    // Icon path and icon disabled path.
    //===================================================================================
    // If you don't set a DIS path, the default DIS path
    // from the user setup library is accessed instead.
 
    // Set paths for the visual objects.
    // You can use string paths for images or raws for destructable objects or both.
    function SetCustomItemIcon takes integer itemId, string filePath, string DISfilePath, integer destId, integer DISdestId returns nothing
    //  • Example 1: call SetCustomItemIcon('I000', null, null, 'B000', 'B001')
    //  • Example 2: call SetCustomItemIcon('I001', itemImageTextureFilePath.tga", itemImageTextureDisabledFilePath.tga, 0, 0)
    function GetCustomItemIconPath      takes integer itemId returns string
    function GetCustomItemIconDISPath   takes integer itemId returns string
    function GetCustomItemIconId        takes integer itemId returns integer
    function GetCustomItemIconDISId     takes integer itemId returns integer
 
    //===================================================================================
    // Twohanded items.
    //===================================================================================
    // This function declares an item id as a twohanded item type.
    // By default every item id is not twohanded.
 
    function SetCustomItemTwohand takes integer itemId, boolean flag returns nothing
    function IsCustomItemTwohand  takes item whichItem returns boolean
    //===================================================================================
    // Special effects. Animation properties. Complex special effects.
    //===================================================================================
    // These effects are added when items are equipped.
 
    // AddSpecialEffectTarget(path, inventoryUnit, pos)
    function SetCustomItemFxFilePath        takes integer itemId, string path, string pos returns nothing
 
    // For effects with multiple attach point names an ability is the best choice.
    // The ability should serve as pure visual and don't add any further bonuses.
    function SetCustomItemFxAbilityId       takes integer itemId, integer abilityId returns nothing
 
    // Animation tag of the item. By default it is "".
    // AddUnitAnimationProperties(inventoryUnit, tag, true)
    function SetCustomItemAnimationProperty takes integer itemId, string tag returns nothing
 
    function GetCustomItemFxFilePath        takes integer itemId returns string
    function GetCustomItemFxPos             takes integer itemId returns string
    function GetCustomItemFxAbilityId       takes integer itemId returns integer
    function GetCustomItemAnimationProperty takes integer itemId returns string
 
    // Complex fx.
    function SetCustomItemFxAbilityId takes integer itemId, integer abilityId returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId, abilityId)
    endfunction
 
    function GetCustomItemFxAbilityId takes integer itemId returns integer
 
    //===================================================================================
    // Item socket count from min to max. Min == 0, Max == 6
    //===================================================================================
    // BOTH FUNCTIONS ARE NOT WORKING YET.
 
    function SetCustomItemIdSockets takes integer itemId, integer min, integer max returns nothing
 
    // Randomizes a socket count from min - max.
    function GetCustomItemIdSockets takes integer itemId returns integer
    //===================================================================================
    // Item costs and charges.
    //===================================================================================
 
    function GetItemTypeIdGoldCost  takes integer id returns integer
    function GetItemTypeIdWoodCost  takes integer id returns integer
    function GetItemTypeIdCharges   takes integer id returns integer
    function GetItemGoldCost        takes item i returns integer
    function GetItemWoodCost        takes item i returns integer
 
    //===================================================================================
    // Misc wrappers functions of CustomItem
    //===================================================================================
 
    // ITEM_TYPE_PURCHASABLE
    function IsItemPurchasable takes item whichItem returns boolean
    //===================================================================================
    // Struct CustomItem
    //===================================================================================
 
    struct CustomItem extends array
 
        // Is the item allocated or not.
        method operator exists takes nothing returns boolean
 
        // Equals GetHandleId(i). Always use CustomItem[]
        static method operator [] takes item i returns thistype
 
        // Returns the item handle of an handle id.
        method getItem      takes nothing returns item
        method getItemId    takes nothing returns integer
 
        // Use getItem() instead.
        method getHandle    takes nothing returns item
 
        // Set an owning unit.
        method getOwner     takes nothing returns unit
        method setOwner     takes unit source returns nothing
        // Not implemented. ( Item cooldowns )
        method isReady      takes nothing returns boolean
 
        // You may not call these two functions!
        method identify     takes nothing returns nothing
        method isIdentified takes nothing returns boolean
        // Returns true for a total item merging. MAX_CHARGES = 99.
        method mergeCharges takes item add returns boolean
 
        method getClass     takes nothing returns ItemClass
        // From 0.00 - 1.00 ( 0% - 100%)
        method getQuality   takes nothing returns real
        method setQuality   takes real value returns nothing
        method getWoodCost  takes nothing returns integer
        method getGoldCost  takes nothing returns integer
        // Returns custom name if set. Otherwise GetItemName()
        method getName      takes nothing returns string
        method setName      takes string name returns nothing
 
        method isItemPawnPossible takes unit seller, unit shop returns boolean
        method pawnItem           takes unit shop returns nothing
        // ( Not implemented yet )
        method isItemBuyPossible takes unit buyer, unit seller returns boolean
 
//=========================================================================
// Item affixes.
// These are lists of Bonuses or abilties
// an item should add to a unit when beeing equipped.
//=========================================================================
 
    // Registers an abiltity to an item id.
    function AddCustomItemIdAbility         takes integer itemId, integer abilityId, integer level returns nothing
 
    // Requires library BonusMod: Register Bonuses to item id.
    function AddCustomItemIdBonus           takes integer itemId, integer bonus, integer amount returns nothing
    // Those functions register affixes directly to item handles.
    // The item parser requires them for dynamic item data,
    // you probably don't need these functions.
    function AddCustomItemAbility           takes item i, integer abilityId, integer level returns nothing
    function AddCustomItemBonus             takes item i, integer bonus, integer amount returns nothing
    function FlushCustomItemAffixes         takes item i returns nothing
 
//=========================================================================
// Dynamic items. ( Roll random affixes )
// These are item ids which can have random affixes.
//=========================================================================
// Dynamic items inherit static bonuses ( above ),
// therefore you can declare affixes an item id should
// always get and combine it with affixes it rolls dynamically.
// You can also declare constant affixes which roll in range min - max. ( See below at 6.) )
    // 1.) Random affixes are rolled over loop indexes. (1, 2, 3, ..... )
    // 2.) Loop indexes start off with 1. Looping stops at the highest defined index of that item id.
    // 3.) Normally you only define the first loop index.
    // 4.) Per loop index an affix can only be rolled once.
    // 5.) Per loop index a random amount ( minimum - maximum ) affixes are rolled.
    // 6.) Affixes added to loop index 0 are considered as constant and will roll always.
    //
    // Example: call SetAffixRangeForLoopIndex('I000', 1, 2, 5)
    //          call SetAffixRangeForLoopIndex('I000', 2, 1, 2)
    //  --> Rolls 2 - 5 affixes in loop index 1.
    //  --> Then 1 - 2 affixes in loop index 2.
    function SetAffixRangeForLoopIndex takes integer itemId, integer index, integer minimum, integer maximum returns nothing
 
    // Add affixes to a loop index.
    // Example: AddItemAffix('I000', 0, "Bonus", BONUS_DAMAGE, 10, 20)
    //          AddItemAffix('I000', 1, "Bonus", BONUS_LIFE, 100, 200)
    //          AddItemAffix('I000', 1, "Bonus", BONUS_MANA, 10, 20)
    //          AddItemAffix('I000', 1, "Bonus", BONUS_Strength, 2, 2)
    // --> For loop 1 the item can roll between 100 - 200 life bonus. Eventually it rolls the mana or strength bonus instead.
    // --> Items of type id 'I000' will always roll between 10 and 20 damage.
 
    // Valid "affixType" arguments are "Ability" or "Bonus". ( Case insensitiv - "BONUS" or "BoNUs" would also be ok )
    function AddItemAffix takes integer itemId, integer loopIndex, string affixType, integer affixId, real min, real max returns nothing
 
//=========================================================================
// Inventory interface.
//=========================================================================
// Each inventory has a hardcoded size of 1500*833 in plane x / y.
    function CreateInventory       takes unit source, real originX, real originY returns Inventory
    function DestroyInventory      takes unit source returns nothing
 
    // Returns the Inventory instance for a unit.
    // Returns 0 for units without inventory.
    function GetUnitInventory      takes unit source returns Inventory
    function ShowUnitInventory     takes unit source, boolean flag returns nothing
 
    function AddUnitInventoryDummy takes unit source, integer dummyId, real dummyY returns nothing
    //  • Optional function with adds a portrait to the interface.
    //  • "dummyY" is the offset in plane y inside the dummy window. ( Different per unit id )
 
//===================================================================================
// 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_CREATED           = 0
        constant integer EVENT_INVENTORY_UNIT_EQUIP_ITEM   = 1
        constant integer EVENT_INVENTORY_UNIT_UNEQUIP_ITEM = 2
        constant integer EVENT_INVENTORY_UNIT_PICKUP_ITEM  = 3
        constant integer EVENT_INVENTORY_UNIT_DROP_ITEM    = 4
        constant integer EVENT_INVENTORY_UNIT_PAWN_ITEM    = 5
        constant integer EVENT_INVENTORY_UNIT_BUY_ITEM     = 6// ( Not working yet )
        constant integer EVENT_INVENTORY_UNIT_TRADE_ITEM   = 7// ( Not working yet )
    endglobals
    //=========================================
    // Inventory Trigger Interface.
    //=========================================
    // Returns the trigger handle for the specified event id.
    // Implemented for usage with the native trigger interface.
    // Use this function very carefully.
    function GetInventoryTrigger takes integer eventId returns trigger
 
    //=======================================================
    // 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
 
    // Returns the interacting item.
    constant function Inventory_GetTriggerItem     takes nothing returns item
 
    // Returns the interacting unit.
    // For most events this function is equal to Inventory_GetTriggerUnit().
    constant function Inventory_GetEventTargetUnit takes nothing returns unit
 
    // Returns the most recent event id.
    constant function Inventory_GetTriggerEventId  takes nothing returns integer
 
    // Returns the inventory owning unit.
    // Specified for events where Inventory_GetEventTargetUnit()
    // doesn't return the inventory source unit. ( Shop events )
    constant function Inventory_GetTriggerUnit     takes nothing returns unit
 
    // Registers code to available inventory events.
    // Condition functions for inventory events don't have to return a boolean.
    function RegisterInventoryEvent takes integer eventId, code func returns nothing
 
//=======================================================
// Struct Inventory.
//=======================================================
 
    struct Inventory extends UIScreen
 
 
        readonly unit    source// Owning unit.
        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
        method setFont takes integer newFont returns nothing
 
        // The global user setup defines a default color for all instances,
        // you can change it any time via this.setColor(ARGB)
        private integer color
        method getColor takes nothing returns ARGB
        method setColor takes integer newColor returns nothing
 
        method unitHasItemEquipped   takes item whichItem returns boolean
        method unitHasItemIdEquipped takes integer itemId returns boolean
 
        method mergeItems            takes item target, item source returns boolean
        method unitRemoveItem        takes item whichItem returns boolean
        method unitUnequipItemToSlot takes item whichItem, InventoryCell slot returns boolean
 
        // Equips items to the unit.
        method unitEquipItemToSlot   takes item whichItem, InventoryCell slot, boolean forceAction returns boolean
        method unitEquipItem         takes item whichItem returns boolean
        // Only accepts Backpack cells as slot.
        method unitAddItemToSlot     takes item whichItem, InventoryCell slot returns boolean
        method unitAddItem           takes item whichItem returns boolean
 
        // UNDER CONSTRUCTION.
        method unitPawnItem          takes item whichItem, unit shop returns boolean
 
        // UNDER CONSTRUCTION.
        method unitBuyItem           takes item whichItem, unit shop returns boolean
        // UNDER CONSTRUCTION.
        method unitTradeItem         takes item whichItem, thistype partner returns boolean
 
        // UNDER CONSTRUCTION.
        method socketItem            takes item whichItem, item gem returns boolean
 
        // UNDER CONSTRUCTION.
        method unsocketItem          takes item whichItem returns boolean
 
//=======================================================
// Struct id getters.
//=======================================================
 
    constant function GetInventoryEquipmentTypeId        takes nothing returns integer
    constant function GetInventoryBackpackTypeId         takes nothing returns integer
    constant function GetInventoryMerchantTypeId         takes nothing returns integer
    constant function GetInventoryNativeInventoryTypeId  takes nothing returns integer
    constant function GetInventoryDummyTypeId            takes nothing returns integer
    constant function GetInventoryControllTypeId         takes nothing returns integer
    constant function GetInventoryTradeTypeId            takes nothing returns integer
//! endnovjass
library InventoryUserSetup
JASS:
//=========================================================================
// Collection of all fields
// you can customize in Inventory.
//=========================================================================
//===========================================================================
// External object merger. ( LUA )
// This tool can generate a bunch of chosen "Object Editor" objects for you,
// so you don't have to copy 'n' paste everything from this map to your map.
//
// Follow the instructions below
// to run the LUA script.
//===========================================================================
    globals
        constant boolean INVENTORY_GENERATE_OBJECTS = false
        // 1.) Enable the "Inventory Generate Objects" trigger.
        // 2.) Set INVENTORY_GENERATE_OBJECTS = true.
        // 3.) Save your map, close the editor, start the editor and open your map.
        // 4.) Set INVENTORY_GENERATE_OBJECTS = false.
        // 5.) Disable or delete the "Inventory Generate Objects" trigger.
    endglobals
//=========================================
// Inventory constants.
// Like the game play constants,
// but for Inventory.
//=========================================
 
    globals
        // Item related.
        constant boolean INVENTORY_ENABLE_DYNAMIC_ITEMS    = true
 
        // Shop related.
        constant real    INVENTORY_SELL_ITEM_RETURN_RATE   = 0.5
        constant integer INVENTORY_MAXIMUM_SHOP_RANGE      = 450
        constant integer INVENTORY_MERCHANT_EXTRA_PAGES    = 0    // ( Not working yet )
 
        // Tooltip related.
        constant string  INVENTORY_RESOURCE_GOLD_ICON      = "UI\\Feedback\\Resources\\ResourceGold.blp"
        constant string  INVENTORY_RESOURCE_WOOD_ICON      = "UI\\Feedback\\Resources\\ResourceLumber.blp"
 
        // Font related.
        constant real    INVENTORY_FONT_SIZE               = 8// Recommended setting is 8.
 
        // Cell selector.
        constant integer INVENTORY_CELL_SELECTOR_ID        = 'Bisa'
        constant real    INVENTORY_CELL_SELECTOR_SCALE     = .75// Relative to a base of 64x64.
 
        // Default icon and DIS icon.
        // This file / id is accessed, when no path / id is set for an object.
        // The string refers to an image file path, the id to a destructable object.
        // If declared the string file path is used over the the destructable id.
        constant string  INVENTORY_ITEM_ICON_PATH     = null
        constant integer INVENTORY_ITEM_ICON_ID       = 'Biba'
        constant string  INVENTORY_ITEM_DIS_ICON_PATH = null
        constant integer INVENTORY_ITEM_DIS_ICON_ID   = 'Biba'
    endglobals
//=============================================
// Global default values.
// These informactions are used,
// if not specified different per instance.
//=============================================
    // Default font for textsplats in every Inventory instance.
    // Use instance.setFont(desiredFont) to change the font of an inventory instances.
    constant function Inventory_GetDefaultFont takes nothing returns font
        return TREBUCHET_MS
    endfunction
 
    // Set the default font color for interface errors in ARGB.
    // instance.setColor(ARGB) allows you to change the font color of an inventory instance.
    function Inventory_GetDefaultFontColor takes nothing returns ARGB
        return ARGB.create(255, 218, 165, 32)
    endfunction
//==================================================
// Struct and race based fields.
// The setup below is specific for each
// individual interface window of Inventory.
//==================================================
 
    //=========================================
    // Equipment related setup.
    //=========================================
    //  To change the visual layout and / or the item classes of buttons, go to library "Inventory Equipment",
    // --> "static method onCreate takes Inventory inventory returns thistype"
    // and configurate the buttons coordinates and data to your needs.
 
    // Set user interface border fields.
    function Inventory_SetEquipmentBorderFields takes unit source returns nothing
        local race r = GetUnitRace(source)
        // Hint: UIBorder uses a default setting ( null ) for each border brick, which is used if
        // you don't specify the path. You can also change the default setting inside the UIBasic library.
        if (r == RACE_ORC) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftOC.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightOC.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpOC.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownOC.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftOC.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightOC.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftOC.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightOC.TGA"
        elseif (r == RACE_NIGHTELF) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftNE.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightNE.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpNE.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownNE.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftNE.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightNE.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftNE.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightNE.TGA"
        endif
        set UI_BORDER_BACKGROUND_COLOR = ARGB.create(255, 0, 0, 0)
        set UI_BORDER_BACKGROUND       = "war3mapImported\\background.TGA"
        set r = null
    endfunction
 
    //=========================================
    // Backpack related setup.
    //=========================================
    // To change the visual layout and / or the item classes of buttons, go to library "Inventory Backpack",
    // --> "static method onCreate takes Inventory inventory returns thistype"
    // and configurate the buttons coordinates and data to your needs.
 
    // Set the backpack row count.
    constant function Inventory_GetBackpackRowCount takes unit source returns integer
        return 4
    endfunction
 
    // Set the backpack column count.
    constant function Inventory_GetBackpackColumnCount takes unit source returns integer
        return 5
    endfunction
 
    // Add extra pages to the backpack. If you don't want to add pages pass in 0 or 1.
    constant function Inventory_GetBackpackPageCount takes unit source returns integer
        return 3
    endfunction
 
    // Set the path for the image used as empty slot.
    constant function Inventory_GetBackpackSlotFilePath takes unit source returns string
        return "UI\\Console\\Human\\human-transport-slot.blp"
    endfunction
 
    // Set user interface border fields. For full API check the available fields for UIBorder.
    function Inventory_SetBackpackBorderFields takes unit source returns nothing
        local race r = GetUnitRace(source)
        // Hint: UIBorder uses a default setting ( null ) for each border brick, which is used if
        // you don't specify the path. You can change the default setting inside the UIBasic library.
        if (r == RACE_ORC) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftOC.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightOC.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpOC.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownOC.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftOC.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightOC.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftOC.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightOC.TGA"
        elseif (r == RACE_NIGHTELF) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftNE.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightNE.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpNE.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownNE.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftNE.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightNE.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftNE.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightNE.TGA"
        endif
        set UI_BORDER_BACKGROUND       = null
    endfunction
 
    //=========================================
    // Controll panel related setup.
    //=========================================
    // To change the visual layout, go to library "Inventory Controll",
    // --> "static method onCreate takes Inventory inventory returns thistype"
    // and configurate the buttons coordinates to your needs.
 
    // Set the path for the scroll up arrow.
    constant function Inventory_GetScrollUpPath takes unit source returns string
        return "war3mapImported\\ScrollUp.tga"
    endfunction
 
     // Set the path for the scroll down arrow.
    constant function Inventory_GetScrollDownPath takes unit source returns string
        return "war3mapImported\\ScrollDown.tga"
    endfunction
 
     // Set the destructable id for the drop item button. You can also use an image file,
     // but then you have to change addDestButton to addImageButton inside the library.
    constant function Inventory_GetDropButtonId takes unit source returns integer
        return 'Bibb'
    endfunction
 
     // Set the destructable id for the close inventory button. You can also use an image file,
     // but then you have to change addDestButton to addImageButton inside the library.
    constant function Inventory_GetCloseButtonId takes unit source returns integer
        return 'Biba'
    endfunction
 
    // Set the destructable id for the pawn item button.
    constant function Inventory_GetPawnButtonId takes unit source returns integer
        return 'Bibc'
    endfunction
 
    // Set the destructable id for the buy item button.
    constant function Inventory_GetBuyButtonId takes unit source returns integer
        return 'Bibd'
    endfunction
 
    // Set the background image path.
    constant function Inventory_GetBackgroundFilePath takes unit source returns string
        return "UI\\Widgets\\EscMenu\\Human\\human-options-button-background-disabled.blp"
    endfunction
 
    // Colorize the background image via an ARGB value.
    function Inventory_GetBackgroundColor takes unit source returns ARGB
        if (GetUnitRace(source) == RACE_NIGHTELF) then
            return ARGB.create(255, 150, 255, 150)
        endif
        return ARGB.create(255, 139, 69, 19)
    endfunction
 
    //===========================================
    // Dummy window and affix box related setup.
    //===========================================
    // This setup relates to the unit affix box,
    // which is displayed when clicking the user interface dummy unit.
    //
    // The affix box is one the more heavy operations within the inventory source code.
    // Please do not overdo it in terms of text rows. Another factor are color codes.
    // Color codes have to be translated to hexadecimal, then to ARGB and through this add overhead.
    //
    // The box automatically updates on equip and unequip.
    // You can update it manually by calling:
    //
    // function Inventory_UpdateAffixBox takes unit source returns nothing
 
    // Set which units should allocate an affix box at all.
    function Inventory_AllocateAffixBoxForUnit takes unit source returns boolean
        return IsUnitType(source, UNIT_TYPE_HERO)
    endfunction
 
    // Set the headline text.
    function Inventory_GetAffixBoxHeadline takes unit source returns string
        return "|cffdaa520Attributes:|r"
    endfunction
 
    // Set the amount of rows inside the box.
    function Inventory_GetAffixBoxRowCount takes unit source returns integer
        return 4
    endfunction
 
    // Set the string per row within the affix box. First row index is 0.
    function Inventory_GetAffixBoxRowString takes unit source, integer row returns string
        if     (row == 0) then
            return"|cffdaa520Strength:|r " + I2S(GetHeroStr(source, true))
        elseif (row == 1) then
            return "|cffdaa520Agility:|r " + I2S(GetHeroAgi(source, true))
        elseif (row == 2) then
            return "|cffdaa520Intelligence:|r " + I2S(GetHeroInt(source, true))
        endif
        return "|cffdaa520Movement Speed:|r " + I2S(R2I(GetUnitMoveSpeed(source)))
    endfunction
 
    // Set border fields for the dummy window.
    function Inventory_SetDummyBorderFields takes unit source returns nothing
        local race r = GetUnitRace(source)
        if r == RACE_NIGHTELF then
            set UI_BORDER_BACKGROUND   = "UI\\Glues\\Loading\\Backgrounds\\Campaigns\\NightElfSymbol.blp"
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftNE.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightNE.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpNE.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownNE.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftNE.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightNE.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftNE.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightNE.TGA"
        elseif r == RACE_ORC then
            set UI_BORDER_BACKGROUND   = "UI\\Glues\\Loading\\Backgrounds\\Campaigns\\OrcSymbol.blp"
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftOC.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightOC.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpOC.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownOC.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftOC.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightOC.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftOC.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightOC.TGA"
        endif
        // Higher layer than the equipment.
        set UI_BORDER_BACKGROUND_IMAGE_TYPE = IMAGE_TYPE_OCCLUSION_MASK
        set r = null
    endfunction
 
    // Set border fields for the affix box window.
    function Inventory_SetAffixBoxBorderFields takes unit source returns nothing
        local race r = GetUnitRace(source)
        // Hint: UIBorder uses a default setting ( null ) for each border brick, which is used if
        // you don't specify the path. You can change the default setting inside the UIBasic library.
        if (r == RACE_ORC) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftOC.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightOC.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpOC.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownOC.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftOC.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightOC.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftOC.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightOC.TGA"
        elseif (r == RACE_NIGHTELF) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftNE.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightNE.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpNE.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownNE.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftNE.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightNE.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftNE.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightNE.TGA"
        endif
        set UI_BORDER_BACKGROUND_COLOR = ARGB.create(255, 0, 0, 0)
        set UI_BORDER_BACKGROUND       = "war3mapImported\\background.TGA"
    endfunction      
 
    //===========================================
    // Native Inventory related setup.
    //===========================================
    constant function Inventory_GetSlotFiller takes unit source returns string
        return "war3mapImported\\background.TGA"
    endfunction
    //===========================================
    // Tooltip box related setup.
    //===========================================
 
    function Inventory_SetTooltipBoxBorderFields takes unit source returns nothing
        local race r = GetUnitRace(source)
        // Hint: UIBorder uses a default setting ( null ) for each border brick, which is used if
        // you don't specify the path. You can change the default setting inside the UIBasic library.
        if (r == RACE_ORC) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftOC.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightOC.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpOC.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownOC.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftOC.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightOC.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftOC.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightOC.TGA"
        elseif (r == RACE_NIGHTELF) then
            set UI_BORDER_LEFT         = "war3mapImported\\BorderLeftNE.TGA"
            set UI_BORDER_RIGHT        = "war3mapImported\\BorderRightNE.TGA"
            set UI_BORDER_TOP          = "war3mapImported\\BorderUpNE.TGA"
            set UI_BORDER_BOTTOM       = "war3mapImported\\BorderDownNE.TGA"
            set UI_BORDER_BOTTOM_LEFT  = "war3mapImported\\BorderDownLeftNE.TGA"
            set UI_BORDER_BOTTOM_RIGHT = "war3mapImported\\BorderDownRightNE.TGA"
            set UI_BORDER_TOP_LEFT     = "war3mapImported\\BorderUpLeftNE.TGA"
            set UI_BORDER_TOP_RIGHT    = "war3mapImported\\BorderUpRightNE.TGA"
        endif
        set UI_BORDER_BACKGROUND = "war3mapImported\\background.TGA"
    endfunction
 
//===================================================================================
// This block serves to configurate the
// item parsing process to your needs without
// messing with the source code.
//===================================================================================
 
//! textmacro ITEM_PARSER_ITEM_TOOLTIP_SUFFIXES takes DO
static if $DO$ then
//===================================================================================
// Bonus tooltip suffixes.
//===================================================================================
// Example: +15 Armor.
// The "+" string is harcoded into the parser.
// The value "15" is internally read out from the bonus list.
// The suffix "Armor" can be configurated in this macro.
    static if LIBRARY_BonusMod then
        call SetSuffix(BONUS_AGILITY,               "|cff0066ccAgility|r")
        call SetSuffix(BONUS_STRENGTH,              "|cff0066ccStrength|r")
        call SetSuffix(BONUS_INTELLIGENCE,          "|cff0066ccIntelligence|r")
        call SetSuffix(BONUS_ARMOR,                 "|cff0066ccArmor|r")
        call SetSuffix(BONUS_DAMAGE,                "|cff0066ccDamage|r")
        call SetSuffix(BONUS_ATTACK_SPEED,          "|cff0066ccAttack Speed|r")
        call SetSuffix(BONUS_SIGHT_RANGE,           "|cff0066ccSight Range|r")
        call SetSuffix(BONUS_ATTACK_SPEED,          "|cff0066ccAttack Speed|r")
        call SetSuffix(BONUS_LIFE_REGEN,            "|cff0066ccLife Regeneration|r")
        call SetSuffix(BONUS_MANA_REGEN_PERCENT,    "|cff99b4d1%|r |cff0066ccMana Regeneration|r")
endif
static if LIBRARY_RegenBonuses and LIBRARY_BonusMod then
        call SetSuffix(BONUS_MANA_REGEN,            "|cff0066ccMana Regeneration|r")
        call SetSuffix(BONUS_LIFE_REGEN_PERCENT,    "|cff99b4d1%|r |cff0066ccLife Regeneration|r")
endif
static if LIBRARY_UnitMaxStateBonuses and LIBRARY_BonusMod then
        call SetSuffix(BONUS_LIFE,                  "|cff0066ccLife|r")
        call SetSuffix(BONUS_MANA,                  "|cff0066ccMana|r")
endif
static if LIBRARY_MovementBonus and LIBRARY_BonusMod then
        call SetSuffix(BONUS_MOVEMENT_SPEED,        "|cff0066ccMovement Speed|r")
endif
endif
//! endtextmacro
//! textmacro ITEM_PARSER_RANDOM_ITEM_NAME
//===================================================================================
// Inventory is able to generate
// random names for custom items, if
// the following conditions match:
//  1.) The item must be of type ITEM_TYPE_PERMANENT.
//  2.) The item must roll random affixes.
//===================================================================================
// Factors which influence the naming are:
//  1.) Item quality. ( In range of 0% - 100% )
//  2.) Strongest rolled affix.
//===================================================================================
// An item name is composed of
// prefix + GetItemName(item) + suffix.
// You can define all suffixes and prefixes in
// the Inventory User Setup.
//
// In case the strongest rolled affix is
// an ability id, the item name will become:
// GetPrefix(abilityId) + GetItemName(item) + of + GetObjectName(abilityId)
// However abilities always lookup their prefix on 0% quality.
//===================================================================================
// Initalize your affix suffixes and prefixes here.
// To speed up the process you can define them in range.
//
// For example SetPrefix("Strong", BONUS_DAMAGE, 0, 40)
// initializes the prefix "Strong" for BONUS_DAMAGE affixes
// in quality range from 0% to 40%.
//
// Minimum allowed quality: 0
// Maximum allowed quality: 100
static if LIBRARY_BonusMod then
        // takes:       text,        bonus,        quality min %, quality max %.
        call SetPrefix("Jagged",     BONUS_DAMAGE, 0,             20)
        call SetPrefix("Deadly",     BONUS_DAMAGE, 21, 40)
        call SetPrefix("Brutal",     BONUS_DAMAGE, 41, 60)
        call SetPrefix("Cruel",      BONUS_DAMAGE, 61, 80)
        call SetPrefix("Merciless",  BONUS_DAMAGE, 81, 100)
        call SetSuffix("of Maiming", BONUS_DAMAGE, 0,  20)
        call SetSuffix("of Strife",  BONUS_DAMAGE, 21, 40)
        call SetSuffix("of Doom",    BONUS_DAMAGE, 41, 60)
        call SetSuffix("of Agony",   BONUS_DAMAGE, 61, 80)
        call SetSuffix("of Death",   BONUS_DAMAGE, 81, 100)
        //
        call SetPrefix("Dueling",     BONUS_STRENGTH, 0,  20)
        call SetPrefix("Cruel",       BONUS_STRENGTH, 21, 40)
        call SetPrefix("Dauntless",   BONUS_STRENGTH, 41, 60)
        call SetPrefix("Severe",      BONUS_STRENGTH, 61, 80)
        call SetPrefix("Murderous",   BONUS_STRENGTH, 81, 100)
        call SetSuffix("of the Lion", BONUS_STRENGTH, 0,  20)
        call SetSuffix("of Invasion", BONUS_STRENGTH, 21, 40)
        call SetSuffix("of Assault",  BONUS_STRENGTH, 41, 60)
        call SetSuffix("of Assault",  BONUS_STRENGTH, 61, 80)
        call SetSuffix("of Assault",  BONUS_STRENGTH, 81, 100)
        //
        call SetPrefix("Feral",       BONUS_AGILITY, 0,  20)
        call SetPrefix("True",        BONUS_AGILITY, 21, 40)
        call SetPrefix("Worthy",      BONUS_AGILITY, 41, 60)
        call SetPrefix("Marvelous",   BONUS_AGILITY, 61, 80)
        call SetPrefix("Valiant",     BONUS_AGILITY, 81, 100)
        call SetSuffix("of the Hawk", BONUS_AGILITY, 0,  20)
        call SetSuffix("of Cruelty",  BONUS_AGILITY, 21, 40)
        call SetSuffix("of Pain",     BONUS_AGILITY, 41, 60)
        call SetSuffix("of Pairon",   BONUS_AGILITY, 61, 80)
        call SetSuffix("of Pain",     BONUS_AGILITY, 81, 100)
        //
        call SetPrefix("Clever",       BONUS_INTELLIGENCE, 0,  20)
        call SetPrefix("Magic",        BONUS_INTELLIGENCE, 21, 40)
        call SetPrefix("Champion",     BONUS_INTELLIGENCE, 41, 60)
        call SetPrefix("Parvagon",     BONUS_INTELLIGENCE, 61, 80)
        call SetPrefix("Magnus's",     BONUS_INTELLIGENCE, 81, 100)
        call SetSuffix("of Focus",     BONUS_INTELLIGENCE, 0,  20)
        call SetSuffix("of the Mind",  BONUS_INTELLIGENCE, 21, 40)
        call SetSuffix("of Omens",     BONUS_INTELLIGENCE, 41, 60)
        call SetSuffix("of Far Sight", BONUS_INTELLIGENCE, 61, 80)
        call SetSuffix("of Far Sight", BONUS_INTELLIGENCE, 81, 100)
        //
        call SetPrefix("Worn-out",  BONUS_ARMOR, 0,  20)
        call SetPrefix("Wicked",    BONUS_ARMOR, 21, 40)
        call SetPrefix("Deadly",    BONUS_ARMOR, 41, 60)
        call SetPrefix("Perfect",   BONUS_ARMOR, 61, 80)
        call SetPrefix("Divine",    BONUS_ARMOR, 81, 100)
        call SetSuffix("of Strife", BONUS_ARMOR, 0,  20)
        call SetSuffix("of Strife", BONUS_ARMOR, 21, 40)
        call SetSuffix("of Strife", BONUS_ARMOR, 41, 60)
        call SetSuffix("of Strife", BONUS_ARMOR, 61, 80)
        call SetSuffix("of Strife", BONUS_ARMOR, 81, 100)
        //
        call SetPrefix("Brave",        BONUS_ATTACK_SPEED, 0,  20)
        call SetPrefix("Wicked",       BONUS_ATTACK_SPEED, 21, 40)
        call SetPrefix("Deadly",       BONUS_ATTACK_SPEED, 41, 60)
        call SetPrefix("Perfect",      BONUS_ATTACK_SPEED, 61, 80)
        call SetPrefix("Divine",       BONUS_ATTACK_SPEED, 81, 100)
        call SetSuffix("of the eagle", BONUS_ATTACK_SPEED, 0,  20)
        call SetSuffix("of Focus",     BONUS_ATTACK_SPEED, 21, 40)
        call SetSuffix("of Speed",     BONUS_ATTACK_SPEED, 41, 60)
        call SetSuffix("of Speed",     BONUS_ATTACK_SPEED, 61, 80)
        call SetSuffix("of Speed",     BONUS_ATTACK_SPEED, 81, 100)
        //
    static if LIBRARY_MovementBonus then
        call SetPrefix("Brave",        BONUS_MOVEMENT_SPEED, 0,  20)
        call SetPrefix("Wicked",       BONUS_MOVEMENT_SPEED, 21, 40)
        call SetPrefix("Deadly",       BONUS_MOVEMENT_SPEED, 41, 60)
        call SetPrefix("Perfect",      BONUS_MOVEMENT_SPEED, 61, 80)
        call SetPrefix("Divine",       BONUS_MOVEMENT_SPEED, 81, 100)
        call SetSuffix("of the eagle", BONUS_MOVEMENT_SPEED, 0,  20)
        call SetSuffix("of Focus",     BONUS_MOVEMENT_SPEED, 21, 40)
        call SetSuffix("of Speed",     BONUS_MOVEMENT_SPEED, 41, 60)
        call SetSuffix("of Speed",     BONUS_MOVEMENT_SPEED, 61, 80)
        call SetSuffix("of Speed",     BONUS_MOVEMENT_SPEED, 81, 100)
    endif
        //
    static if LIBRARY_UnitMaxStateBonuses then
        call SetPrefix("Brave",        BONUS_LIFE, 0,  20)
        call SetPrefix("Wicked",       BONUS_LIFE, 21, 40)
        call SetPrefix("Deadly",       BONUS_LIFE, 41, 60)
        call SetPrefix("Perfect",      BONUS_LIFE, 61, 80)
        call SetPrefix("Divine",       BONUS_LIFE, 81, 100)
        call SetSuffix("of the eagle", BONUS_LIFE, 0,  20)
        call SetSuffix("of Focus",     BONUS_LIFE, 21, 40)
        call SetSuffix("of Speed",     BONUS_LIFE, 41, 60)
        call SetSuffix("of Speed",     BONUS_LIFE, 61, 80)
        call SetSuffix("of Speed",     BONUS_LIFE, 81, 100)
        //
        call SetPrefix("Brave",        BONUS_MANA, 0,  20)
        call SetPrefix("Wicked",       BONUS_MANA, 21, 40)
        call SetPrefix("Deadly",       BONUS_MANA, 41, 60)
        call SetPrefix("Perfect",      BONUS_MANA, 61, 80)
        call SetPrefix("Divine",       BONUS_MANA, 81, 100)
        call SetSuffix("of the eagle", BONUS_MANA, 0,  20)
        call SetSuffix("of Focus",     BONUS_MANA, 21, 40)
        call SetSuffix("of Speed",     BONUS_MANA, 41, 60)
        call SetSuffix("of Speed",     BONUS_MANA, 61, 80)
        call SetSuffix("of Speed",     BONUS_MANA, 81, 100)
    endif
endif
//! endtextmacro
endlibrary
JASS:
library InventoryCore /*
//===================================================================================================
// Inventory Core.
// This library contains a collection of variables, constants and functions
// used in all libraries associated with the inventory interface.
// It also lists the required resources for Inventory.
//
// Don't change anything in here.
//===================================================================================================
    */ uses /*
 
        */ UIPackage                /* - Available in this map.
        */ InventoryUserSetup       /*
 
        */ Font                     /* hiveworkshop.com/forums/submissions-414/textsplat2-273717/
        */ Ascii                    /* hiveworkshop.com/forums/jass-functions-413/snippet-ascii-190746/
        */ WordWrap                 /* wc3jass.com/5016/system-wordwrap/
        */ TextSplat2               /* hiveworkshop.com/forums/submissions-414/textsplat2-273717/
        */ StringSize               /* http://wc3jass.com/5014/snippet-stringsize/msg38482/topicseen/#new
        */ ErrorMessage             /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
        */ RegisterPlayerUnitEvent  /* hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
        */ optional BonusMod        /* wc3c.net/showthread.php?t=107940
        */ optional GetItemCost     /* github.com/nestharus/JASS/tree/master/jass/Systems/Costs%20-%20Unit%20%26%20Item
=====================================================================================================*/
// Credits to:
// Bribe:         Ascii, Table
// Nestharus:     Ascii, ErrorMessage
// PurgeandFire:  StringSize, WordWrap
// Earth-Fury:    BonusMod
// Magtheridon96: RegisterPlayerUnitEvent
//===================================================================================================
    //===================================================================================
    // Databank.
    //===================================================================================
    globals
        private constant hashtable HASH = InitHashtable()
    endglobals
    // Access the databank.
    constant function Inventory_GetTable takes nothing returns hashtable
        return HASH
    endfunction
    //===================================================================================
    // Parent Keys. Changing their values can break the entire Inventory system.
    //===================================================================================
    globals
        constant integer INVENTORY_ITEM_COSTS         = 0
        constant integer INVENTORY_ITEM_CHARGES       = 1
        constant integer INVENTORY_ITEM_WOOD_COST     = 2
        constant integer INVENTORY_ITEM_GOLD_COST     = 3
 
        constant integer INVENTORY_KEY_CUSTOM_ITEM_NEXT   = 4
        constant integer INVENTORY_KEY_CUSTOM_ITEM_PREV   = 5
        constant integer INVENTORY_KEY_CUSTOM_ITEM        = 6
        constant integer INVENTORY_KEY_CUSTOM_ITEM_OWNER  = 7
        constant integer INVENTORY_KEY_CUSTOM_ITEM_CELL   = 8
        constant integer INVENTORY_KEY_ITEM_DATA          = 9
        constant integer INVENTORY_ITEM_NAME              = 20
        constant integer INVENTORY_KEY_ITEM_PARSER        = 21
        constant integer INVENTORY_KEY_ITEM_PARSER_SUFFIX = 22
        constant integer INVENTORY_KEY_UNIT_REFERENCE     = 23
        //
        constant integer INVENTORY_ITEM_TYPE_ID_AFFIXES      = 24
        constant integer INVENTORY_ITEM_TYPE_ID_ABILITY_LIST = 25
        constant integer INVENTORY_ITEM_TYPE_ID_BONUS_LIST   = 26
        constant integer INVENTORY_ITEM_ID_AFFIXES           = 27
        constant integer INVENTORY_ITEM_ID_ABILITY_LIST      = 28
        constant integer INVENTORY_ITEM_ID_BONUS_LIST        = 29
        //
        constant integer INVENTORY_LIST_LIST    = 40
        constant integer INVENTORY_LIST_NEXT    = 41
        constant integer INVENTORY_LIST_PREV    = 42
        constant integer INVENTORY_LIST_FIRST   = 43
        constant integer INVENTORY_LIST_LAST    = 44
  debug constant integer INVENTORY_LIST_NODES   = 45
  debug constant integer INVENTORY_LIST_LISTS   = 46
        //
        constant integer INVENTORY_AFFIX_STACK  = 50
        constant integer INVENTORY_AFFIX_DEPTH_LIST  = 51
        constant integer INVENTORY_AFFIX_DEPTH_DEPTH  = 52
        constant integer INVENTORY_AFFIX_DEPTH_RANDOM  = 53
        constant integer INVENTORY_CONSTANT_AFFIX_LIST = 54
        constant integer INVENTORY_CONSTANT_AFFIX_TYPES = 55
        constant integer INVENTORY_RANDOM_AFFIX_LIST = 56
        constant integer INVENTORY_RANDOM_AFFIX_TYPES = 57
    endglobals
 
    // Flush a child key.
    function Inventory_FlushChild takes integer parent returns nothing
        call FlushChildHashtable(HASH, parent)
    endfunction
 
    //===================================================================================
    // Global linked list.
    //===================================================================================
    globals
        private integer lists = 0
        private integer nodes = 0
    endglobals
    // It would be naive to believe everything works perfect right away ...
    // Better let the system shout at us, than ignoring fatal mistakes in code.
static if DEBUG_MODE then
    private function IsNode takes integer node returns boolean
        return HaveSavedBoolean(Inventory_GetTable(), INVENTORY_LIST_NODES, node)
    endfunction
    private function IsList takes integer list returns boolean
        return HaveSavedBoolean(Inventory_GetTable(), INVENTORY_LIST_LISTS, list)
    endfunction
endif
    function Inventory_GetListByNode takes integer node returns integer
        debug call ThrowError((node == 0),      "InventoryCore", "Inventory_ListGetListByNode", "node", node, "Attempt to read null node!")
        debug call ThrowError(not IsNode(node), "InventoryCore", "Inventory_ListGetListByNode", "node", node, "Attempt to read invalid node!")
 
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_LIST, node)
    endfunction
    private function GetList takes integer node returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_LIST, node)
    endfunction
    private function SetList takes integer node, integer list returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_LIST_LIST, node, list)
    endfunction
    //
    function Inventory_GetListFirstNode takes integer list returns integer
        debug call ThrowError((list == 0),      "InventoryCore", "Inventory_GetListFirstNode", "list", list, "Attempt to read null list!")
        debug call ThrowError(not IsList(list), "InventoryCore", "Inventory_GetListFirstNode", "list", list, "Attempt to read invalid list!")
        //
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_FIRST, list)
    endfunction
    private function GetFirst takes integer list returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_FIRST, list)
    endfunction
    private function SetFirst takes integer list, integer node returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_LIST_FIRST, list, node)
    endfunction
    //
    function Inventory_GetListLastNode takes integer list returns integer
        debug call ThrowError((list == 0),      "InventoryCore", "Inventory_GetListLastNode", "list", list, "Attempt to read null list!")
        debug call ThrowError(not IsList(list), "InventoryCore", "Inventory_GetListLastNode", "list", list, "Attempt to read invalid list!")
        //
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_LAST, list)
    endfunction
    private function GetLast takes integer list returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_LAST, list)
    endfunction
    private function SetLast takes integer list, integer node returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_LIST_LAST, list, node)
    endfunction
    //
    function Inventory_GetListNextNode takes integer node returns integer
        debug call ThrowError((node == 0),      "InventoryCore", "Inventory_GetListNextNode", "node", node, "Attempt to read null node!")
        debug call ThrowError(not IsNode(node), "InventoryCore", "Inventory_GetListNextNode", "node", node, "Attempt to read invalid node for !" + I2S(GetList(node)))
        //
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_NEXT, node)
    endfunction
    private function GetNext takes integer node returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_NEXT, node)
    endfunction
    private function SetNext takes integer node, integer nextNode returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_LIST_NEXT, node, nextNode)
    endfunction
    //
    function Inventory_GetListPrevNode takes integer node returns integer
        debug call ThrowError((node == 0),      "InventoryCore", "Inventory_GetListPrevNode", "node", node, "Attempt to read null node!")
        debug call ThrowError(not IsNode(node), "InventoryCore", "Inventory_GetListPrevNode", "node", node, "Attempt to read invalid node!")
        //
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_PREV, node)
    endfunction
    private function GetPrev takes integer node returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_LIST_PREV, node)
    endfunction
    private function SetPrev takes integer node, integer prevNode returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_LIST_PREV, node, prevNode)
    endfunction
 
    private function AllocateList takes nothing returns integer
        local integer list = GetFirst(0)
        if (0 == list) then
            // Pffffff.
            debug call ThrowError(lists == 2147483648, "InventoryCore", "AllocateList", "lists", 0, "Overflow!")
            //
            set lists = lists + 1
            set list = lists
        else
            call SetFirst(0, GetFirst(list))
        endif
        call SetFirst(list, 0)
        debug call SaveBoolean(Inventory_GetTable(), INVENTORY_LIST_LISTS, list, true)
        return list
    endfunction
    private function AllocateNode takes nothing returns integer
        local integer node = GetNext(0)
        if (0 == node) then
            // Pffffff.
            debug call ThrowError(nodes == 2147483648, "InventoryCore", "AllocateNode", "nodes", 0, "Overflow!")
            //
            set nodes = nodes + 1
            set node = nodes
        else
            call SetNext(0, GetNext(node))
        endif
        debug call SaveBoolean(Inventory_GetTable(), INVENTORY_LIST_NODES, node, true)
        return node
    endfunction
 
    function Inventory_EnqueueToList takes integer list returns integer
        local integer node = AllocateNode()
 
        debug call ThrowError((list == 0),      "InventoryCore", "Inventory_ListEnqueue", "list", list, "Attempted to enqueue on to null list.")
        debug call ThrowError(not IsList(list) ,"InventoryCore", "Inventory_ListEnqueue", "list", list, "Attempted to enqueue on to invalid list.")
 
        call SetList(node, list)
        if (0 == GetFirst(list)) then
            call SetFirst(list, node)
            call SetLast(list, node)
            call SetPrev(node, 0)
        else
            call SetNext(GetLast(list), node)
            call SetPrev(node, GetLast(list))
            call SetLast(list, node)
        endif
 
        call SetNext(node, 0)
        return node
    endfunction
 
    function Inventory_RemoveNodeFromList takes integer node returns nothing
        local integer list = GetList(node)
 
        debug call ThrowError(node == 0,        "InventoryCore", "Inventory_ListRemoveNode", "list", list, "Attempted to remove null node!")
        debug call ThrowError(not IsNode(node), "InventoryCore", "Inventory_ListRemoveNode", "list", list, "Attempted to remove invalid node [" + I2S(node) + "]!")
        debug call RemoveSavedBoolean(Inventory_GetTable(), INVENTORY_LIST_NODES, node)
        call SetList(node, 0)
        if 0 == GetPrev(node) then
            call SetFirst(list, GetNext(node))
        else
            call SetNext(GetPrev(node), GetNext(node))
        endif
        if (0 == GetNext(node)) then
            call SetLast(list, GetPrev(node))
        else
            call SetPrev(GetNext(node), GetPrev(node))
        endif
        call SetNext(node, GetNext(0))
        call SetNext(0, node)
    endfunction
 
    private function ReleaseNodes takes integer list returns nothing
        local integer node = GetFirst(list)
        local integer temp
        loop
            exitwhen 0 == node
            set temp = GetNext(node)
            call Inventory_RemoveNodeFromList(node)
            set node = temp
        endloop
    endfunction
 
    private function Inventory_DestroyList takes integer list returns nothing
        debug call ThrowError(list == 0,        "InventoryCore", "Inventory_DestroyList", "list", list, "Attempted to destroy a null list!")
        debug call ThrowError(not IsList(list), "InventoryCore", "Inventory_DestroyList", "list", list, "Attempted to destroy an invalid list!")
 
        call ReleaseNodes(list)
        if 0 != GetFirst(list) then
            call SetNext(GetLast(list), GetNext(0))
            call SetNext(0, GetFirst(list))
            call SetLast(list, 0)
        endif
 
        call SetFirst(list, GetFirst(0))
        call SetFirst(0, list)
        debug call RemoveSavedBoolean(Inventory_GetTable(), INVENTORY_LIST_LISTS, list)
    endfunction
 
    function Inventory_KeyForListExists takes integer parent, integer whichKey returns boolean
        return HaveSavedInteger(Inventory_GetTable(), parent, whichKey)
    endfunction
 
    // Returns the list or creates one.
    function Inventory_GetListByKey takes integer parent, integer whichKey returns integer
        if not HaveSavedInteger(Inventory_GetTable(), parent, whichKey) then
            call SaveInteger(Inventory_GetTable(), parent, whichKey, AllocateList())
        endif
        return LoadInteger(Inventory_GetTable(), parent, whichKey)
    endfunction
 
    function Inventory_DestroyListByKey takes integer parent, integer whichKey returns nothing
        call Inventory_DestroyList(LoadInteger(Inventory_GetTable(), parent, whichKey))
        call RemoveSavedInteger(Inventory_GetTable(), parent, whichKey)
    endfunction
 
endlibrary
JASS:
// Inventory main code. Written by BPower. Version 1.6                    
library Inventory uses InventoryCore, InventoryUserSetup, CustomItem
//===================================================================================
// 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_CREATED           = 0
        constant integer EVENT_INVENTORY_UNIT_EQUIP_ITEM   = 1
        constant integer EVENT_INVENTORY_UNIT_UNEQUIP_ITEM = 2
        constant integer EVENT_INVENTORY_UNIT_PICKUP_ITEM  = 3
        constant integer EVENT_INVENTORY_UNIT_DROP_ITEM    = 4
        constant integer EVENT_INVENTORY_UNIT_PAWN_ITEM    = 5
        constant integer EVENT_INVENTORY_UNIT_BUY_ITEM     = 6// ( Not working yet )
        constant integer EVENT_INVENTORY_UNIT_TRADE_ITEM   = 7// ( Not working yet )
 
        //=======================================================
        // 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.
    // Implemented for usage 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.
    // For most events this function is equal to Inventory_GetTriggerUnit().
    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.
    // Specified for events where Inventory_GetEventTargetUnit()
    // doesn't return the inventory source unit. ( Shop events )
    constant function Inventory_GetTriggerUnit takes nothing returns unit
        return GetTriggerInventory().source
    endfunction
 
    // Registers code to available Inventory events.
    // Condition functions don't have to return a boolean in this case.
    function RegisterInventoryEvent takes integer eventId, code func returns nothing
        if events[eventId] == null then
            set events[eventId] = CreateTrigger()
        endif
        call TriggerAddCondition(events[eventId], Condition(func))
    endfunction
 
    //=======================================================
    // Event Trigger Evaluation.
    //=======================================================
    // Under normal circumstances Inventory events don't
    // run into recursion issues. Yet the system is prepared
    // to handle such specific cases without glitches.
                                                         // Inventory instance.
    private function FireEvent takes integer id, integer index, item object, unit target returns nothing
        // Save previous data.
        local integer prevId     = eventId
        local item    prevItem   = eventItem
        local integer prevIndex  = eventIndex
        local unit    prevTarget = eventTarget
 
        // Set current data and fire.
        set eventId     = id
        set eventItem   = object
        set eventIndex  = index
        set eventTarget = target
        if IsTriggerEnabled(events[id]) then// For "null" triggers function IsTriggerEnable(t) returns "false"
            call TriggerEvaluate(events[id])// TriggerEvaluate would force disabled triggers to fire.
        endif
 
        // Restore previous data.
        set eventId     = prevId
        set eventItem   = prevItem
        set eventIndex  = prevIndex
        set eventTarget = prevTarget
        // Prevent handle leaks.
        set prevItem    = null
        set prevTarget  = null
    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 nothing
            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
        endmethod
 
        private static method onInit takes nothing returns nothing
            call RegisterInventoryEvent(EVENT_INVENTORY_CREATED, function thistype.onCreateBefore)
        endmethod
    endmodule
 
//===================================================================================
// Inventory instance referencing.
// As an inventory belongs to a unit, you can get an instance
// by calling function GetUnitInventory(unit).
//===================================================================================
 
    // Returns the Inventory instance for a unit.
    // Returns 0 for units without inventory.
    function GetUnitInventory takes unit source returns Inventory
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(source))
    endfunction
    function ShowUnitInventory takes unit source, boolean flag returns nothing
        local UIScreen screen = GetUnitInventory(source)
        if screen.exists and screen.enabled != flag then
            call screen.show(flag)
        endif
    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 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// font
            return fontType
        endmethod
        method setFont takes integer newFont returns nothing
            set fontType = newFont
        endmethod
 
        // The global user setup defines a default color for all instances,
        // you can change it any time via this.setColor(ARGB)
        private integer color
        method getColor takes nothing returns ARGB
            return color
        endmethod
        method setColor takes integer newColor returns nothing
            set color = newColor
        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 = getColor()
            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  = getColor()
            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 of the inventory 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
 
        //====================================
        // Shop Detection.
        //====================================
 
        private method getClosestShop takes nothing returns nothing
            local real dist = INVENTORY_MAXIMUM_SHOP_RANGE*INVENTORY_MAXIMUM_SHOP_RANGE + 1.
            local real x = GetUnitX(source)
            local real y = GetUnitY(source)
            local group temp = CreateGroup()
            local real dx
            local real dy
            local real d
            local unit u
 
            call GroupEnumUnitsInRange(temp, x, y, INVENTORY_MAXIMUM_SHOP_RANGE, null)
            loop
                set u = FirstOfGroup(temp)
                exitwhen u == null
                call GroupRemoveUnit(temp, u)
                                                                   // Shop purchase item ability.
                if IsUnitAlly(u, user) and GetUnitAbilityLevel(u, 'Apit') != 0 then
                    if IsUnitVisible(u, user) and not IsUnitType(u, UNIT_TYPE_DEAD) then
                        set dx = x - GetUnitX(u)
                        set dy = y - GetUnitY(u)
                        set d = dx*dx + dy*dy
                        if d < dist then
                            set dist = d
                            set shop = u
                        endif
                    endif
                endif
            endloop
            call DestroyGroup(temp)
 
            if shop != null then
                call getWindowOfType(GetMerchantTypeId()).show(true)
            endif
 
            set temp = null
        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
 
        //===============================
        // 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 getItemSlotIndex takes item whichItem returns integer
            local integer dex = UnitInventorySize(source)
            loop
                exitwhen whichItem == null
                exitwhen dex <= 0
                set dex = dex - 1
                if UnitItemInSlot(source, dex) == whichItem then
                    return dex
                endif
            endloop
            return -1
        endmethod
 
        method unitRemoveItem takes item whichItem returns boolean
            local CustomItem id = GetCustomItem(whichItem)
            local InventoryCell cell = id.getCell()
 
            // Check cell and item owner.
            if cell.exists and id.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, source)
                endif
                call cell.clear()
 
                // Place and fire. Automatically clears the item owner.
                call id.placeInMap(GetUnitX(source), GetUnitY(source))
                call FireEvent(EVENT_INVENTORY_UNIT_DROP_ITEM, this, whichItem, source)
                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 RemoveUnitItemAffixes(source, whichItem)
            call removeFx(whichItem)
            call FireEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, this, whichItem, source)
            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 AddUnitItemAffixes(source, whichItem)
            call addFx(whichItem)
            call FireEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM, this, whichItem, source)
            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 CustomItem id = GetCustomItem(whichItem)
            local thistype temp = GetUnitInventory(id.getOwner())
 
            // Invalid operation. Happens when the inventory is full.
            if (slot.getTypeId() != GetBackpackTypeId()) then
                return false
            endif
 
            // Check item.
            if id.isCustom() then
                // Check slot.
                if slot.enabled and slot.getItem() == null and slot.matchesItem(whichItem) then
    
                    // Check the previous item owner.
                    // Analogous to UnitAddItem this method
                    // must work if the owner is not this.source.
                    if temp != 0 then
                        call temp.unitRemoveItem(whichItem)// Fires all events for temp.
                    endif
    
                    call slot.moveItem(whichItem, enabled)
                    call id.setOwner(source)
                    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 IsItemStackableWithItem(target, source) then
                call CustomItem[target].mergeCharges(source)
                return true
            endif
            return false
        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 color           = Inventory_GetDefaultFontColor()
            set box             = UIBorder.create()
            set effects         = Table.create()
            //
            // Reference the inventory instance.
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_UNIT_REFERENCE, GetHandleId(sourceUnit), this)
            //
            // Init all interfaces for Inventory.
            call FireEvent(EVENT_INVENTORY_CREATED, this, null, null)
            //
            // 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
 
        //===============================================
        // Bridge Between Native And Custom Inventory UI.
        //===============================================
 
        // We must evaluate if an item can be moved between the two UIs.
        private static method preSwapCheckItem takes item i, InventoryCell slot returns boolean
            if i == null then
                return true
            elseif not IsCustomItem(i) or not IsItemCustomNativeCompatible(i) then
                return false
            endif
            return slot.matchesItem(i)
        endmethod
                                                                                          // UnitDropItemSlot
        method swapCustomNative takes InventoryCell customCell, InventoryCell nativeCell, integer slot returns boolean
            local item customItem = customCell.getItem()
            local item nativeItem = nativeCell.getItem()
            // Check all swapping conditions.
            local boolean condition = nativeCell.enabled and customCell.enabled and slot > -1
            set condition = preSwapCheckItem(customItem, nativeCell) and preSwapCheckItem(nativeItem, customCell)
 
            // All conditions have run successfully.
            if condition then
                // Free custom slot.
                if customItem != null then
                    call unitRemoveItem(customItem)
                endif
                // Free native slot and add to custom slot.
                if nativeItem != null then
                    call unitAddItemToSlot(nativeItem, customCell)
                endif
                // Add to native slot.
                call DisableTrigger(GetPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_PICKUP_ITEM))
                if UnitAddItem(source, customItem) then
                    call UnitDropItemSlot(source, customItem, slot)
                endif
                call EnableTrigger(GetPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_PICKUP_ITEM))
            endif
 
            set customItem = null
            set nativeItem = null
            return condition
        endmethod
 
        //====================================
        // Sync With The Native Inventory UI.
        //====================================
        // Calling method sync from the trigger action function,
        // will lead to false results, thus the native and
        // custom inventory UI are syncronized
        // with a 0 second timeout timer on:
        //  • EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER ( orders within the native UI ).
        //  • EVENT_PLAYER_UNIT_DROP_ITEM.
        //
        // And directly syncronized on:
        //  • EVENT_PLAYER_UNIT_PICKUP_ITEM.
        //  • ShowUnitInventory(unit, true)
        private boolean  syncReady
        private thistype syncNext  // Singly linked list.
 
        private method sync takes nothing returns nothing
            local UIWindow window = getWindowOfType(GetNativeInventoryTypeId())
            local integer dex = IMinBJ(window.getButtonCount(), UnitInventorySize(source))
            local item temp
 
            loop
                exitwhen dex <= 0
                set dex = dex - 1
                set temp = UnitItemInSlot(source, dex)
 
                if temp == null then
                    call InventoryCell(window.getButton(dex).data).clear()
                else
                    call InventoryCell(window.getButton(dex).data).moveItem(temp, enabled)
                endif
            endloop
 
            set temp = null
            set syncReady = true
        endmethod
                              // 0 seconds timer timeout.
        private static method syncWait takes nothing returns nothing
            local thistype this = thistype(0).syncNext
            call DestroyTimer(GetExpiredTimer())
 
            loop
                exitwhen 0 == this
                call sync()
                set this = syncNext
            endloop
            set thistype(0).syncNext = 0
        endmethod
 
        private static method startSync takes thistype this returns nothing
            if not exists or not enabled or not syncReady then
                return
            endif
            set syncReady = false
 
            if 0 == thistype(0).syncNext then
                call TimerStart(CreateTimer(), 0, false, function thistype.syncWait)
            endif
            set syncNext = thistype(0).syncNext
            set thistype(0).syncNext = this
        endmethod
 
        //====================================
        // Stub Method On Show.
        //====================================
        // Inventory is a child struct of UIScreen,
        // therefore it evaluates an onShow stub method
        // each time instance.show(boolean) is called.
 
        private method onShow takes boolean show returns nothing
            set shop = null
            if show then
                call sync()
                if hasWindowOfType(GetMerchantTypeId()) then
                    call getClosestShop()
                endif
            else
                call releaseTextBox()
                call hideLastMsg()
                call deselect()
            endif
        endmethod
 
        //===========================================
        // Native Unit Item Trigger Events.
        //===========================================
        // These events are essential for Inventory,
        // as items are allocated and moved as action
        // function of their event triggers.
        // Thus disabling a player unit event trigger
        // will make Inventory do nothing when the event occurs.
                              // EVENT_PLAYER_UNIT_PICKUP_ITEM.
        private static method onPickUpItem takes nothing returns nothing
            local item picked = GetManipulatedItem()
            local thistype this
 
            if GetCustomItem(picked).isCustom() then
                set this = GetUnitInventory(GetTriggerUnit())
                if exists then// Does this unit has an inventory.
 
                    if not IsItemPurchasable(picked) then
                        if not unitAddItem(picked) then
                            call iHaveNoRoom()
                            call UnitRemoveItem(source, picked)
                        endif
    
                    elseif enabled and syncReady then
                        call sync()
                    endif
                endif
            endif
 
            set picked = null
        endmethod
                              // EVENT_PLAYER_UNIT_DROP_ITEM.
        private static method onDropItem takes nothing returns nothing
            call startSync(GetUnitInventory(GetTriggerUnit()))
        endmethod
                              // EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER.
        private static method onTargetOrder takes nothing returns nothing
            if GetIssuedOrderId() > 852001 and GetIssuedOrderId() < 852008 then
                if GetOrderTargetItem() != null then
                    call GetCustomItem(GetOrderTargetItem())
                    call startSync(GetUnitInventory(GetTriggerUnit()))
                endif
            endif
        endmethod
 
        // NOT REQUIRED YET.
        private static method onPawnItem takes nothing returns nothing
        endmethod
        private static method onSellItem takes nothing returns nothing
        endmethod
        private static method onUseItem takes nothing returns nothing
        endmethod
 
        private static method init takes nothing returns nothing
            call InitSoundFiles()
 
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM,         function thistype.onPickUpItem)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PAWN_ITEM,           function thistype.onPawnItem)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM,           function thistype.onDropItem)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELL_ITEM,           function thistype.onSellItem)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM,            function thistype.onUseItem)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onTargetOrder)
        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 enable 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.getItem()
        endmethod
 
        // Called each time something happens in an Inventory.
        method update takes boolean show returns nothing
            local integer itemId = data.getItemId()
            local integer iconId = GetCustomItemIconId(itemId)
            local string  path   = GetCustomItemIconPath(itemId)
 
            if data == 0 then
                call icon.remove()
            else
                if path != "" and path != null then
                    call icon.addImage(path, show)
                elseif iconId != 0 then
                    call icon.addDest(iconId, show)
                elseif INVENTORY_ITEM_ICON_PATH != null and INVENTORY_ITEM_ICON_PATH != "" then
                    call icon.addImage(INVENTORY_ITEM_ICON_PATH, show)
                elseif INVENTORY_ITEM_ICON_ID != 0 then
                    call icon.addDest(INVENTORY_ITEM_ICON_ID, 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.getItem())
        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 (INVENTORY_ITEM_DIS_ICON_ID != 0) then
                call icon.addDest(INVENTORY_ITEM_DIS_ICON_ID, flag)
            elseif (INVENTORY_ITEM_DIS_ICON_PATH != "") and (INVENTORY_ITEM_DIS_ICON_PATH != null) then
                call icon.addImage(INVENTORY_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.getItem())
            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
JASS:
// CustomItem main code.
library CustomItem uses InventoryCore
//===================================================
// Design:
// CustomItem extends the native item handle.
// It has a "PUI" like indexing design while
// referencing items to their respective handle id.
// A cleanup check runs after each 16 items indexed.
//===================================================
//===================================================
// Events:
// On first indexing and cleanup
// a respective trigger event fires.
//  - EVENT_CUSTOM_ITEM_CREATE
//  - EVENT_CUSTOM_ITEM_DESTROY
//===================================================
//===================================================
// Restrictions:
//  - ITEM_TYPE_POWERUP items can't be CustomItems.
//  - ITEM_TYPE_PURCHASABLE items can be CustomItems,
//    but not picked into the inventory interface.
//===================================================
//====================================================
// Struct id getters:
// Use these constant functions to get the
// unique struct id of structs
// from the inventory interface.
//==================================================
// Example: GetBackpackTypeId() returns the struct id for the backpack interface.
    //! textmacro INVENTORY_STRUCT_ID_GETTER takes NAME
        constant function Get$NAME$TypeId takes nothing returns integer
            static if LIBRARY_Inventory$NAME$ then
                return Inventory$NAME$.typeid
            else
                return 0
            endif
        endfunction
    //! endtextmacro
    //
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Equipment")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Backpack")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Merchant")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Controll")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Dummy")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("Trade")
    //! runtextmacro INVENTORY_STRUCT_ID_GETTER("NativeInventory")
 
//===================================================================================
// CustomItem 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.
//
// The on destroy event only lets you get the handle id,
// but no longer the handle itself.
//===================================================================================
    globals
        //=========================================
        // Available Event Triggers.
        //=========================================
        constant trigger EVENT_CUSTOM_ITEM_CREATE  = CreateTrigger()
        constant trigger EVENT_CUSTOM_ITEM_DESTROY = CreateTrigger()// Does not allow .getItem()
    endglobals
 
    globals
        //=======================================================
        // Event Variables
        //=======================================================
        // This variable updates before an event fires.
        // Get the information through the function below.
        private integer eventIndex = 0
    endglobals
 
    //=======================================================
    // Trigger Inventory Event API.
    //=======================================================
    // The function only return its proper information when called
    // from within a registered trigger action function.
    // For incorrect usage they will return "0".
 
    // Returns the triggering CustomItem instance.
    constant function GetTriggerCustomItem takes nothing returns CustomItem
        return eventIndex
    endfunction
 
    // Registers code to available CustomItem events.
    // Condition function not have to return a boolean, as the function outsmarts PJASS.
    function RegisterCustomItemEvent takes trigger trig, code func returns nothing
        if (trig == EVENT_CUSTOM_ITEM_CREATE) or (trig == EVENT_CUSTOM_ITEM_DESTROY) then
            call TriggerAddCondition(trig, Condition(func))
        endif
    endfunction
 
    //=======================================================
    // Event Trigger Evaluation.
    //=======================================================
 
    // Fires CustomItem events while providing recursion safety.
    private function FireEvent takes trigger trig, integer index returns nothing
        local integer prevIndex = eventIndex
        set eventIndex = index
        call TriggerEvaluate(trig)
        set eventIndex = prevIndex
    endfunction
 
//===================================================================================
// ItemClass Code.
//===================================================================================
    //! runtextmacro CUSTOM_ITEM_CLASS_CODE()
 
//===================================================================================
// ItemCosts Code. ( Optional )
//===================================================================================
 
    //! runtextmacro CUSTOM_ITEM_COST_CODE("LIBRARY_GetItemCost")
//===================================================================================
// CustomItem API. Extends object editor fields.
//===================================================================================
// I reserved 10 parent keys inside the Inventory_Table for custom item data fields.
// INVENTORY_KEY_ITEM_DATA to INVENTORY_KEY_ITEM_DATA + 10.
    //==========================
    // Item Id Registration.
    //==========================
    // Items of type ITEM_TYPE_POWERUP are not allowed.
 
    // Registers an item id as CustomItem.
    function RegisterCustomItemId takes integer itemId, ItemClass class returns nothing
        debug local boolean has = HaveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA, itemId)
        debug call ThrowError((itemId == 0),           "CustomItem", "RegisterCustomItemId", "itemId", 0, "Invalid item id ( 0 )!")
        debug call ThrowError(IsItemIdPowerup(itemId), "CustomItem", "RegisterCustomItemId", "itemId", 0, GetObjectName(itemId) + " has an invalid item type for CustomItems ( ITEM_TYPE_POWERUP )!")
        debug call ThrowError(has,                     "CustomItem", "RegisterCustomItemId", "itemId", 0, "Attempt to register " + GetObjectName(itemId) + " twice!")
        //
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA, itemId, class)
    endfunction
 
    // Returns true for registered item ids.
    function IsCustomItemId takes integer itemId returns boolean
        return HaveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA, itemId)
    endfunction
    function IsCustomItem takes item whichItem returns boolean
        return IsCustomItemId(GetItemTypeId(whichItem))
    endfunction
 
    //==========================
    // Item Class Related.
    //==========================
 
    // Returns the item class for an item id.
    // Returns "0" if the item is not registered.
    function GetCustomItemIdClass takes integer itemId returns ItemClass
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA, itemId)
    endfunction
    function GetCustomItemClass takes item whichItem returns ItemClass
        return GetCustomItemIdClass(GetItemTypeId(whichItem))
    endfunction
 
    // Returns true if a given item is of ItemClass "class".
    function IsCustomItemClass takes item whichItem, ItemClass class returns boolean
        return GetCustomItemIdClass(GetItemTypeId(whichItem)) == class
    endfunction
 
    //===================================================================================
    // Icon path and DISpath, twohanded.
    //===================================================================================
 
    function GetCustomItemIconPath takes integer itemId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, itemId)
    endfunction
 
    function GetCustomItemIconDISPath takes integer itemId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, -itemId)
    endfunction
 
    function GetCustomItemIconId takes integer itemId returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, itemId)
    endfunction
 
    function GetCustomItemIconDISId takes integer itemId returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, -itemId)
    endfunction
 
    // Set item icon path and DIS path.
    function SetCustomItemIcon takes integer itemId, string filePath, string DISfilePath, integer destId, integer DISdestId returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, itemId, destId)
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, -itemId, DISdestId)
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, itemId, filePath)
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, -itemId, DISfilePath)
    endfunction
 
    // Set item twohanded.
    function IsCustomItemTwohand takes item whichItem returns boolean
        return LoadBoolean(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, GetItemTypeId(whichItem))
    endfunction
 
    function SetCustomItemTwohand takes integer itemId, boolean flag returns nothing
        call SaveBoolean(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 1, itemId, flag)
    endfunction
    //===================================================================================
    // Effects via string.
    //===================================================================================
 
    // Item based fxs.
    function GetCustomItemFxFilePath takes integer itemId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 2, itemId)
    endfunction
 
    function GetCustomItemFxPos takes integer itemId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 2, -itemId)
    endfunction
 
    function SetCustomItemFxFilePath takes integer itemId, string path, string pos returns nothing
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 2, itemId, path)
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 2, -itemId, pos)
    endfunction
 
    //===================================================================================
    // Animation properties, Effects displayed via ability.
    //===================================================================================
 
    function GetCustomItemAnimationProperty takes integer itemId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId)
    endfunction
 
    function SetCustomItemAnimationProperty takes integer itemId, string tag returns nothing
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId, tag)
    endfunction
 
    // Complex fx.
    function SetCustomItemFxAbilityId takes integer itemId, integer abilityId returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId, abilityId)
    endfunction
 
    function GetCustomItemFxAbilityId takes integer itemId returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId)
    endfunction
 
    //===================================================================================
    // Socket count from min to max. Min == 0, Max == 6
    //===================================================================================
 
    function SetCustomItemIdSockets takes integer itemId, integer min, integer max returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, -itemId, min)
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId, max)
    endfunction
 
    function GetCustomItemIdSockets takes integer itemId returns integer
        local integer min = LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, -itemId)
        local integer max = LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_DATA + 3, itemId)
        return GetRandomInt(min, max)
    endfunction
 
    //===========================
    // Useful Wrapper Functions.
    //===========================
 
    function IsItemStackableWithItem takes item a, item b returns boolean
        return GetItemCharges(a) > 0 and GetItemCharges(b) > 0 and GetItemTypeId(a) == GetItemTypeId(b)
    endfunction
 
    function IsItemPurchasable takes item whichItem returns boolean
        return (GetItemType(whichItem) == ITEM_TYPE_PURCHASABLE)
    endfunction
    function IsItemCustomNativeCompatible takes item whichItem returns boolean
        return GetItemType(whichItem) == ITEM_TYPE_CHARGED or GetItemType(whichItem) == ITEM_TYPE_PURCHASABLE
    endfunction
 
    function StackChargedItems takes item a, item b returns boolean
        local integer chargesA
        local integer chargesB
        local integer max
        if IsItemStackableWithItem(a, b) then
            set chargesA = GetItemCharges(a)
            set chargesB = GetItemCharges(b)
            set max = IMinBJ(chargesB, IMaxBJ(0, 99 - chargesA))
            call SetItemCharges(a, chargesA + max)
            if (max < chargesB) then
                call SetItemCharges(b, chargesB - max)
            else
                call RemoveItem(b)
            endif
            return true
        endif
        return false
    endfunction
 
    //===========================
    // Private Stuff.
    //===========================
 
    private function SetItemUnavailable takes item i returns nothing
        call DisableTrigger(GetPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_DROP_ITEM))
        call SetItemPosition(i, WorldBounds.minX, WorldBounds.minY)
        call EnableTrigger(GetPlayerUnitEventTrigger(EVENT_PLAYER_UNIT_DROP_ITEM))
        call SetItemVisible(i, false)
    endfunction
 
    private function AddPlayerResource takes player p, integer gold, integer lumber returns nothing
        call SetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(p, PLAYER_STATE_RESOURCE_GOLD) + gold)
        call SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER) + lumber)
    endfunction
 
    //====================================
    // Wrappers methods .....
    //====================================
    private module CustomItemExtendedAPI
 
        // Converts items to custom items on the first interaction.
        static method operator [] takes item i returns thistype
            local thistype this = GetHandleId(i)
            if not (exists) then
                return create(i)
            endif
            return this
        endmethod
 
        method getOwner takes nothing returns unit
            return LoadUnitHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this)
        endmethod
 
        method setOwner takes unit source returns nothing
            call RemoveSavedHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this)
            call SaveUnitHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this, source)
        endmethod
 
        // Not implemented. ( Item cooldowns )
        method isReady takes nothing returns boolean
            return true
        endmethod
 
        // Returns true for a total item merging.
        method mergeCharges takes item add returns boolean
            return StackChargedItems(getItem(), add)
        endmethod
 
        method getClass takes nothing returns ItemClass
            return GetCustomItemClass(getItem())
        endmethod
 
        method getQuality takes nothing returns real
            return LoadReal(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
        endmethod
 
        method setQuality takes real value returns nothing
            call SaveReal(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this, value)
        endmethod
        method getWoodCost takes nothing returns integer
            return R2I(GetItemWoodCost(getItem())*INVENTORY_SELL_ITEM_RETURN_RATE*getQuality())
        endmethod
 
        method getGoldCost takes nothing returns integer
            return R2I(GetItemGoldCost(getItem())*INVENTORY_SELL_ITEM_RETURN_RATE*getQuality())
        endmethod
 
        method setName takes string name returns nothing
            call SaveStr(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this, name)
        endmethod
 
        method getName takes nothing returns string
            if HaveSavedString(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this) then
                return LoadStr(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
            endif
            return GetItemName(getItem())
        endmethod
 
        method isItemPawnPossible takes unit seller, unit shop returns boolean
            return (seller == getOwner()) and (seller != shop) and (seller != null) and IsItemPawnable(getItem())
        endmethod
 
        method pawnItem takes unit shop returns nothing
            call AddPlayerResource(GetOwningPlayer(shop), getGoldCost(), getWoodCost())
        endmethod
 
        method isItemBuyPossible takes unit buyer, unit seller returns boolean
            local player  user = GetOwningPlayer(buyer)
            local boolean gold = GetPlayerState(user, PLAYER_STATE_RESOURCE_GOLD)   >= GetItemGoldCost(getItem())
            local boolean wood = GetPlayerState(user, PLAYER_STATE_RESOURCE_LUMBER) >= GetItemWoodCost(getItem())
            set user = null
            return (seller != getOwner()) and wood and gold and IsItemSellable(getItem())
        endmethod
 
    endmodule
//===================================================
// Core.
//===================================================
 
    struct CustomItem extends array
 
        method operator exists takes nothing returns boolean
            return HaveSavedHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
        endmethod
 
        method getItem takes nothing returns item
            return LoadItemHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
        endmethod
        method getHandle takes nothing returns item
            debug call BJDebugMsg("getHandle should not be used")
            return getItem()
        endmethod
 
        method getItemId takes nothing returns integer
            return GetItemTypeId(getItem())
        endmethod
 
        method getCell takes nothing returns integer
            return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this)
        endmethod
 
        method setCell takes integer id, boolean disable returns nothing
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this, id)
            call SaveBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this, disable)
            if (disable) then
                call SetItemUnavailable(getItem())
            endif
        endmethod
 
        method placeInMap takes real x, real y returns nothing
            if HaveSavedBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this) then
                call RemoveSavedHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this)
                call RemoveSavedBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this)
                call RemoveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this)
                call SetItemPosition(getItem(), x, y)
            //
            debug else
                debug call ThrowWarning(true, "CustomItem", "placeInMap", "protected", this, GetItemName(getItem()) + " was not protected!")
            endif
        endmethod
 
        method isCustom takes nothing returns boolean
            return HaveSavedBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
        endmethod
 
        // Removes all hashtable entries for this item.
        private method clear takes nothing returns nothing
            call RemoveSavedString(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
            call RemoveSavedBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
            call RemoveSavedHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this)
            call RemoveSavedHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this)
            call RemoveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_OWNER, this)
            call RemoveSavedInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_CELL, this)
        endmethod
 
        // Read-ability. next/prev...
        private method operator next takes nothing returns thistype
            return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_NEXT, this)
        endmethod
        private method operator prev takes nothing returns thistype
            return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_PREV, this)
        endmethod
        private method remove takes nothing returns nothing
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_NEXT, prev, next)
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_PREV, next, prev)
        endmethod
        private method add takes nothing returns nothing
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_NEXT, this, 0)
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_PREV, this, thistype(0).prev)
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_NEXT, thistype(0).prev, this)
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM_PREV, 0, this)
        endmethod
 
        private method destroy takes nothing returns nothing
            if (exists) then
                call remove()
                if isCustom() then
                    call FireEvent(EVENT_CUSTOM_ITEM_DESTROY, this)
                endif
                call clear()
            //
            debug else
                debug call ThrowWarning(not exists, "CustomItem", "deallocate", "handle", this, "Invalid deallocation process!")
            endif
        endmethod
 
        private static thistype pos
        private static method checkList takes nothing returns nothing
            local thistype this = thistype.pos
            local integer max = 1000// I don't know when we would hit the OP-limit.
            loop
                exitwhen (0 == this)
                exitwhen (0 == max)
                if (getItemId() == 0) then
                    call destroy()
                endif
                set this = next
                set max = max - 1
            endloop
            if (this != 0) then
                set thistype.pos = this
                call ForForce(bj_FORCE_PLAYER[0], function thistype.checkList)
            endif
        endmethod
        private static integer wasted = 0
        static method create takes item i returns thistype
            local thistype this = GetHandleId(i)
            local integer  itemId = GetItemTypeId(i)
            // Invalid.
            if (0 != itemId) and not IsItemPowerup(i) then
                // Cleanup what is already there.
                set wasted = wasted + 1
                if (wasted >= 16) then
                    set wasted = 0
                    set thistype.pos = thistype(0).next// First node.
                    call ForForce(bj_FORCE_PLAYER[0], function thistype.checkList)
                endif
                // Add to list.
                call add()
                // Reference.
                call SaveItemHandle(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this, i)
                call SaveReal(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this, 1.)// Quality.
                // Fire event.
                if (IsCustomItemId(itemId)) then
                    call SaveBoolean(Inventory_GetTable(), INVENTORY_KEY_CUSTOM_ITEM, this, true)
                    call FireEvent(EVENT_CUSTOM_ITEM_CREATE, this)
                endif
            endif
            //
            return this
        endmethod
 
        implement CustomItemExtendedAPI
 
    endstruct
 
    function GetCustomItem takes item i returns CustomItem
        local CustomItem id = GetHandleId(i)
        if id.exists then
            return id
        endif
        return CustomItem.create(i)
    endfunction
 
//===================================================
// Item classes.
//===================================================
// Items of a specific class can only go to a slot of that class
// or to a slot of a parent class of that specific class.
// Example: ANY > Hand > Sword.
//  --> A "Sword" class item can only go into slot declared as "Sword".
//  --> A "Hand" class item only into "Hand" and "Sword"
//  --> An "ANY" class item can go into all three slots.
//! textmacro CUSTOM_ITEM_CLASS_CODE
    struct ItemClass extends array
 
        readonly thistype parent
        readonly string   name
 
        // Every item class is based of ANY.
        static constant integer ANY = 1
        private static method init takes nothing returns nothing
            set thistype(ANY).name = "Any Class"
            set thistype(ANY).parent = thistype.ANY
        endmethod
        implement UIInit// Runs on map init. Calls thistype.init.
 
        private static integer alloc = thistype.ANY
        private static method allocate takes string text returns thistype
            local thistype this = alloc + 1
            set alloc           = this
            set name            = text
            return this
        endmethod
 
        method operator root takes nothing returns boolean
            return (thistype.ANY == integer(this))
        endmethod
 
        // Every class is a sub class of ItemClass.ANY.
        static method create takes string name returns thistype
            local thistype this = allocate(name)
            set parent          = thistype.ANY
            return this
        endmethod
 
        // Any -> Hand -> Weapon -> Sword -> Mighty Sword ....
        method createSubClass takes string name returns thistype
            local thistype node = allocate(name)
            set node.parent     = this
            return node
        endmethod
 
    endstruct
//! endtextmacro
    //! textmacro CUSTOM_ITEM_COST_CODE takes BOOL
// I was not going for performance, but accurarity.
static if not $BOOL$ then
    globals
        private integer itemId = 0  
        private unit    shop   = null
    endglobals
    private function MarkOtherItems takes nothing returns nothing
        call SaveBoolean(Inventory_GetTable(), INVENTORY_ITEM_COSTS, GetHandleId(GetEnumItem()), true)
    endfunction
 
    private function EnumItems takes nothing returns nothing  
        local item i = GetEnumItem()
        if itemId == GetItemTypeId(i) and not HaveSavedBoolean(Inventory_GetTable(), INVENTORY_ITEM_COSTS, GetHandleId(i)) then
            call SaveInteger(Inventory_GetTable(), INVENTORY_ITEM_CHARGES, itemId, GetItemCharges(i))
            call RemoveItem(i)
            set itemId = 0
        endif
        set i = null
    endfunction
 
    private function LogItem takes integer id returns nothing
        local real shopX = GetUnitX(shop)
        local real shopY = GetUnitY(shop)
        // Pepare gold and lumber.
        local integer gold = GetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_GOLD)
        local integer wood = GetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_LUMBER)
        call SetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_GOLD, 1000000)
        call SetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_LUMBER, 1000000)
        // Find other close items.
        call SetRect(UI_RECT, shopX - 1088, shopY - 1088, shopX + 1088, shopY + 1088)
        call EnumItemsInRect(UI_RECT, null, function MarkOtherItems)
        // Prepare the shop and order it to buy the item.
        call UnitAddAbility(shop, 'Asid')
        call AddItemToStock(shop, id, 1, 1)
        call IssueNeutralImmediateOrderById(UI_NEUTRAL_PLAYER, shop, id)
        call RemoveItemFromStock(shop, id)
        call UnitRemoveAbility(shop, 'Asid')
        // Find the sold item. UI_Rect is a global shared rect
        // and the buy order fires trigger events.
        // Therefore I set the rect again.
        set itemId = id
        call SetRect(UI_RECT, shopX - 1088, shopY - 1088, shopX + 1088, shopY + 1088)
        call EnumItemsInRect(UI_RECT, null, function EnumItems)
        call FlushChildHashtable(Inventory_GetTable(), INVENTORY_ITEM_COSTS)
        // Save data for this item and restore the player state.
        call SaveInteger(Inventory_GetTable(), INVENTORY_ITEM_GOLD_COST, id, 1000000 - GetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_GOLD))
        call SaveInteger(Inventory_GetTable(), INVENTORY_ITEM_WOOD_COST, id, 1000000 - GetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_LUMBER))
        call SetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_GOLD, gold)
        call SetPlayerState(UI_NEUTRAL_PLAYER, PLAYER_STATE_RESOURCE_LUMBER, wood)
    endfunction
 
    function GetItemTypeIdGoldCost takes integer id returns integer
        if not HaveSavedInteger(Inventory_GetTable(), INVENTORY_ITEM_GOLD_COST, id) then
            call LogItem(id)
        endif
        return LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_GOLD_COST, id)
    endfunction
 
    function GetItemTypeIdWoodCost takes integer id returns integer
        if not HaveSavedInteger(Inventory_GetTable(), INVENTORY_ITEM_WOOD_COST, id) then
            call LogItem(id)
        endif
        return LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_WOOD_COST, id)
    endfunction
 
    function GetItemTypeIdCharges takes integer id returns integer
        if not HaveSavedInteger(Inventory_GetTable(), INVENTORY_ITEM_CHARGES, id) then
            call LogItem(id)
        endif
        return LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_CHARGES, id)
    endfunction
 
    function GetItemGoldCost takes item i returns integer
        local integer id = GetItemTypeId(i)
        local real s2 = GetItemTypeIdCharges(id)
        if (s2 > 0) then
            return R2I(LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_GOLD_COST, id)*(GetItemCharges(i)/s2))
        endif
        return LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_GOLD_COST, id)
    endfunction
 
    function GetItemWoodCost takes item i returns integer
        local integer id = GetItemTypeId(i)
        local real s2 = GetItemTypeIdCharges(id)
        if (s2 > 0) then
            return R2I(LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_WOOD_COST, id)*(GetItemCharges(i)/s2))
        endif
        return LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_WOOD_COST, id)
    endfunction
 
    private struct I extends array
        private static method init takes nothing returns nothing
            local boolean prev = ToogleUnitIndexer(false)
            set shop = CreateUnit(UI_NEUTRAL_PLAYER, 'hpea', 0, 0, 0)
            call ToogleUnitIndexer(prev)
            call ShowUnit(shop, false)
            call UnitAddAbility(shop, 'Aloc')
        endmethod
        implement UIInit
    endstruct
endif
//! endtextmacro
endlibrary
JASS:
library CustomItemAffix uses ErrorMessage, InventoryCore
//===================================================================================
// CustomItemAffix rolls random affixes for custom items.
//===================================================================================
 
    // Read-ability. ( in item parser )
    function ItemHasRandomAffixes takes integer itemId returns boolean
        return HaveSavedBoolean(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, itemId)
    endfunction
    function ItemHasConstantAffixes takes integer itemId returns boolean
        return HaveSavedBoolean(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, itemId)
    endfunction
    function ItemRollsAffixes takes integer itemId returns boolean
        return ItemHasRandomAffixes(itemId) or ItemHasConstantAffixes(itemId)
    endfunction
 
    private function SearchNodeMatch takes integer parent, integer list, integer match returns integer
        local integer node = Inventory_GetListFirstNode(list)
 
        loop
            exitwhen 0 == node
            if LoadInteger(Inventory_GetTable(), parent, node) == match then
                return node
            endif
            set node = Inventory_GetListNextNode(node)
        endloop
 
        return -1
    endfunction
                                                           // Start of with 0.
    function SetItemIdAffixRollRange takes integer itemId, integer depth, integer minimum, integer maximum returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_AFFIX_DEPTH_LIST, itemId)
        local integer node = SearchNodeMatch(INVENTORY_AFFIX_DEPTH_DEPTH, list, depth)
 
        if (node == -1) then
            set node = Inventory_EnqueueToList(list)
        endif
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_DEPTH_DEPTH, node, depth)
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_DEPTH_RANDOM, -node, minimum)
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_DEPTH_RANDOM, node, IMaxBJ(minimum, maximum))
    endfunction
    function AddItemIdConstantAffix takes integer itemId, string affixType, integer affixId, real min, real max returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_CONSTANT_AFFIX_LIST, itemId)
        local integer node = Inventory_EnqueueToList(list)
 
        call SaveStr(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, node, affixType)         // Identifier
        call SaveReal(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, -node, min)             // Min
        call SaveReal(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, node, RMaxBJ(min, max)) // Max
        call SaveInteger(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, node, affixId)       // Id
 
        call SaveBoolean(Inventory_GetTable(), INVENTORY_CONSTANT_AFFIX_TYPES, itemId, true)        // System knows item id.
   endfunction
 
    function AddItemIdRandomAffix takes integer itemId, integer depth, string affixType, integer affixId, real min, real max returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_RANDOM_AFFIX_LIST, itemId)
        local integer node = Inventory_EnqueueToList(list)
 
        call SaveStr(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, node, affixType)
        call SaveReal(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, -node, min)
        call SaveReal(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, node, RMaxBJ(min, max))
        call SaveInteger(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, -node, depth)          // Loop index.
        call SaveInteger(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, node, affixId)
 
        call SaveBoolean(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, itemId, true)
   endfunction
 
//===================================================================================
// Internal API
//===================================================================================
 
    globals
        private string  affixType = null
        private real    quality   = 0
        private real    rolled    = 0
        private integer affixId   = 0
    endglobals
 
    function PrepareRandomAffixRoll takes integer itemId, integer depth returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_RANDOM_AFFIX_LIST, itemId)
        local integer node = Inventory_GetListFirstNode(list)
        local integer size = 0
 
        call Inventory_FlushChild(INVENTORY_AFFIX_STACK)
 
        loop
            exitwhen node == 0
            if LoadInteger(Inventory_GetTable(), INVENTORY_RANDOM_AFFIX_TYPES, -node) == depth then
                call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, size, node)
                set size = size + 1
            endif
 
            set node = Inventory_GetListNextNode(node)
        endloop
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -1, list)
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -2, size)
    endfunction
    function PrepareConstantAffixRoll takes integer itemId returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_CONSTANT_AFFIX_LIST, itemId)
        local integer node = Inventory_GetListFirstNode(list)
        local integer size = 0
 
        call Inventory_FlushChild(INVENTORY_AFFIX_STACK)
 
        loop
            exitwhen node == 0
            call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, size, node)
            set size = size + 1
            set node = Inventory_GetListNextNode(node)
        endloop
 
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -1, list)
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -2, size)
    endfunction
 
    private function Roll takes integer parent returns boolean
        local integer list = LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -1)
        local integer size = LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -2) - 1
        local integer node
        local real min
        local real max
        local integer roll
        local integer trunc
        if (size < 0) then
            return false
        endif
        if parent == INVENTORY_RANDOM_AFFIX_TYPES then
            set roll = GetRandomInt(0, size)
        else
            set roll = 0
        endif
        set node = LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, roll)
        set min = LoadReal(Inventory_GetTable(), parent, -node)
        set max = LoadReal(Inventory_GetTable(), parent, node)
        set trunc = R2I(GetRandomReal(min, max) + .5)
 
        set affixId = LoadInteger(Inventory_GetTable(), parent, node)
        set affixType = LoadStr(Inventory_GetTable(), parent, node)
        set rolled = trunc
 
        if (max != 0) then
            set quality = rolled/max
        else
            set quality = 1
        endif
 
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, roll, LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, size))
        call SaveInteger(Inventory_GetTable(), INVENTORY_AFFIX_STACK, -2, size)
        return true
    endfunction
 
    function RollRandomAffix takes nothing returns boolean
        return Roll(INVENTORY_RANDOM_AFFIX_TYPES)
    endfunction
 
    function RollConstantAffix takes nothing returns boolean
        return Roll(INVENTORY_CONSTANT_AFFIX_TYPES)
    endfunction
 
    function AffixTableHasLoopDepth takes integer itemId, integer depth returns boolean
        local integer list = Inventory_GetListByKey(INVENTORY_AFFIX_DEPTH_LIST, itemId)
        return (SearchNodeMatch(INVENTORY_AFFIX_DEPTH_DEPTH, list, depth) != -1)
    endfunction
 
    function GetAffixMaxCount takes integer itemId, integer depth returns integer
        local integer list = Inventory_GetListByKey(INVENTORY_AFFIX_DEPTH_LIST, itemId)
        local integer node = SearchNodeMatch(INVENTORY_AFFIX_DEPTH_DEPTH, list, depth)
        return LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_DEPTH_RANDOM, node)
    endfunction
    private function GetAffixMinCount takes integer itemId, integer depth returns integer
        local integer list = Inventory_GetListByKey(INVENTORY_AFFIX_DEPTH_LIST, itemId)
        local integer node = SearchNodeMatch(INVENTORY_AFFIX_DEPTH_DEPTH, list, depth)
        return LoadInteger(Inventory_GetTable(), INVENTORY_AFFIX_DEPTH_RANDOM, -node)
    endfunction
 
    function RollAffixCount takes integer itemId, integer depth returns integer
        return GetRandomInt(GetAffixMinCount(itemId, depth), GetAffixMaxCount(itemId, depth))
    endfunction
 
    constant function GetRolledAffixId takes nothing returns integer
        return affixId
    endfunction
    constant function GetRolledAffixType takes nothing returns string
        return affixType
    endfunction
    constant function GetRolledAffixAmount takes nothing returns real
        return rolled
    endfunction
    constant function GetRolledAffixQuality takes nothing returns real
        return quality
    endfunction
endlibrary
JASS:
library CustomItemName initializer Init uses InventoryCore
//===================================================================================
// CustomItemName is able to generate
// random names for custom items, if
// the following conditions match:
//  1.) The item must be of type ITEM_TYPE_PERMANENT.
//  2.) The item must roll random affixes.
//===================================================================================
// Factors which influence the naming are:
//  1.) Item quality. ( In range of 0% - 100% )
//  2.) Strongest rolled affix.
//===================================================================================
// An item name is composed of
// prefix + GetItemName(item) + suffix.
// You can define all suffixes and prefixes in
// the Inventory User Setup.
//
// In case the strongest rolled affix is
// an ability id, the item name will become:
// GetPrefix(abilityId) + GetItemName(item) + of + GetObjectName(abilityId)
//===================================================================================
 
    private function SetPrefix takes string prefix, integer affix, integer min, integer max returns nothing
        set affix = affix*101// I wonder if we can hit a limit sequence here. abilityId*101
        // Grrr
        set min = IMaxBJ(0, min)
 
        loop
            exitwhen (min > max)
            exitwhen (min > 100)
            call SaveStr(Inventory_GetTable(), INVENTORY_ITEM_NAME, affix + min, prefix)
            set min = min + 1
        endloop
    endfunction
 
    private function SetSuffix takes string suffix, integer affix, integer min, integer max returns nothing
        set affix = affix*101
        set min = IMaxBJ(0, min)
 
        loop
            exitwhen (min > max)
            exitwhen (min > 100)
            call SaveStr(Inventory_GetTable(), INVENTORY_ITEM_NAME, -affix - min, suffix)
            set min = min + 1
        endloop
    endfunction
 
    //===================================================================================
    // Init prefixes and suffixes.
    //===================================================================================
 
    private function Init takes nothing returns nothing
        //! runtextmacro ITEM_PARSER_RANDOM_ITEM_NAME()
    endfunction
    //===================================================================================
    // Naming. Called from the item parser.
    //===================================================================================
 
    // For read-ability.
    private function GetSuffix takes integer affix, integer quality returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_ITEM_NAME, -(affix*101) - quality)
    endfunction
    private function GetPrefix takes integer affix, integer quality returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_ITEM_NAME, affix*101 + quality)
    endfunction
 
    function GenerateCustomItemName takes item whichItem, integer affix, real quality returns string
        local integer value = R2I(quality + .5)
        if value > 100 then
            set value = 100
        elseif value < 0 then
            set value = 0
        endif
 
        if (GetItemType(whichItem) != ITEM_TYPE_PERMANENT) then
            return GetItemName(whichItem)
 
               // Probably an ability id.
        elseif affix > 8910 then
            return GetPrefix(affix, 0) + GetItemName(whichItem) + " of " + GetObjectName(affix)
        endif
        // Bonus.
        return GetPrefix(affix, value) + " " + GetItemName(whichItem) + " " + GetSuffix(affix, value)
    endfunction
endlibrary
JASS:
library CustomItemParser initializer InitSuffixes uses CustomItem, InventoryUserSetup, CustomItemName, CustomItemAffix, CustomItemAffixList
//===================================================================================
// This library parses each new created CustomItem
// to create the bonus and ability lists.
// Furthermore it prepares the final item tooltip.
//===================================================================================
//===================================================================================
// Bonus tooltip suffixes.
//===================================================================================
    // Listed for read-ability.
    // Define a suffix for an affixId.
    private function SetSuffix takes integer affixId, string text returns nothing
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER_SUFFIX, affixId, text)
    endfunction
    private function GetSuffix takes integer affixId returns string
        return LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER_SUFFIX, affixId)
    endfunction
 
    // Init you affix suffixes here.
    private function InitSuffixes takes nothing returns nothing
        //! runtextmacro optional ITEM_PARSER_ITEM_TOOLTIP_SUFFIXES("INVENTORY_ENABLE_DYNAMIC_ITEMS")
    endfunction
//===================================================================================
// Writing the item tooltip.
//===================================================================================
    // Imports all fragments defined for that specific item id.
    private function ImportConstantTooltipFragments takes CustomItem id returns nothing
        local integer itemId = id.getItemId()
        local integer dex = 0
        local integer end = GetItemTooltipSize(itemId)
        local integer pos
        loop
            exitwhen (dex == end)
            set pos = GetItemTooltipFragmentPointer(itemId, dex)
            call SetItemTooltipFragment(id, GetItemTooltipPointerSize(id, pos), GetItemTooltipFragment(itemId, dex), GetItemTooltipFragmentIcon(itemId, dex), pos)
            set dex = dex + 1
        endloop
        call GenerateItemTooltip(id)
    endfunction
 
    // Imports all fragments from the ability and bonus list of an item handle.
    // Ability list child key: -id
    // Bonus list child key: id
    private function ImportDynamicTooltipFragments takes CustomItem id returns nothing
        local string prefix = "|cff99b4d1+"
        local string icon = "UI\\Minimap\\MinimapIconCreepLoc2.blp"
        local string text = null
 
        // Access the bonus list.
        local integer size = GetItemTooltipPointerSize(id, TOOLTIP_POINTER_BONUS_LIST)
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_ID_BONUS_LIST, id)
        local integer node = Inventory_GetListFirstNode(list)
        local integer affix
        local integer amount
        loop
            exitwhen node == 0
            set affix = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_ID_AFFIXES, node)
            set amount = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_ID_AFFIXES, -node)
            set text = prefix + I2S(amount) + "|r " + GetSuffix(affix)
 
            call SetItemTooltipFragment(id, size, text, icon, TOOLTIP_POINTER_BONUS_LIST)
 
            set size = size + 1
            set node = Inventory_GetListNextNode(node)
        endloop
        // Access the ability list.
        set size = GetItemTooltipPointerSize(id, TOOLTIP_POINTER_ABILITY_LIST)
        set list = Inventory_GetListByKey(INVENTORY_ITEM_ID_ABILITY_LIST, id)
        set node = Inventory_GetListFirstNode(list)
 
        loop
            exitwhen node == 0
            set affix = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_ID_AFFIXES, node)
            set amount = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_ID_AFFIXES, -node)
            set text = prefix + I2S(amount) + " to|r " + GetObjectName(affix)
 
            call SetItemTooltipFragment(id, size, text, icon, TOOLTIP_POINTER_ABILITY_LIST)
 
            set size = size + 1
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
    function UpdateCustomItemTooltip takes item whichItem returns nothing
        local CustomItem id = CustomItem[whichItem]
        if not id.exists then
            return
        endif
        // Clear what was written before.
        call FlushItemTooltip(id)
        // Get what should always be there.
        call ImportConstantTooltipFragments(id)
        // Get what is different per handle id.
        call ImportDynamicTooltipFragments(id)
        // Write it down.
        call GenerateItemTooltip(id)
    endfunction
 
//===================================================================================
// Dynamic item generation.
// This code block is responsible for
// items with random rolled affixes.
//===================================================================================
// Child key offsets used:
// 0x186A0  - Rolled affix quality, compared to the maximum possible rolled value.
// 0xF4240  - In PrepareParser, getters and setters below.
//
// Item quality is combined of:
//  1.) rolled value/maximum possible value
//  2.) rolled affix count/maximum possible affix count.
    private function PrepareParser takes item forItem returns nothing
        call Inventory_FlushChild(INVENTORY_KEY_ITEM_PARSER)
        //
        call SaveItemHandle(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240, forItem)// Item to parse.
        call SaveReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240, 1.)           // Item quality.
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240, 0)         // Stack size.
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, -0xF4240, 1)        // Item quality divisor.
    endfunction
    // For read-ability only.
    private function SetStackSize takes integer size returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240, size)
    endfunction
    private function GetStackSize takes nothing returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240)
    endfunction
    private function SetQualityDivisor takes integer new returns nothing
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, -0xF4240, new)
    endfunction
    private function GetQualityDivisor takes nothing returns integer
        return LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, -0xF4240)
    endfunction
    private function SetQuality takes real new returns nothing
        call SaveReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240, new)
    endfunction
    private function GetQuality takes nothing returns real
        return LoadReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240)
    endfunction
    private function GetItem takes nothing returns item
        return LoadItemHandle(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0xF4240)
    endfunction
 
    // By default the item quality is 1.
    private function MeasureQuality takes nothing returns nothing
        local CustomItem id = GetHandleId(GetItem())
        local integer affixType = StringHash(LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0))
        local integer affixId = LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0)
        local real quality = LoadReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0)
        //
        if (affixId != 0) then
            set quality = (quality + GetQuality()/GetQualityDivisor())/2
            //
            call id.setQuality(quality)
            call id.setName(GenerateCustomItemName(id.getItem(), affixId, quality))
        endif
    endfunction
 
    // Registers what was rolled.
    private function RegisterAffixes takes nothing returns nothing
        local item temp = GetItem()
        //
        debug local string error = "[" + GetItemName(temp) + "] Missing requirement: Can't register"
        debug local string valid = "Valid affix string indentifiers are 'Bonus' or 'Ability'"
        //
        // Prepare type identifiers.
        local integer bonus = StringHash("Bonus")
        local integer skill = StringHash("Ability")
        local integer dex   = 0
        local integer end   = GetStackSize()
        //
        local integer affixType
        local real    affixAmount
        local integer affixId
        local integer trunc
        //
        // Start.
        loop
            exitwhen (dex >= end)
            set affixType   = StringHash(LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, dex))
            set affixAmount = LoadReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, dex)
            set affixId     = LoadInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, dex)
 
            // Check for Bonuses.
            if (affixType == bonus) then
                          // Earth-Fury's BonusMod library.
                static if LIBRARY_BonusMod then
                    set trunc = R2I(affixAmount)// Pretty solid.
                    if (trunc != 0) then
                        call AddCustomItemBonus(temp, affixId, trunc)
                    endif
                 //
                 // BonusMod is not present, but bonuses are registered by the user.
                 debug else
                    debug call ThrowWarning(true, "CustomItemParser", "RegisterAffixes", "BonusMod", 0, error + " bonuses without library BonusMod!")
                endif
            //
            // Check for native abilities.
            elseif (affixType == skill) then
                set trunc = R2I(affixAmount)
                if (trunc != 0) then
                    call AddCustomItemAbility(temp, affixId, trunc)
                endif
 
            //
            // Invalid string identifier.
            debug else
                debug call ThrowWarning(true, "CustomItemParser", "RegisterAffixes", "affixType", 0, "Can't identify affix type! [" + LoadStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, dex) + "]! \n" + valid)
            endif
            //
 
            set dex = dex + 1
        endloop
        set temp = null
    endfunction
 
    private function SaveData takes nothing returns nothing
        local integer stackSize = GetStackSize()
        local real quality = GetRolledAffixQuality()
        local real bestQuality = LoadReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0)
        //
        call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, stackSize, GetRolledAffixId())
        call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, stackSize, GetRolledAffixType())
        call SaveReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, stackSize, GetRolledAffixAmount())
        //
        // Detect the best rolled affix so far.
        if (quality > bestQuality) or (bestQuality == 0) or ((quality == bestQuality) and (GetRandomInt(0, 1) == 0)) then
            call SaveReal(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0, quality)
            call SaveStr(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0, GetRolledAffixType())
            call SaveInteger(Inventory_GetTable(), INVENTORY_KEY_ITEM_PARSER, 0x186A0, GetRolledAffixId())
        endif
        //
        call SetStackSize(stackSize + 1)
    endfunction
 
    // Randomizes affixes for item handles.
    private function RollAffixes takes nothing returns nothing
        local item temp = GetItem()
        local integer itemId = GetItemTypeId(temp)
        local integer depth
        local integer count
        local integer max
        //
        // Roll all constant affixes.
        if ItemHasConstantAffixes(itemId) then
            call PrepareConstantAffixRoll(itemId)
            loop
                exitwhen not RollConstantAffix()
                call SaveData()
            endloop
        endif
        //
        // Roll random affixes of all loop instances.
        if ItemHasRandomAffixes(itemId) then
            set depth = 0
            loop
                exitwhen not AffixTableHasLoopDepth(itemId, depth)
                set count = RollAffixCount(itemId, depth)
 
                // Quality measuring. Rolled vs. maximum possible affixes.
                set max = GetAffixMaxCount(itemId, depth)
                if (max > 0) then
                    call SetQuality(GetQuality() + count/max)
                    call SetQualityDivisor(GetQualityDivisor() + 1)
                endif
                //
                call PrepareRandomAffixRoll(itemId, depth)
                loop
                    exitwhen (count <= 0)
                    exitwhen not RollRandomAffix()
                    call SaveData()
                    set count = count - 1
                endloop
                set depth = depth + 1
            endloop
        endif
        set temp = null
    endfunction
    private function ParseItem takes item temp returns nothing
        call PrepareParser(temp)
        //
        call ForForce(bj_FORCE_PLAYER[0], function RollAffixes)
        call ForForce(bj_FORCE_PLAYER[0], function RegisterAffixes)
        call ForForce(bj_FORCE_PLAYER[0], function MeasureQuality)
        //
        // Flushes literally everything the parser saved
        // during the three function calls above!
        call Inventory_FlushChild(INVENTORY_KEY_ITEM_PARSER)
    endfunction
 
//===================================================================================
// Event response for custom items.
//===================================================================================
 
                     // EVENT_CUSTOM_ITEM_CREATE.
    private function OnCreate takes nothing returns nothing
        local item temp = GetTriggerCustomItem().getItem()
        local integer itemId = GetItemTypeId(temp)
        //
        // Transfers all static affixes registered on the item id
        // to the handle id of temp.
        call ImportCustomItemAffixes(temp)
        //
        // Global condition for dynamic items.
        if ItemRollsAffixes(itemId) then
            call ParseItem(temp)
        endif
        call UpdateCustomItemTooltip(temp)
 
        set temp = null
    endfunction
 
                     // EVENT_CUSTOM_ITEM_DESTROY
    private function OnDestroy takes nothing returns nothing
        local CustomItem id = GetTriggerCustomItem()
        call FlushItemTooltip(id)
        call FlushCustomItemAffixes(id)
    endfunction
 
    private struct I extends array
        private static method init takes nothing returns nothing
            call RegisterCustomItemEvent(EVENT_CUSTOM_ITEM_CREATE,  function OnCreate)
            call RegisterCustomItemEvent(EVENT_CUSTOM_ITEM_DESTROY, function OnDestroy)
        endmethod
        implement UIInit
    endstruct
endlibrary
JASS:
library CustomItemTooltip/* v1.0
*************************************************************************************
*
*   CustomItemTooltip handles tooltips for the "CustomItem" type.
*   Each tooltip is stored in lines and can be loaded row by row.
*
*   Exampe:
*       row 1 - "Short Axe"
*       row 2 - " "
*       row 3 - "+5 Damage"
*       row 4 - " "
*       row 5 - "Sell Value 75 Gold"
*
*************************************************************************************
*
*   */ uses /*
*
*       */ optional Ascii        /*
*       */ optional WordWrap     /*
*       */ optional StringSize   /*
*       */ optional ErrorMessage /*
*
************************************************************************************
*/
 
    //*  Pointers:
    //*  ==============
    //*  Pointers point to a segment of the tooltip.
    //* You can use them to change, add, remove rows within the tooltip,
    //* while obtaining the structure of the whole tooltip.
    //* You can also flush a whole pointer, if it shouldn't be in the tooltip anymore.
    //*
    //* Pointers start with the index 1 and end with x.
    //* This list from 1 to 9 is just an example. Use as many as you need.
    //* Give them any name, which fits to your logic.
    globals
        //*  Always start with 1! 0 will not work.
        constant integer TOOLTIP_POINTER_HEADLINE      = 1
        constant integer TOOLTIP_POINTER_RUNE          = 2
        constant integer TOOLTIP_POINTER_BONUS_LIST    = 3// ( Hardcoded inside the item parser )
        constant integer TOOLTIP_POINTER_ABILITY_LIST  = 4// ( Hardcoded inside the item parser )
        constant integer TOOLTIP_POINTER_SOCKETS       = 5
        constant integer TOOLTIP_POINTER_ITEM_SET      = 6
        constant integer TOOLTIP_POINTER_STORY         = 7
        constant integer TOOLTIP_POINTER_DURABILITY    = 8
        constant integer TOOLTIP_POINTER_REQUIREMENTS  = 9
    endglobals
    //*  Structure:
    //*  ==========
    //*  The system loops over all pointers, so please define,
    //*  which pointer is the first and which is the last.
    globals
        constant integer ITEM_TOOLTIP_FIRST_POINTER   = 1
        constant integer ITEM_TOOLTIP_LAST_POINTER    = 9
    endglobals
 
    //*  CustomizePointers:
    //*  ==================
    //*  Here you can define, if a healine should be added between to pointers.
    //* Furthermore you can set if no space should be added between to pointers.
    //* Only applies if the tooltip has a value for the specified pointer.
    globals
        private string  array headline //*  By default no headline text is added between to pointers.
        private boolean array nospace  //*  By default the system adds a space between to pointers.
    endglobals
 
    private function CustomizePointers takes nothing returns nothing
        set nospace[TOOLTIP_POINTER_ABILITY_LIST] = true//*  In the example no space between primary affix and affixes is added.
        set headline[TOOLTIP_POINTER_BONUS_LIST] = "Primary:"
        set headline[TOOLTIP_POINTER_ABILITY_LIST] = "Secondary:"
        // ....
    endfunction
 
    //*  Here starts the CustomItemTooltip code.
    //*  =======================================
    globals
        private constant hashtable TABLE   = InitHashtable()
        private constant integer   OFFSET = 0x64   //* Number of high value. Available rows per pointer.
        private constant integer   ICONS  = 0x186A0//* Number of higher value. Plus one dimension for icons.
    endglobals
 
    //*  API:
    //*  ====
    //*
    //*  Get:
    //* The total amount of lines.
    function GetItemTooltipSize takes integer itemIndex returns integer
        return LoadInteger(TABLE, 0, itemIndex)
    endfunction
    //* A single line.
    function GetItemTooltipFragment takes integer itemIndex, integer pos returns string
        return LoadStr(TABLE, itemIndex, pos)
    endfunction
    //* A Icon for the specified line.
    function GetItemTooltipFragmentIcon takes integer itemIndex, integer pos returns string
        return LoadStr(TABLE, itemIndex, pos + ICONS)
    endfunction
    //* Pointer used in the line.
    function GetItemTooltipFragmentPointer takes integer itemIndex, integer pos returns integer
        return LoadInteger(TABLE, itemIndex, pos + ICONS)
    endfunction
    function GetItemTooltipPointerSize takes integer itemIndex, integer pointer returns integer
        return LoadInteger(TABLE, itemIndex, pointer)
    endfunction
    function GetItemTooltipMaxWidth takes integer itemIndex returns real
        return LoadReal(TABLE, 0, itemIndex)
    endfunction
    //*
    //*  Set:
    //* A text in a line. Uses pointers.
    function SetItemTooltipFragment takes integer itemIndex, integer pos, string text, string icon, integer pointer returns nothing
        local integer size = IMaxBJ(LoadInteger(TABLE, itemIndex, pointer), pos + 1)
        call SaveStr(TABLE, itemIndex, pos + pointer*OFFSET, text)
        call SaveStr(TABLE, itemIndex, pos + pointer*OFFSET + ICONS, icon)
        call SaveInteger(TABLE, itemIndex, pointer, size)
    endfunction
    //* Word wrap a whole text block to a tooltip.
    function WordWrapStringToItemTooltip takes integer itemIndex, string text, real stringWidth, integer pointer returns nothing
        static if LIBRARY_WordWrap then
            local integer index = 0
            call WordWrapString(text, stringWidth, true)
            loop
                exitwhen index == GetWrappedStringCount()
                call SetItemTooltipFragment(itemIndex, index, GetWrappedStringFragment(index), null, pointer)
                set index = index + 1
            endloop
        endif
    endfunction
    //*
    //*  Remove:
    //* A single line of a specific pointer
    function RemoveItemTooltipFragment takes integer itemIndex, integer pos, integer pointer returns nothing
        call RemoveSavedString(TABLE, itemIndex, pos + pointer*OFFSET)
        call RemoveSavedString(TABLE, itemIndex, pos + pointer*OFFSET + ICONS)
    endfunction
    //* All lines of a specific pointer.
    function FlushItemTooltipPointer takes integer itemIndex, integer pointer returns nothing
        local integer index = 0
        local integer size  = LoadInteger(TABLE, itemIndex, pointer)
        loop
            exitwhen (index == size)
            call RemoveSavedString(TABLE, itemIndex, index + pointer*OFFSET)
            call RemoveSavedString(TABLE, itemIndex, index + pointer*OFFSET + ICONS)
            set index = index + 1
        endloop
        call RemoveSavedInteger(TABLE, itemIndex, pointer)
    endfunction
    //*
    //*  Flush:
    //* A complete tooltip
    function FlushItemTooltip takes integer itemIndex returns nothing
        call RemoveSavedInteger(TABLE, 0, itemIndex)
        call FlushChildHashtable(TABLE, itemIndex)
    endfunction
    //*
    //* Clear: Not as radical as flush.
    private function ClearTooltip takes integer itemIndex returns nothing
        local integer size  = LoadInteger(TABLE, 0, itemIndex)
        local integer index = 0
        loop
            exitwhen index == size
            call RemoveSavedString(TABLE, itemIndex, index)
            call RemoveSavedString(TABLE, itemIndex, index + ICONS)
            call RemoveSavedInteger(TABLE, itemIndex, index + ICONS)
            set index = index + 1
        endloop
    endfunction
 
    //*  Average char length without library StringSize is set to 13.
    private function Concatenate takes integer itemIndex returns nothing
        local integer pointer = ITEM_TOOLTIP_FIRST_POINTER
        local integer size    = 0
        local integer line
        local integer lines
        local boolean entry
        local string  text
        local real    length  = 0.
 
        //*  Let's start!
        loop
            exitwhen (pointer > ITEM_TOOLTIP_LAST_POINTER)
 
            //*  Strings per pointer.
            set line  = 0
            set lines = LoadInteger(TABLE, itemIndex, pointer)
            set entry = (lines > 0)
 
            if (entry) and not (pointer == ITEM_TOOLTIP_LAST_POINTER) and (size != 0) then
 
                //*  Space?
                if not (nospace[pointer]) or (LoadInteger(TABLE, itemIndex, pointer - 1) == 0) then
                    call SaveStr(TABLE, itemIndex, size, " ")
                    call SaveInteger(TABLE, itemIndex, size + ICONS, 0)
                    set size  = size + 1
                endif
            endif
 
            //*  Headline.
            if (entry) and (headline[pointer] != null) then
                set text = headline[pointer]
                static if LIBRARY_StringSize then
                    set length = RMaxBJ(MeasureString(text), length)
                else
                    set length = RMaxBJ(StringLength(text)*13, length)
                endif
                call SaveStr(TABLE, itemIndex, size, text)
                call SaveInteger(TABLE, itemIndex, size + ICONS, 0)
                set size = size + 1
            endif
 
            //*  Tooltip block of "pointer".
            loop
                exitwhen (line == lines)
                if HaveSavedString(TABLE, itemIndex, line + pointer*OFFSET) then
                    //*  Text and icon.
                    set text = LoadStr(TABLE, itemIndex, line + pointer*OFFSET)
                    static if LIBRARY_StringSize then
                        set length = RMaxBJ(MeasureString(text), length)
                    else
                        set length = RMaxBJ(StringLength(text)*13, length)
                    endif
                    call SaveStr(TABLE, itemIndex, size, text)
                    call SaveStr(TABLE, itemIndex, size + ICONS, LoadStr(TABLE, itemIndex, line + pointer*OFFSET + ICONS))
    
                    //*  Which type of pointer.
                    call SaveInteger(TABLE, itemIndex, size + ICONS, pointer)
                    set size = size + 1
                else
                    //*  Null string is ignored.
                endif
                set line = line + 1
            endloop
            set pointer = pointer + 1
        endloop
 
        //*  Total string size:
        call SaveInteger(TABLE, 0, itemIndex, size)
        call SaveReal(TABLE,    0, itemIndex, length)
    endfunction
 
    function GenerateItemTooltip takes integer itemIndex returns nothing
        call ClearTooltip(itemIndex)
        call Concatenate(itemIndex)
    endfunction
 
    //*  Seriously?
    private module Inits
        private static method onInit takes nothing returns nothing
            static if LIBRARY_ErrorMessage then
                debug call ThrowError(ITEM_TOOLTIP_FIRST_POINTER <= 0,           "CustomItemTooltip", "Init", "FIRST_POINTER", ITEM_TOOLTIP_FIRST_POINTER, "ITEM_TOOLTIP_FIRST_POINTER has to be bigger than 0.")
                debug call ThrowError(ITEM_TOOLTIP_LAST_POINTER < ITEM_TOOLTIP_FIRST_POINTER, "CustomItemTooltip", "Init", "LAST_POINTER",  ITEM_TOOLTIP_LAST_POINTER,  "ITEM_TOOLTIP_LAST_POINTER has to be bigger than ITEM_TOOLTIP_FIRST_POINTER.")
            endif
            call CustomizePointers()
        endmethod
    endmodule
    private struct I extends array
        implement Inits
    endstruct
 
endlibrary
JASS:
library CustomItemAffixList uses InventoryCore
//=========================================================================
// Item affixes are effects an item grants to its carrier unit.
// These affixes can be of two types:
//  1.) ability id     ( inlcuding ability levels )
//  2.) BonusMod Bonus ( refer to: wc3c.net/showthread.php?t=107940 )
//=========================================================================
// CustomItemAffixList saves all affixes for item ids
// and item type ids in seperate lists.
// Only affixes of item id lists are added to units,
// while the item type id lists transfer their nodes
// to item ids lists when a new CustomItem is created.
//=========================================================================
// In order to avoid code collision inside the hashtable,
// there are all together four parent keys,
// which determine the type of affix list.
//  1.) INVENTORY_ITEM_TYPE_ID_ABILITY_LIST
//  2.) INVENTORY_ITEM_TYPE_ID_BONUS_LIST
//  3.) INVENTORY_ITEM_ID_ABILITY_LIST
//  4.) INVENTORY_ITEM_ID_BONUS_LIST
//
// Also two parent keys to distinguish affix id and
// affix quantity between item type ids and item ids.
//  A.) INVENTORY_ITEM_TYPE_ID_AFFIXES
//  B.) INVENTORY_ITEM_ID_AFFIXES
//
// Affix ids are saved on the + value of a node.
// Affix quantities are saved on the - value of a node.
//========================================================================
//========================================================================
// Saving affixes to lists.
//========================================================================
                                                   // Bonus or ability.
    private function SearchNodeMatch takes integer affixType, integer list, integer affix returns integer
        local integer node = Inventory_GetListFirstNode(list)
 
        loop
            exitwhen 0 == node
            if LoadInteger(Inventory_GetTable(), affixType, node) == affix then
                return node
            endif
            set node = Inventory_GetListNextNode(node)
        endloop
 
        return 0
    endfunction
                                             // Bonus or ability.
    private function SaveAffix takes integer affixType, integer parent, integer itemKey, integer affix, integer amount returns nothing
        local integer list = Inventory_GetListByKey(parent, itemKey)
        local integer node = SearchNodeMatch(affixType, list, affix)
        local integer sum  = LoadInteger(Inventory_GetTable(), affixType, -node) + amount
 
        if node == 0 then
            if amount != 0 then
                set node = Inventory_EnqueueToList(list)
                call SaveInteger(Inventory_GetTable(), affixType,  node, affix)
                call SaveInteger(Inventory_GetTable(), affixType, -node, amount)
            endif
        elseif sum == 0 then
            call RemoveSavedInteger(Inventory_GetTable(), affixType, node)
            call RemoveSavedInteger(Inventory_GetTable(), affixType, -node)
            call Inventory_RemoveNodeFromList(node)
        else
            call SaveInteger(Inventory_GetTable(), affixType, -node, sum)
        endif
    endfunction
 
    // For read-ability only.
    private function SaveItemTypeIdAffix takes integer parent, integer itemKey, integer affix, integer amount returns nothing
        call SaveAffix(INVENTORY_ITEM_TYPE_ID_AFFIXES, parent, itemKey, affix, amount)
    endfunction
    private function SaveItemIdAffix takes integer parent, integer itemKey, integer affix, integer amount returns nothing
        call SaveAffix(INVENTORY_ITEM_ID_AFFIXES, parent, itemKey, affix, amount)
    endfunction
//========================================================================
// Adding & removing affixes to units.
//========================================================================
 
static if not LIBRARY_BonusMod and not LIBRARY_Bonus then
// Required system is not present.
else                                                                                      // + or -
    private function AddBonuses takes unit source, integer affixType, integer id, integer signum returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_ID_BONUS_LIST, id)
        local integer node = Inventory_GetListFirstNode(list)// List of an item handle id.
        local integer bonus
        local integer amount
 
        loop
            exitwhen 0 == node
            set bonus = LoadInteger(Inventory_GetTable(), affixType, node)
            set amount = LoadInteger(Inventory_GetTable(), affixType, -node)*signum
            call AddUnitBonus(source, bonus, amount)
 
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
endif
                                                                                // GetHandleId(item)
    private function AddAbilities takes unit source, integer affixType, integer id returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_ID_ABILITY_LIST, id)
        local integer node = Inventory_GetListFirstNode(list)
        local integer abilityId
        local integer level
 
        loop
            exitwhen 0 == node
            set abilityId = LoadInteger(Inventory_GetTable(), affixType, node)
            set level = LoadInteger(Inventory_GetTable(), affixType, -node)
 
            if UnitAddAbility(source, abilityId) then
                call SetUnitAbilityLevel(source, abilityId, level)
                call UnitMakeAbilityPermanent(source, true, abilityId)
            // The ability was already on the unit.
            else
                set level = level + GetUnitAbilityLevel(source, abilityId)
                if level != SetUnitAbilityLevel(source, abilityId, level) then
                    debug call ThrowWarning(true, "CustomItemAffixList", "AddAbilities", "level", 0, "The map doesn't support for " + GetObjectName(abilityId) + " a level of " + I2S(level))
                endif
            endif
 
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
                                                                                   // GetHandleId(item)
    private function RemoveAbilities takes unit source, integer affixType, integer id returns nothing
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_ID_ABILITY_LIST, id)
        local integer node = Inventory_GetListFirstNode(list)
        local integer abilityId
        local integer level
 
        loop
            exitwhen 0 == node
            set abilityId = LoadInteger(Inventory_GetTable(), affixType, node)
            set level = LoadInteger(Inventory_GetTable(), affixType, -node)
 
            if GetUnitAbilityLevel(source, abilityId) <= level then
                call UnitRemoveAbility(source, abilityId)
            else
                call SetUnitAbilityLevel(source, abilityId, GetUnitAbilityLevel(source, abilityId) - level)
            endif
 
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
//========================================================================
// Public API called from Inventory on equip and unequip event.
//========================================================================
    function RemoveUnitItemAffixes takes unit source, item whichItem returns nothing
        local integer id = GetHandleId(whichItem)
        if (id != 0) and (GetUnitTypeId(source) != 0) then
            call RemoveAbilities(source, INVENTORY_ITEM_ID_AFFIXES, id)
 
            static if not LIBRARY_BonusMod and not LIBRARY_Bonus then
            // Required system is not present.
            else
                call AddBonuses(source, INVENTORY_ITEM_ID_AFFIXES, id, -1)
            endif
        endif
    endfunction
 
    function AddUnitItemAffixes takes unit source, item whichItem returns nothing
        local integer id = GetHandleId(whichItem)
        if (id != 0) and (GetUnitTypeId(source) != 0) then
            call AddAbilities(source, INVENTORY_ITEM_ID_AFFIXES, id)
 
            static if not LIBRARY_BonusMod and not LIBRARY_Bonus then
            // Required system is not present.
            else
                call AddBonuses(source, INVENTORY_ITEM_ID_AFFIXES, id, 1)
            endif
        endif
    endfunction
//========================================================================
// User API; Documentated in Inventory API.
//========================================================================
    // These abilities are always present on the ability list for items of type "itemId".
    function AddCustomItemIdAbility takes integer itemId, integer abilityId, integer level returns nothing
        debug call ThrowError(itemId == 0, "CustomItemAffixList", "AddCustomItemIdAbility", "itemId", 0, "Invalid item id ( 0 )!")
 
        call SaveItemTypeIdAffix(INVENTORY_ITEM_TYPE_ID_ABILITY_LIST, itemId, abilityId, level)
    endfunction
 
    // These bonuses are always present on the bonus list for items of type "itemId".
    function AddCustomItemIdBonus takes integer itemId, integer bonus, integer amount returns nothing
        debug call ThrowError(itemId == 0, "CustomItemAffixList", "AddCustomItemIdAbility", "itemId", 0, "Invalid item id ( 0 )!")
 
        call SaveItemTypeIdAffix(INVENTORY_ITEM_TYPE_ID_BONUS_LIST, itemId, bonus, amount)
    endfunction
 
    // These abilities are only present on the ability list of a specific item.
    function AddCustomItemAbility takes item i, integer abilityId, integer level returns nothing
        if (GetItemTypeId(i) != 0) then
            call SaveItemIdAffix(INVENTORY_ITEM_ID_ABILITY_LIST, GetHandleId(i), abilityId, level)
        endif
    endfunction
 
    // These bonuses are only present on the bonus list of a specific item.
    function AddCustomItemBonus takes item i, integer bonus, integer amount returns nothing
        if (GetItemTypeId(i) != 0) then
            call SaveItemIdAffix(INVENTORY_ITEM_ID_BONUS_LIST, GetHandleId(i), bonus, amount)
        endif
    endfunction
 
    // Completely empties all lists for an item handle id.
    function FlushCustomItemAffixes takes integer id returns nothing
        debug call ThrowWarning(id == 0, "CustomItemAffixList", "FlushCustomItemAffixes", "id", 0, "Invalid handle id ( 0 )!")
 
        if (id != 0) then
            call Inventory_DestroyListByKey(INVENTORY_ITEM_ID_BONUS_LIST, id)
            call Inventory_DestroyListByKey(INVENTORY_ITEM_ID_ABILITY_LIST, id)
        endif
    endfunction
//==========================================================================
// Import from item id list to handle id list. Called from the item parser.
//==========================================================================
 
    private function ImportBonuses takes item i returns nothing
        local integer id = GetHandleId(i)
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_TYPE_ID_BONUS_LIST, GetItemTypeId(i))
        local integer node = Inventory_GetListFirstNode(list)
        local integer bonus
        local integer amount
 
        loop
            exitwhen 0 == node
            set bonus = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_TYPE_ID_AFFIXES, node)
            set amount = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_TYPE_ID_AFFIXES, -node)
            call SaveItemIdAffix(INVENTORY_ITEM_ID_BONUS_LIST, id, bonus, amount)
 
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
 
    private function ImportAbilities takes item i returns nothing
        local integer id = GetHandleId(i)
        local integer list = Inventory_GetListByKey(INVENTORY_ITEM_TYPE_ID_ABILITY_LIST, GetItemTypeId(i))
        local integer node = Inventory_GetListFirstNode(list)
        local integer abilityId
        local integer level
 
        loop
            exitwhen 0 == node
            set abilityId = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_TYPE_ID_AFFIXES, node)
            set level = LoadInteger(Inventory_GetTable(), INVENTORY_ITEM_TYPE_ID_AFFIXES, -node)
            call SaveItemIdAffix(INVENTORY_ITEM_ID_ABILITY_LIST, id, abilityId, level)
 
            set node = Inventory_GetListNextNode(node)
        endloop
    endfunction
 
    function ImportCustomItemAffixes takes item i returns nothing
        local integer itemId = GetItemTypeId(i)
        if (itemId != 0) then
            if Inventory_KeyForListExists(INVENTORY_ITEM_TYPE_ID_BONUS_LIST, itemId) then
                call ImportBonuses(i)
            endif
 
            if Inventory_KeyForListExists(INVENTORY_ITEM_TYPE_ID_ABILITY_LIST, itemId) then
                call ImportAbilities(i)
            endif
        endif
    endfunction
 
endlibrary
Demo Code:
JASS:
//**
//*  How to create an inventory for a unit:
//*  ======================================
    //*  function CreateInventory takes unit source, real posX, real posY, integer dummyId, real dummyOffsetY returns Inventory.
 
scope IntializeUnit initializer Init
    //*  How to access the Inventory surface is up to you.
    //*  Most common events are:
    //*     - on esc key pressed.
    //*     - on ability event ( maybe in a spellbook )
    //*     - on item event
    //*
    //* In this example I detect a certain spell effect event to open or close an Inventory.
    globals
        private constant integer ABILITY_ID = 'A000'
        unit HERO
    endglobals
 
    private function OnSpellEffect takes nothing returns nothing
        //*  Get a unit inventory.
        local Inventory inventory = GetUnitInventory(GetTriggerUnit())
        //*  Is it our ability?
        if (GetSpellAbilityId() == ABILITY_ID) then
            //*  For more safety inventory inherits an "exists" operator from the UIScreen parent struct.
            //* It will return false, if this inventory is not allocated.
            if (inventory.exists) then
                //*  inventory.show has the same logic like i.e. ShowUnit(unit, boolean)
                //* "inventory.enabled" gives us information if an inventory is currently shown.
                //* Show when closed, hide when opened.
                call inventory.show(not inventory.enabled)
            endif
        endif
    endfunction
 
    private function AddStuffToUnit takes unit u returns nothing
        call UnitAddAbility(u, ABILITY_ID)
        call UnitMakeAbilityPermanent(u, true, ABILITY_ID)
    endfunction
 
    private function Init takes nothing returns nothing
        local unit u2 = CreateUnit(Player(0), 'Ogrh', -1600, 3300, 0)
        local unit u  = CreateUnit(Player(0), 'Edem', -1600, 3300, 0)
        //* unit, originX, originY
        call CreateInventory(u, 500., -900)
        //*  unit, dummyId, offsetY ( offsetY is different for every unit )
        call AddUnitInventoryDummy(u, 'h000', 1.5)
        set HERO = u
        //*
        //*  Inventory interfaces can be on the same position.
        //*  Using a dummy unit as portrait is optional. For this one I didn't want to.
        call CreateInventory(u2, 500., -900.)
 
        //*
        call SetCameraPosition(GetUnitX(u), GetUnitY(u))
        call SelectUnit(u, true)
        //*
        call AddStuffToUnit(u)
        call AddStuffToUnit(u2)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function OnSpellEffect)
        set u  = null
        set u2 = null
    endfunction
endscope
JASS:
//**
//*  How to create an inventory for a unit:
//*  ======================================
    //*  function CreateInventory takes unit source, real posX, real posY, integer dummyId, real dummyOffsetY returns Inventory.
 
scope IntializeUnit initializer Init
    //*  How to access the Inventory surface is up to you.
    //*  Most common events are:
    //*     - on esc key pressed.
    //*     - on ability event ( maybe in a spellbook )
    //*     - on item event
    //*
    //* In this example I detect a certain spell effect event to open or close an Inventory.
    globals
        private constant integer ABILITY_ID = 'A000'
        unit HERO
    endglobals
 
    private function OnSpellEffect takes nothing returns nothing
        //*  Get a unit inventory.
        local Inventory inventory = GetUnitInventory(GetTriggerUnit())
        //*  Is it our ability?
        if (GetSpellAbilityId() == ABILITY_ID) then
            //*  For more safety inventory inherits an "exists" operator from the UIScreen parent struct.
            //* It will return false, if this inventory is not allocated.
            if (inventory.exists) then
                //*  inventory.show has the same logic like i.e. ShowUnit(unit, boolean)
                //* "inventory.enabled" gives us information if an inventory is currently shown.
                //* Show when closed, hide when opened.
                call inventory.show(not inventory.enabled)
            endif
        endif
    endfunction
 
    private function AddStuffToUnit takes unit u returns nothing
        call UnitAddAbility(u, ABILITY_ID)
        call UnitMakeAbilityPermanent(u, true, ABILITY_ID)
    endfunction
 
    private function Init takes nothing returns nothing
        local unit u2 = CreateUnit(Player(0), 'Ogrh', -1600, 3300, 0)
        local unit u  = CreateUnit(Player(0), 'Edem', -1600, 3300, 0)
        //* unit, originX, originY
        call CreateInventory(u, 500., -900)
        //*  unit, dummyId, offsetY ( offsetY is different for every unit )
        call AddUnitInventoryDummy(u, 'h000', 1.5)
        set HERO = u
        //*
        //*  Inventory interfaces can be on the same position.
        //*  Using a dummy unit as portrait is optional. For this one I didn't want to.
        call CreateInventory(u2, 500., -900.)
 
        //*
        call SetCameraPosition(GetUnitX(u), GetUnitY(u))
        call SelectUnit(u, true)
        //*
        call AddStuffToUnit(u)
        call AddStuffToUnit(u2)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function OnSpellEffect)
        set u  = null
        set u2 = null
    endfunction
endscope
JASS:
scope InitializeEvents initializer Init
    private function OnPawn takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "EVENT_INVENTORY_UNIT_PAWN_ITEM")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger Inventory [" + I2S(GetTriggerInventory()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger item [" + GetItemName(Inventory_GetTriggerItem()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Event target unit [" + GetUnitName(Inventory_GetEventTargetUnit()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
    endfunction
    private function OnDrop takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "EVENT_INVENTORY_UNIT_DROP_ITEM")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger Inventory [" + I2S(GetTriggerInventory()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger item [" + GetItemName(Inventory_GetTriggerItem()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Event target unit [" + GetUnitName(Inventory_GetEventTargetUnit()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        endfunction
    private function OnPick takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "EVENT_INVENTORY_UNIT_PICKUP_ITEM")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger Inventory [" + I2S(GetTriggerInventory()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger item [" + GetItemName(Inventory_GetTriggerItem()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Event target unit [" + GetUnitName(Inventory_GetEventTargetUnit()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
    endfunction
    private function OnUnequip takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "EVENT_INVENTORY_UNIT_UNEQUIP_ITEM")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger Inventory [" + I2S(GetTriggerInventory()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger item [" + GetItemName(Inventory_GetTriggerItem()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Event target unit [" + GetUnitName(Inventory_GetEventTargetUnit()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
    endfunction
    private function OnEquip takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "EVENT_INVENTORY_UNIT_EQUIP_ITEM")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger Inventory [" + I2S(GetTriggerInventory()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Trigger item [" + GetItemName(Inventory_GetTriggerItem()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., "Event target unit [" + GetUnitName(Inventory_GetEventTargetUnit()) + "]")
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 5., " ")
    endfunction
    private function Init takes nothing returns nothing
        call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_PAWN_ITEM,    function OnPawn)
        call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_DROP_ITEM,    function OnDrop)
        call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_PICKUP_ITEM,  function OnPick)
        call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_EQUIP_ITEM,   function OnEquip)
        call RegisterInventoryEvent(EVENT_INVENTORY_UNIT_UNEQUIP_ITEM, function OnUnequip)
    endfunction
endscope
JASS:
scope InitCustomItemClasses
 
//**
//*  Creating ItemClasses:
//*  =====================
//*  Item classes must be initialized via ItemClass.create("name")
//*  You can create a sub class of another class via myClass.createSubClass("name")
//*  Also possible is a sub class of another subclass -> mySubClass.createSubClass("name")
//*  Finally it looks like a tree with branches.
//*
//*  The root of all classes is 0 which resembles ITEM_CLASS_ANY. Check this:
//*                                                                                ItemClass.ANY
//*                                                                                /     |     \
//*  Example: class:    WEAPON      = ItemClass.create("Weapon")                WAND  WEAPON  BOW
//*           subclass: AXE         = WEAPON.createSubClass("Axe")                      |       \
//*           subclass: MIGHTY_AXE  = AXE.createSubClass("Mighty Axe")                 AXE   CROSS_BOW
//*           subclass: WARRIOR_AXE = AXE.createSubClass("Warrior Axe").              /  \
//*                                                                           MIGHTY_AXE WARRIOR_AXE
//**
//*  Example Setup:
//*  ==============
    globals
        ItemClass ITEM_CLASS_HAND          = 0
        ItemClass ITEM_CLASS_BOW           = 0
        ItemClass ITEM_CLASS_QUIVER        = 0
        ItemClass ITEM_CLASS_WAND          = 0
        ItemClass ITEM_CLASS_WEAPON        = 0
        ItemClass ITEM_CLASS_ORB           = 0
        ItemClass ITEM_CLASS_MIGHTY_WEAPON = 0
        ItemClass ITEM_CLASS_FIST_WEAPON   = 0
        ItemClass ITEM_CLASS_BOOK          = 0
        ItemClass ITEM_CLASS_POTION        = 0
        ItemClass ITEM_CLASS_BELT          = 0
        ItemClass ITEM_CLASS_RING          = 0
        ItemClass ITEM_CLASS_BOOTS         = 0
        ItemClass ITEM_CLASS_CHEST         = 0
        ItemClass ITEM_CLASS_PANTS         = 0
        ItemClass ITEM_CLASS_AMULET        = 0
        ItemClass ITEM_CLASS_GLOVES        = 0
        ItemClass ITEM_CLASS_HELMET        = 0
        ItemClass ITEM_CLASS_SHOULDER      = 0
        ItemClass ITEM_CLASS_WRIST         = 0
        ItemClass ITEM_CLASS_HAT           = 0
        ItemClass ITEM_CLASS_CORONA        = 0
        ItemClass ITEM_CLASS_MIGHTY_HELMET = 0
    endglobals
    private function InitClasses takes nothing returns nothing
        //*  Main Classes:
        //*  =============
        set ITEM_CLASS_HAND          = ItemClass.create("Hand")
        set ITEM_CLASS_BOW           = ItemClass.create("Bow")
        set ITEM_CLASS_QUIVER        = ItemClass.create("Quiver")
        set ITEM_CLASS_WAND          = ItemClass.create("Wand")
        //*  Hand sub classes - Char classes:
        set ITEM_CLASS_WEAPON        = ITEM_CLASS_HAND.createSubClass("Weapon")
        set ITEM_CLASS_ORB           = ITEM_CLASS_HAND.createSubClass("Orb")
        //*  Offhand sub sub classes.
        set ITEM_CLASS_FIST_WEAPON   = ITEM_CLASS_WEAPON.createSubClass("Fist Weapon")
        set ITEM_CLASS_MIGHTY_WEAPON = ITEM_CLASS_WEAPON.createSubClass("Mighty Weapon")
        set ITEM_CLASS_BOOK          = ITEM_CLASS_FIST_WEAPON.createSubClass("Book")
 
        //*  Potions:
        //*  ========
        set ITEM_CLASS_POTION        = ItemClass.create("Potion")
 
        //*  Helmet Classes:
        //*  ===============
        set ITEM_CLASS_HELMET        = ItemClass.create("Helmet")
        set ITEM_CLASS_CORONA        = ITEM_CLASS_HELMET.createSubClass("Corona")
        set ITEM_CLASS_HAT           = ITEM_CLASS_HELMET.createSubClass("Hat")
        set ITEM_CLASS_MIGHTY_HELMET = ITEM_CLASS_HELMET.createSubClass("Mighty Helmet")
 
        //*  Other Classes:
        //*  ==============
        set ITEM_CLASS_BELT          = ItemClass.create("Belt")
        set ITEM_CLASS_RING          = ItemClass.create("Ring")
        set ITEM_CLASS_BOOTS         = ItemClass.create("Boots")
        set ITEM_CLASS_CHEST         = ItemClass.create("Chest")
        set ITEM_CLASS_PANTS         = ItemClass.create("Pant")
        set ITEM_CLASS_AMULET        = ItemClass.create("Amulet")
        set ITEM_CLASS_GLOVES        = ItemClass.create("Glove")
        set ITEM_CLASS_WRIST         = ItemClass.create("Wrist")
        set ITEM_CLASS_SHOULDER      = ItemClass.create("Shoulder")
    endfunction
 
    private struct I extends array
        private static method init takes nothing returns nothing
            call InitClasses()
        endmethod
        implement UIInit
    endstruct
endscope



Quick Download for Bpower's Demo Map Attached​
 

Attachments

  • Inventory Interface Map.w3x
    611.9 KB · Views: 22
Last edited:
Status
Not open for further replies.
Top