• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Inventory v2.1a

Info

Inventory is used to extend the default unit inventory in terms of usage and size.
A Bag system is included which takes advantage of "Inventory" and allows easily to add/remove custom bags.

Code

JASS:
library Inventory  /* v2.1a -- hiveworkshop.com/threads/bag-v1-6-1.252002/

*/ requires /* Credits
       
        */ VectorT                  /* Bannar -- hiveworkshop.com/threads/containers-vector-t.248942/
        */ RegisterPlayerUnitEvent  /* Bannar (new version) -- http://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
                                    or
                                       Magtherigon96 (old version) -- hiveworkshop.com/threads/snippet-registerplayerunitevent.203338/
               
        */ Table                    /* Bribe -- hiveworkshop.com/threads/snippet-new-table.188084/
        */ UnitDex                  /* TriggerHappy -- hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/

       
    VectorT requires Alloc module.
    Example: http://www.hiveworkshop.com/threads/unique-id-allocation.260897/
       

    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Inventory is an improvement of the normal hero inventory.
        It extends the treatment with items in inventory, (stack/split) 
        and also adds the possibilty to add abstract inventories. 
       
       
     Mechanics
    ¯¯¯¯¯¯¯¯¯¯¯
        Each unit can have infinite inventories.
        Each inventory can have infinte items.
        So each unit has a vector of inventories, and 
        each inventory has a vector of items in itself.
        With both vectors you can use the VecotorT's API.
       
        When adding a new item to an inventory,
        the item will be hided automaticaly, and when removed from the inventory,
        it will be unhided again, and moved to unit's position.
       
        When an item is added there is a default procedure.
        It goes from step 1 to 4 and in case one step succeed, it will stop.
       
        1) Try to stack item with current inventory.
        2) Try to stack item with all inventories of the unit.
        3) Try to find a free slot in current inventory.
        4) Try to find a free slot in any inventory of the unit.
       
        If none of the steps was succesfull it means that the item was not added.
   
*/
//  --- API --- 
//! novjass
   
    // read StackConfig trigger for stack API
   
    // read SplitConfig trigger for stack API
   
    // read Fully Inventory Order trigger for FullInventory API
   
    struct InventoryData
   
        readonly ItemVector item
            // vector of items
            // to read, VectorAPI can be used, item[0] -> returns first element
       
        readonly Inventory inventory // represents the unit's entire inventory
                                     // this is actually the UnitId of the unit where the inventory is applied.
                                     // so when using GetUnitById() of UnitIndexer function we can retrive the respective unit.
       
    // all the getSlot functions return "-1" if wanted slot doesnt exist
       
        method init takes integer size returns nothing
            // inits the vector with empty slots
           
        method getFreeSlot takes nothing returns integer
            // returns an empty slot
           
        method getItemSlot takes item it returns integer
            // which slot item has
           
        method addItem takes item it returns boolean
            // returns if item was succesfully added
           
        method addItemAtSlot takes item it, integer slot returns boolean
            // returns if item was succesfully added
           
        method stackItem takes item it returns boolean
            // tries to stack item with any items inside the vector
           
        method getItemStackableSlot takes item it returns integer
            // returns an slot with which item could stack with
           
        method switchItems takes integer slot1, integer slot2 returns boolean
            // you can switch slots of two items
           
        method hasItem takes item it returns boolean
            // does this vector contain the item
           
        method removeItem takes item it returns boolean
            // returns if item was succesfully removed
           
        method flush takes nothing returns nothing
            // removed all items from the vector
           
        static method isItemOwned takes item it returns boolean
            // returns if this item is part of ANY inventory already
           
        static method getItemVector takes item it returns thistype
            // returns the vector the item belongs to
           
           
    struct Inventory
       
        static constant group GROUP
            // defines all units which have a custom inventory applied
       
        readonly InventoryVector inventory
            // vector of "InventoryData" , see struct above
            // you may read for example, with the vector api inventory[0] -> returns first element
           
        public integer vectorPos
            // current position in vector
            // this can be used to keep track of current inventory
            // it always starts with "0"
           
           
        static method operator[] takes unit u returns thistype
           
           
    //  Functions to handle inventories. 
   
   
        method setInventoryAmount takes integer amount returns boolean
            // set amount of inventories
           
        method getInventoryAmount takes nothing returns integer
            // how many custom inventories the unit has
           
        method addInventory takes integer amount returns boolean
            // to add/remove custom inventories
           
        method removeInventory takes integer whichInventory returns boolean
            // remove a certain inventory from vector
           
        method flush takes nothing returns boolean
            // removes all inventories from the unit
           
           
    // Functions to handle inventory content.
   
       // all the getSlot functions return "-1" if wanted slot doesnt exist
   
   
        method hasFreeSlot takes nothing returns boolean
            // returns if there is any inventory left with a free slot
           
        method hasItem takes item it returns boolean
            // returns if the item is part of any of the unit's inventory
           
        method hasStackableItem takes item it returns boolean
            // returns if there is any item with which "it" could stack
           
        method stackItem takes item it returns boolean
            // stack item "it" with any items inside any inventory until it's used up
            // returns if item could be completly stacked
           
        method addItem takes item it returns boolean
            // 1. tries to stack the item with whole inventory
            // 2. if item still exists, it will get a free slot anywhere
            // 3. if item was not stacked and could not be added, return false
       

    //  You can registder code that runs when a item is added to an inventory. 
   
         static method register takes boolexpr bx returns triggercondition
         static method unregister takes triggercondition tc returns nothing
         
         //inside the code refer to:
            static unit Unit
                // inventory unit
               
            static integer InitialCharges
                // charges the item had originaly, before it tried to apply
               
            static item InitialItem
                // item itself (can be removed, too, in case it was fully stacked)
               
            static integer InitialItemId
                // itemId of item that was added
               
               
    struct UnitInventory
   
    // UnitInventory works only with the current/actual inventory of the unit,
    // instead of "Inventory" struct, which works with all inventories.
   
    // all the getSlot functions return "-1" if wanted slot doesnt exist
   
           
        static method getFreeSlot takes unit u returns integer
            // get free slot
           
        static method getItemSlot takes unit u, item it returns integer
            // which slot the item has
           
        static method stackItem takes unit u, item it returns boolean
            // stack item "it" with any items inside any inventory until it's used up
            // returns if item could be completly stacked
           
        static method getItemStackableSlot takes unit u, item it returns integer
            // returns an slot with which "it" could stack with
           
    // method operator to enable/disable triggers:
   
        // The system works with some events, such as drop, pickup, and inventory orders
        // to properly stack/split, and add/remove items automaticaly from/in the inventory.
       
        // if you manipulate some data on your own, such as "Bag" library does,
        // you may require some functions to toggle some system triggers.
   
        static method operator order_enabled= takes boolean flag returns nothing
            // toggle the Inventory_Order trigger
           
        static method operator pickUp_enabled= takes boolean flag returns nothing
            // toogle the Item_Pick_Up trigger
       
        static method operator drop_enabled= takes boolean flag returns nothing
            // toggle the Item_Drop trigger
   
   
//! endnovjass
// ==== End API ====      

// _g suffix means global, not struct specific
// _p suffix means private, to avoid name collision in case there is something (same) public

globals
    private boolean pickup_enabled_p = true
    private boolean drop_enabled_p = true
    private boolean order_enabled_p = true
endglobals

native UnitAlive            takes unit id                               returns boolean

// fast init, so stack/split modules work
private module Init_T
    private static method onInit takes nothing returns nothing
        set table = Table.create()
    endmethod
endmodule

struct ItemSplit extends array
    private static Table table
   
    private static method SetSplitAmount takes integer itemType, integer amount returns nothing
        set table.integer[itemType] = amount
    endmethod
   
    public static method operator [] takes integer itemType returns integer
        if table.integer.has(itemType) then
            return table.integer[itemType]
        else
            return 1
        endif
    endmethod
    public static method exists takes integer itemType returns boolean
        return table.integer.has(itemType)
    endmethod
   
    implement Init_T
    implement ItemSplitConfig
endstruct

struct ItemStack extends array
    private static Table table
   
    private static method SetStackLimit takes integer itemType, integer limit returns nothing
        set table.integer[itemType] = limit
    endmethod
   
    public static method operator [] takes integer itemType returns integer
        if table.integer.has(itemType) then
            return table.integer[itemType]
        else
            return DEFAULT_STACK_LIMIT
        endif
    endmethod
    public static method exists takes integer itemType returns boolean
        return table.integer.has(itemType)
    endmethod
   
    implement Init_T
    implement ItemStackConfig
   
    public static method isStackable takes item it returns boolean
        return GetItemCharges(it) > 0 and GetItemCharges(it) < thistype[GetItemTypeId(it)]
    endmethod
   
    // return if item was fully stacked.
    public static method stackItems takes item oldItem, item newItem returns boolean
        local integer max
        local integer charges_old
        local integer charges_new = GetItemCharges(newItem)
       
        if (not isStackable(oldItem) or charges_new < 1 or GetItemTypeId(oldItem) != GetItemTypeId(newItem) or oldItem == null) then
            return false
        endif
       
        set max = ItemStack[GetItemTypeId(oldItem)]
        set charges_old = GetItemCharges(oldItem)
       
        if (charges_old + charges_new) > max then
           
            // charges is too big, stack only partialy
            call SetItemCharges(newItem, max - charges_old)
            call SetItemCharges(oldItem, max)
            return false
        else
            // items can completly stack, we can remove one
       
            call SetItemCharges(oldItem, charges_old + charges_new)
           
            // clean from inventory
            if InventoryData.isItemOwned(newItem) then
                call InventoryData.getItemVector(newItem).removeItem(newItem)
            endif
           
            set UnitInventory.drop_enabled = false
            call RemoveItem(newItem)
            set UnitInventory.drop_enabled = true
            return true
        endif
       
    endmethod
endstruct

//! runtextmacro DEFINE_VECTOR("", "ItemVector", "item")

struct InventoryData
    readonly ItemVector item
    readonly Inventory inventory
   
    private static Table table
    implement Init_T
   
    // just the read the unit later, by it's id
    method operator Inventory= takes Inventory i returns nothing
        set inventory = i
        if .item == 0 then
            set .item = ItemVector.create()
        endif
    endmethod
   
    // we create empty slots
    public method init takes integer size returns nothing
        local integer max = .item.size()
        loop
            exitwhen (max > size)
            call .item.push(null)
            set max =  max + 1
        endloop
    endmethod
   
    public static method isItemOwned takes item it returns boolean
        return table.has(GetHandleId(it))
    endmethod
   
    public method hasItem takes item it returns boolean
        return table[GetHandleId(it)] == this
    endmethod
   
    public static method getItemVector takes item it returns thistype
        return table[GetHandleId(it)]
    endmethod
   
    // find item slot
    public method getItemSlot takes item it returns integer
        if .hasItem(it) then
            return table[-GetHandleId(it)]
        endif
        return -1
    endmethod
   
    // find any free slot
    public method getFreeSlot takes nothing returns integer
        local integer max = .item.size() - 1
        local integer i = 0
        loop
            exitwhen (i > max)
           
            if (.item[i] == null) then
                return i
            endif
           
            set i = i + 1
        endloop
        return -1
    endmethod
   
    // find slot with same item type that is stackable
    public method getItemStackableSlot takes item it returns integer
        local integer max
        local integer i
        local integer iType
       
        if (it == null or GetItemTypeId(it) == 0 or GetItemCharges(it) < 1) then
            return -1
        endif
       
        set max = .item.size() - 1
        set i = 0
        set iType = GetItemTypeId(it)
        loop
            exitwhen (i > max)
           
            if iType == GetItemTypeId(.item[i]) and ItemStack.isStackable(.item[i]) and it != .item[i] then
                return i
            endif
           
            set i = i + 1
        endloop
        return -1
    endmethod
   
    // stack item until it's used up
    public method stackItem takes item it returns boolean
        local integer slot
       
        if (GetItemTypeId(it) == 0 or GetItemCharges(it) < 1) then
            return false
        endif
       
        loop
            set slot = .getItemStackableSlot(it)
            exitwhen (slot ==-1)
            call ItemStack.stackItems(.item[slot], it)
            if (GetItemTypeId(it) == 0) then
                return true
            endif
        endloop
        return false
    endmethod
   
    // adds item at specific slot
    public method addItemAtSlot takes item it, integer slot returns boolean
        if (slot >= item.size() or slot < 0) then
            return false
        endif
       
        if (.item[slot] != null) then
            call .removeItem(.item[slot])
        endif
       
        set .item[slot] = it
       
        // link data to item
        if it != null then
            set table.integer[GetHandleId(it)] = this
            set table.integer[-GetHandleId(it)] = slot
            // always hide added items
            call SetItemVisible(it, false)
        endif
        return true
    endmethod
   
    // adds item at empty slot
    public method addItem takes item it returns boolean
        local integer slot = .getFreeSlot()
       
        if (slot != -1) then
            return .addItemAtSlot(it, slot)
        endif
       
        return false
    endmethod
   
    public method removeItem takes item it returns boolean
        local integer handleId
        local unit u
       
        if (GetItemTypeId(it) == 0 or it == null) then
            return false
        endif
       
        if .hasItem(it) then
            set handleId = GetHandleId(it)
            if not IsItemOwned(it) then
           
                // only move items that are somewhere on map,
                // else they will be dropped
                set u = GetUnitById(this.inventory)
                call SetItemVisible(it, true)
                call SetItemPosition(it, GetUnitX(u), GetUnitY(u))
                set u = null
            endif
           
            set .item[table[-handleId]] = null
            // unlink data from item
            call table.integer.remove(handleId)
            call table.integer.remove(-handleId)
           
            return true
        endif
        return false
    endmethod
   
    public method switchItems takes integer slot1, integer slot2 returns boolean
        local item it1
        local item it2
        if slot1 >= item.size() or slot1 < 0 or slot2 >= item.size() or slot2 < 0 then
            return false
        endif
        set it1 = .item[slot1]
        set it2 = .item[slot2]
        call .removeItem(.item[slot1])
        call .removeItem(.item[slot2])
        call .addItemAtSlot(it2, slot1)
        call .addItemAtSlot(it1, slot2)
        set it1 = null
        set it2 = null
        return true
    endmethod
   
    // removes all items from an inventory
    public method flush takes nothing returns nothing
        local integer i = .item.size() - 1
        loop
            exitwhen (i < 0)
            call .removeItem(.item[i])
            set i = i - 1
        endloop
    endmethod

endstruct
//! runtextmacro DEFINE_STRUCT_VECTOR("", "InventoryVector", "InventoryData")

struct Inventory extends array
    readonly InventoryVector inventory
    public integer vectorPos // current popsition in vector
    public static constant group GROUP = CreateGroup() // all units that use custom inventories
   
    private static constant trigger Handler = CreateTrigger()
    public static unit Unit
    public static integer InitialCharges
    public static item InitialItem
    public static integer InitialItemId
    public static method register takes boolexpr bx returns triggercondition
        return TriggerAddCondition(Handler, bx)
    endmethod
    public static method unregister takes triggercondition tc returns nothing
        call TriggerRemoveCondition(Handler, tc)
    endmethod
   
    public method getInventoryAmount takes nothing returns integer
        if IsUnitInGroup(GetUnitById(this), GROUP) then
            return .inventory.size() - 1
        else
            return 0
        endif
    endmethod
   
    public method hasFreeSlot takes nothing returns boolean
        local integer i
        if IsUnitInGroup(GetUnitById(this), GROUP) then
            set i = .getInventoryAmount()
            loop
                exitwhen (i < 0)
                if (.inventory[i].getFreeSlot()!= -1) then
                    return true
                endif
                set i = i - 1
            endloop
        endif
        return false
    endmethod
   
    // if item is in any inventory
    public method hasItem takes item it returns boolean
        local integer i
        if IsUnitInGroup(GetUnitById(this), GROUP) then
            set i = .getInventoryAmount()
            loop
                exitwhen (i < 0)
                if (.inventory[i].hasItem(it)) then
                    return true
                endif
                set i = i - 1
            endloop
        endif
        return false
    endmethod
   
    // has any inventory a stackable item
    public method hasStackableItem takes item it returns boolean
        local integer i
       
        if not IsUnitInGroup(GetUnitById(this), GROUP) or GetItemCharges(it) < 1 then
            return false
        endif
       
        set i = .getInventoryAmount()
        loop
            exitwhen (i < 0)
            if (.inventory[i].getItemStackableSlot(it) != -1) then
                return true
            endif
            set i = i - 1
        endloop
        return false
    endmethod
   
    // stack item with any other items in any inventory
    public method stackItem takes item it returns boolean
        local integer i
        local integer max
        if IsUnitInGroup(GetUnitById(this), GROUP) then
            set i = 0
            set max = .getInventoryAmount()
            loop
                exitwhen (i > max)
                if .inventory[i].stackItem(it) then
                    return true
                else
                    set i = i + 1
                endif
            endloop
        endif
        return false
    endmethod
   
    public method addItem takes item it returns boolean
        local integer max
        local integer i
        local integer slot
        local unit u = GetUnitById(this)
       
        set Unit = u
        set InitialItem = it
        set InitialItemId = GetItemTypeId(it)
        set InitialCharges = GetItemCharges(it)
       
        if InitialCharges > 0 then
       
            // try to stack with items in current inventory
         
            if UnitInventory.stackItem(u, it) then
                call TriggerEvaluate(Handler)
                set u  = null
                return true
            endif
           
            // try to stack with items in any inventory

            if .stackItem(it) then
                call TriggerEvaluate(Handler)
                set u  = null
                return true
            endif
           
        endif
       
        // item didn't stack -- try to find a free slot
       
        if UnitHasItem(u, it) then
            set slot = UnitInventory.getItemSlot(u, it)
            call .inventory[.vectorPos].addItemAtSlot(it, slot)
            call TriggerEvaluate(Handler)
            set u  = null
            return true
        else
            set slot = UnitInventory.getFreeSlot(u)
            if(slot != -1) then
                if IsUnitInGroup(u, GROUP) then
                    call .inventory[.vectorPos].addItemAtSlot(it, slot)
                endif
                call TriggerEvaluate(Handler)
                set u  = null
                return true
            endif
        endif
       
        // find free slot in any inventory
        set i = 0
        set max = .getInventoryAmount()
        loop
            exitwhen i > max
            if .inventory[i].addItem(it) then
                call TriggerEvaluate(Handler)
                set u  = null
                return true
            endif
            set i = i + 1
        endloop
       
        if (GetItemTypeId(it) != 0) and InitialCharges != GetItemCharges(it) then
            // if this runs item was partialy added
            call TriggerEvaluate(Handler)
        else
            // item was not added at all
        endif
       
        set u  = null
        return false
    endmethod
   
    // just adds a new inventory to the vector
    // "_p" because there is also such a public method to add x inventories
    private method addInventory_p takes nothing returns nothing
        local InventoryData ID = InventoryData.create()
        set ID.Inventory = this
        call .inventory.push(ID)
    endmethod
   
    public method removeInventory takes integer whichInventory returns boolean
       
        if (whichInventory < 1) or (whichInventory > .getInventoryAmount()) then
            return false
        endif
       
        call .inventory[whichInventory].flush()
        call .inventory[whichInventory].item.destroy()
        call .inventory.erase(whichInventory, 1)
       
        // fix current inventory position
        if whichInventory == .vectorPos then
            set .vectorPos = .vectorPos - 1
        endif
       
        return true
    endmethod
   
    // clears unit from all inventories
    public method flush takes nothing returns boolean
        local integer i
       
        if IsUnitInGroup(GetUnitById(this), GROUP) then
           
            set i = getInventoryAmount()
            loop
                exitwhen (i < 0)
               
                call .inventory[i].flush()
                call .inventory[i].item.destroy()
               
                set i = i - 1
            endloop
            call .inventory.destroy()
            call GroupRemoveUnit(GROUP, GetUnitById(this))
            set .vectorPos = 0
            return true
        else
            return false
        endif
    endmethod
   
    private method apply takes nothing returns nothing
        set .inventory = InventoryVector.create()
       
        // immediatly create an inventory, so the
        // current inventory can be applied
        call .addInventory_p()
        set .vectorPos = 0
       
        call GroupAddUnit(GROUP, GetUnitById(this))
    endmethod
   
    public method setInventoryAmount takes integer amount returns boolean
        local integer size
       
        if (GetUnitTypeId(GetUnitById(this)) == 0) then
            return false
        endif
       
        if (amount < 1) then
            return .flush()
        endif
       
        if not IsUnitInGroup(GetUnitById(this), GROUP) then
            call .apply()
        endif
       
        set size = .getInventoryAmount()
        if (size == amount) then
            return true
        endif
       
        if (amount > size) then
           
            // add inventories
            loop
                exitwhen (size >= amount)
                call .addInventory_p()
                set size = size + 1
            endloop
       
        else
            // amount < size
            // remove inventories
            loop
                exitwhen (size <= amount)
                call .removeInventory(size)
                set size = size - 1
            endloop
           
        endif
        return true
    endmethod
   
    public method addInventory takes integer amount returns boolean
        return .setInventoryAmount(.getInventoryAmount() + amount)
    endmethod
   
    public static method operator[] takes unit u returns thistype
        return GetUnitId(u)
    endmethod
   
    private static method onDeindex takes nothing returns boolean
        return thistype(GetIndexedUnitId()).flush()
    endmethod
   
    private static method onInit takes nothing returns nothing
        call OnUnitDeindex(function thistype.onDeindex)
    endmethod

endstruct

struct UnitInventory extends array
   
    // finds an other item than "it" in current inventory to stack with
    public static method getItemStackableSlot takes unit u, item it returns integer
        local integer i
        local integer max
        local integer iType
        local item temp
       
        if (GetItemTypeId(it) == 0 or GetItemCharges(it) < 1) then
            return -1
        endif
       
        set max = UnitInventorySize(u)
        set i = 0
        set iType = GetItemTypeId(it)
        loop
            exitwhen (i >= max)
            set temp = UnitItemInSlot(u, i)
            if GetItemTypeId(temp) == iType and it != temp and ItemStack.isStackable(temp) then
                set temp = null
                return i
            endif
            set i = i + 1
        endloop
        set temp = null
        return -1
    endmethod
   
    public static method getFreeSlot takes unit u returns integer
        local integer i = 0
        local integer max = UnitInventorySize(u)
        loop
            exitwhen i >= max
            if UnitItemInSlot(u, i) == null then
                return i
            endif
            set i = i + 1
        endloop
        return -1
    endmethod
   
    public static method getItemSlot takes unit u, item it returns integer
        local integer i = 0
        local integer max = UnitInventorySize(u)
        loop
            exitwhen i >= max
            if UnitItemInSlot(u, i) == it then
                return i
            endif
            set i = i + 1
        endloop
        return -1
    endmethod
   
    // tries to stack the item with current inventory until it's used up
    public static method stackItem takes unit u, item it returns boolean
        local integer slot
       
        if (GetItemTypeId(it) == 0 or GetItemCharges(it) < 1) then
            return false
        endif 
       
        loop
            set slot = getItemStackableSlot(u, it)
            exitwhen (slot ==-1)
            call ItemStack.stackItems(UnitItemInSlot(u, slot), it)
            if (GetItemTypeId(it) == 0) then
                return true
            endif
        endloop
        return false
    endmethod

    private static method convertoToInventoryOrderId takes integer orderId returns integer
        return (orderId - 852002)
    endmethod
   
    public static method operator order_enabled= takes boolean flag returns nothing
        set order_enabled_p = flag
    endmethod
   
    // just to save some redundancy -- used to clean next function
    //! textmacro UNIT_INTERFACE_ON_ORDER_CLEAN
        set u = null
        set itemTarget = null
        set itemOrigin = null
        return
    //! endtextmacro
   
    private static method onOrder takes nothing returns nothing
        local item itemOrigin
        local item itemTarget
        local integer slot
        local integer charges
        local integer stackCharges
       
        local integer itemType
       
        local integer slotTarget
        local integer slotOrigin
       
        local unit u
        local Inventory this
        local integer max
        local integer i
       
        if order_enabled_p then
       
            set itemOrigin = GetOrderTargetItem()
            set itemType =  GetItemTypeId(itemOrigin)
            set slotTarget = convertoToInventoryOrderId(GetIssuedOrderId())
            set u = GetTriggerUnit()
            set this = GetUnitId(u)
       
            if (slotTarget > -1 and slotTarget < 6) then
               
                //  get slot of item that is moved.
                set slotOrigin = 0
                loop
                    exitwhen UnitItemInSlot(u, slotOrigin) == itemOrigin
                    set slotOrigin = slotOrigin + 1
                endloop
               
                set charges = GetItemCharges(itemOrigin)
               
                // potential split
                if (slotOrigin == slotTarget) then
                   
                    if charges > 1 then
                       
                        // split
                       
                        // we first abstractly try to split the item,
                        // so we can stack the split amount with other items
                        // without even creating a new item
                       
                        set stackCharges = ItemSplit[itemType]
                        if (GetItemCharges(itemOrigin) <= stackCharges) then
                            set stackCharges = 1
                        endif
                        call SetItemCharges(itemOrigin, charges - stackCharges)
                         
                        // try stack with current inventory
                        loop
                            set slot = UnitInventory.getItemStackableSlot(u, itemOrigin)
                            exitwhen (slot == -1)
                            set itemTarget = UnitItemInSlot(u, slot)
                            set max = ItemStack[GetItemTypeId(itemTarget)]
                            if  max >= (GetItemCharges(itemTarget) + stackCharges) then
                                call SetItemCharges(itemTarget, GetItemCharges(itemTarget) + stackCharges)
                                //! runtextmacro UNIT_INTERFACE_ON_ORDER_CLEAN()
                            else
                                set stackCharges = stackCharges - (max - GetItemCharges(itemTarget))
                                call SetItemCharges(itemTarget, max)
                            endif
                        endloop
                       
                        // try stack with any inventory
                        if IsUnitInGroup(u, Inventory.GROUP) then
                            set i = 0
                            set max = this.getInventoryAmount()
                            loop
                                exitwhen i > max
                               
                                loop
                                    set slot = this.inventory[i].getItemStackableSlot(itemOrigin)
                                    exitwhen (slot == -1)
                                    set itemTarget = this.inventory[i].item[slot]
                                    set max = ItemStack[GetItemTypeId(itemTarget)]
                                    if  max >= (GetItemCharges(itemTarget) + stackCharges) then
                                        call SetItemCharges(itemTarget, GetItemCharges(itemTarget) + stackCharges)
                                        //! runtextmacro UNIT_INTERFACE_ON_ORDER_CLEAN()
                                    else
                                        set stackCharges = stackCharges - (max - GetItemCharges(itemTarget))
                                        call SetItemCharges(itemTarget, max)
                                    endif
                                endloop
                               
                                set i = i + 1
                            endloop
                           
                        endif
                       
                        // Item didn't stack, or did stack only partialy.
                        // We create the rest of the split and try to find a free slot
                       
                        set itemTarget = CreateItem(itemType, GetUnitX(u), GetUnitY(u))
                        call SetItemCharges(itemTarget, stackCharges)
                       
                        // in current incentory
                        set slot = UnitInventory.getFreeSlot(u)
                        if(slot != -1) then
                       
                            set pickup_enabled_p = false
                            call UnitAddItem(u, itemTarget)
                            set pickup_enabled_p = true
                           
                            if IsUnitInGroup(u, Inventory.GROUP) then
                                call this.inventory[this.vectorPos].addItemAtSlot(itemTarget, slot)
                            endif
                            //! runtextmacro UNIT_INTERFACE_ON_ORDER_CLEAN()
                        endif
                       
                        // find free slot in any inventory
                        if IsUnitInGroup(u, Inventory.GROUP) then
                           
                            set i = 0
                            set max = this.getInventoryAmount()
                            loop
                               
                                exitwhen i > max
                               
                                if this.inventory[i].addItem(itemTarget) then
                                    //! runtextmacro UNIT_INTERFACE_ON_ORDER_CLEAN()
                                endif
                                set i = i + 1
                            endloop
                        endif
                       
                        // if this runs, the split was dropped at unit's position
                       
                    else
                        // not splitable, do nothing
                       
                    endif
               
                else
                    // order was on other slot, try to strack
                   
                    if not ItemStack.stackItems(itemOrigin, UnitItemInSlot(u, slotTarget)) then
                        if IsUnitInGroup(u, Inventory.GROUP) then
                            set drop_enabled = false
                            call this.inventory[this.vectorPos].switchItems(slotOrigin, slotTarget)
                            set drop_enabled = true
                        endif
                    endif
                endif
            endif
            //! runtextmacro UNIT_INTERFACE_ON_ORDER_CLEAN()
        endif
    endmethod
   
    public static method operator pickUp_enabled= takes boolean flag returns nothing
        set pickup_enabled_p = flag
    endmethod
    private static method onPickup takes nothing returns nothing
        if pickup_enabled_p then
            call Inventory[GetTriggerUnit()].addItem(GetManipulatedItem())
        endif
    endmethod
   
    public static method operator drop_enabled= takes boolean flag returns nothing
        set drop_enabled_p = flag
    endmethod
     // clean from inventory
    private static method onDrop takes nothing returns nothing
        local item it
        if drop_enabled_p then
           
            if IsUnitInGroup(GetTriggerUnit(), Inventory.GROUP) then
                set it = GetManipulatedItem()
                if InventoryData.isItemOwned(it) then
                    call InventoryData.getItemVector(it).removeItem(it)
                endif
                set it = null
            endif
        endif
    endmethod
   
    private static method onInit takes nothing returns nothing
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onOrder)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.onPickup)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DROP_ITEM, function thistype.onDrop)
    endmethod

endstruct
endlibrary

JASS:
library Bag /* v2.1a -- related: hiveworkshop.com/threads/bag-v1-6-1.252002/

*/ requires /*
       
        */ Inventory    /* hiveworkshop.com/threads/bag-v1-6-1.252002/


    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Extenstion library to "Inventory".
        It allows units the usage of the Bag Abilty,
        and handles all kind of item drop and pickup internaly with using the Inventor's API.
       

    Mechanics
    ¯¯¯¯¯¯¯¯¯¯
   
        Items registered in Inventory are hidden by default.
        The Bag library switches between the current items inside the unit inventory,
        and the hidden items from Inventory, depending on the usage of the Bag ability.
       
       
    Importing
    ¯¯¯¯¯¯¯¯¯¯
   
        Copy the bag ability to your map.
        Ensure that ABILITY_ID (directly after API docu) hold the bag's rawcode.
   
*/

//  --- API --- 
//! novjass
    struct Bag
   
    //  Functions to handle bags. (The bag ability will be added/removed automaticaly)
   
        static method getBagAmount takes unit u returns integer
        static method setBagAmount takes unit u, integer amount returns boolean
        static method addBag takes unit u, integer amount returns nothing
       
        static method getCurrentBag takes unit u returns integer
        static method setCurrentBag takes unit u, integer whichBag returns boolean
        static method removeBag takes unit u, integer whichBag returns boolean
       
    // Functions to handle inventory.
       
        static method getItem takes unit u, integer whichBag, integer whichSlot returns item
        static method setItem takes unit u, item it, integer whichBag, integer whichSlot returns boolean
        static method removeItem takes unit u, integer whichBag, item it returns boolean
       
    // The size of a bag equals the "UnitInventorySize()" of the unit when registered.
       

    //  You can registder code that runs when a unit uses bag ability. 
   
         static method register takes boolexpr bx returns triggercondition
         static method unregister takes triggercondition tc returns nothing
         
         //inside the code refer to:
            static unit Unit
//! endnovjass
// ==== End API ====
   
    struct Bag extends array
        private static constant integer ABILITY_ID = 'A000'
       
        private static constant trigger Handler = CreateTrigger()
        public static unit Unit
        public static method register takes boolexpr bx returns triggercondition
            return TriggerAddCondition(Handler, bx)
        endmethod
        public static method unregister takes triggercondition tc returns nothing
            call TriggerRemoveCondition(Handler, tc)
        endmethod
       
        public static method getBagAmount takes unit u returns integer
            return Inventory[u].getInventoryAmount()
        endmethod
       
        public static method setBagAmount takes unit u, integer amount returns boolean
            local Inventory this
            local integer size = getBagAmount(u)
            local integer i
            local boolean initCurrentBag
           
            if amount == size then
                return true
            endif
           
            set this = GetUnitId(u)
            set initCurrentBag = false
           
            if amount < 1 then
                if size < 1 then
                    return true
                endif
               
                // Remove all bags from unit
                if getCurrentBag(u) != 0 then
                    call setCurrentBag(u, 0)
                    set Unit = u
                    call TriggerEvaluate(Handler)
                endif
                call UnitRemoveAbility(u, ABILITY_ID)
                return Inventory[u].flush()
            endif
       
            if size > 0 then
               
                // Remove some bags from unit
                if amount < size then
               
                    if amount < getCurrentBag(u) then
                        call setCurrentBag(u, amount)
                        set Unit = u
                        call TriggerEvaluate(Handler)
                    endif
                   
                    loop
                        exitwhen amount >= size
                        call Inventory[u].removeInventory(size)
                        set size = size - 1
                    endloop
                endif
            else
                set initCurrentBag = UnitAddAbility(u, ABILITY_ID)
            endif
           
            call Inventory[u].setInventoryAmount(amount)
            loop
                exitwhen size > amount
                call this.inventory[size].init(5)
                set size = size + 1
            endloop
           
            if initCurrentBag then
                // apply current inventory to first bag
                set size = UnitInventorySize(u)
                loop
                    exitwhen size < 0
                    call this.inventory[0].addItemAtSlot(UnitItemInSlot(u, size), size)
                    set size = size - 1
                endloop
            endif
           
            return true
        endmethod
       
        public static method addBag takes unit u, integer amount returns nothing
            call setBagAmount(u, getBagAmount(u) + amount)
        endmethod
       
        private static constant integer SLOT_OFFSET = 852002
        private static method openInventory takes unit u, integer vectorPos returns nothing
            local Inventory this = GetUnitId(u)
            local integer i = this.inventory[vectorPos].item.size() - 1
            local item it
            set UnitInventory.pickUp_enabled = false
            set UnitInventory.order_enabled = false
            loop
                exitwhen i < 0
               
                set it = this.inventory[vectorPos].item[i]
                if it != null then
                    call SetItemVisible(it, true)
                    call UnitAddItem(u, it)
                    // For safety, move item to wanted slot
                    call IssueTargetOrderById(u, SLOT_OFFSET + i, it)
                endif
                set it = null
                set i = i - 1
            endloop
            set UnitInventory.pickUp_enabled = true
            set UnitInventory.order_enabled = true
            set it = null
        endmethod
       
        private static method dropCurrentInventory takes unit u returns nothing
            local integer i = UnitInventorySize(u)
            local item it
            set UnitInventory.drop_enabled = false
            loop
                exitwhen i < 0
                set it = UnitItemInSlot(u, i)
                if (it != null) then
                    call UnitRemoveItem(u, it)
                    call SetItemVisible(it, false)
                endif
                set i = i - 1
            endloop
            set UnitInventory.drop_enabled = true
        endmethod
       
        public static method getCurrentBag takes unit u returns integer
            if getBagAmount(u) == 0 then
                return -1
            else
                return Inventory[u].vectorPos
            endif
        endmethod
       
        public static method setCurrentBag takes unit u, integer whichBag returns boolean
       
            if (getBagAmount(u) == 0 or (not UnitAlive(u)) or (whichBag > getBagAmount(u)) )then
                return false
            endif
            if (whichBag == getCurrentBag(u) ) then
                return true
            endif
           
            call dropCurrentInventory(u)
            set Inventory[u].vectorPos = whichBag
            call openInventory(u, whichBag)
            return true
        endmethod
       
        private static method onCast takes nothing returns nothing
            local unit u
           
            if (GetSpellAbilityId() == ABILITY_ID) then
               
                set u = GetTriggerUnit()
               
                if getCurrentBag(u) == getBagAmount(u) then
               
                    // open 1st bag
                    call setCurrentBag(u, 0)
                   
                else
                    // open next bag
                    call setCurrentBag(u, getCurrentBag(u) + 1)
               
                endif
                set Unit = u
                call TriggerEvaluate(Handler)
            endif
            set u = null
        endmethod
       
        public static method removeBag takes unit u, integer whichBag returns boolean
            return Inventory[u].removeInventory(whichBag)
        endmethod
       
        public static method getItem takes unit u, integer whichBag, integer whichSlot returns item
            return Inventory[u].inventory[whichBag].item[whichSlot]
        endmethod
       
        public static method removeItem takes unit u, integer whichBag, item it returns boolean
            return Inventory[u].inventory[whichBag].removeItem(it)
        endmethod
       
        public static method setItem takes unit u, item it, integer whichBag, integer whichSlot returns boolean
            return Inventory[u].inventory[whichBag].addItemAtSlot(it, whichSlot)
        endmethod
       
        private static method onInit takes nothing returns nothing
            //call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onCast)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onCast)
        endmethod

    endstruct
endlibrary

JASS:
library FullInventory /* v1.1a -- related: hiveworkshop.com/threads/bag-v1-6-1.252002/

*/ requires /*
       
        */ Inventory                /* hiveworkshop.com/threads/bag-v1-6-1.252002/
        */ OrderType                /* hiveworkshop.com/threads/ordertype.291223/
        */ TimerUtils               /* Vexorian -- wc3c.net/showthread.php?t=101322


    Information
    ¯¯¯¯¯¯¯¯¯¯¯

        Extenstion library to "Inventory".
        It allows to pickup/stack items with having full inventory.
        The default wc3 mechnaics don't allow picking up items with full inventory.

    Mechanics
    ¯¯¯¯¯¯¯¯¯¯
   
        When a unit gets a move/smart order towards an item, and at same time
        it has full inventory, the unit would stop with error "Full Inventory".
        The system ignores this error, and orders the unit to move to x/y of wanted item,
        so it looks like the unit accpeted the first order. When the unit is close enough
        of the target item, the system will emulate a possible pickup/stacking with the item.
       
        ATTENTION.
       
        The default wc3 mechanis will play an eror sound and display a error message,
        when a unit tries to pickup an item with full inventory.
        You easily can disable both.
       
        GameInterface -> Text-Message-FullInventory -> Shift-double click it, and write in a space character.
        GameInterface -> Sound-FullInventory -> Shift-double click it, and write in a space character.
*/

//  API -- You can registder code that runs when a unit tries to add an item to a full inventory.
//! novjass
    struct FullInventory
   
         static method register takes boolexpr bx returns triggercondition
         static method unregister takes triggercondition tc returns nothing
         
         //inside the code refer to:
            static unit Unit
//! endnovjass
// ==== End API ====      
       
struct FullInventory extends array

   
    // set this to something close to the value inside your gameplay constants
    private static constant real PICKUP_RANGE = 120.
   
// ============================================================== //

    private unit whichUnit
    private item whichItem
    private real x
    private real y
    private timer clock
   
    private static constant trigger Handler = CreateTrigger()
    public static unit Unit
    public static method register takes boolexpr bx returns triggercondition
        return TriggerAddCondition(Handler, bx)
    endmethod
    public static method unregister takes triggercondition tc returns nothing
        call TriggerRemoveCondition(Handler, tc)
    endmethod
   
    private method destroy takes nothing returns nothing
        if IsUnitInGroup(.whichUnit, GROUP) then
            call GroupRemoveUnit(GROUP, .whichUnit)
            call ReleaseTimer(.clock)
            set .whichItem = null
            set .whichUnit = null
        endif
    endmethod
   
    // check if item is still available to be picked up
    private static method itemValidate takes item it returns boolean
        return GetItemTypeId(it) != 0 and IsItemVisible(it) and not IsItemOwned(it)
    endmethod
   
    // callback perodicly checks distance from unit and item, and then adds to inventory
   
    private static constant real INTERVAL = 0.1
    private static method callback takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
       
        if (itemValidate(.whichItem)) then
           
            if (IsUnitInRangeXY(.whichUnit, .x, .y, PICKUP_RANGE)) then
               
                // double check with the current x/y of the item, to ensure the item wasn't moved somewhere else, however
                if IsUnitInRangeXY(.whichUnit, GetItemX(.whichItem), GetItemY(.whichItem), PICKUP_RANGE + 1) then
                    call Inventory[.whichUnit].addItem(.whichItem)
                endif
                set cancel_enabled_p = false
                call IssueImmediateOrderById(.whichUnit, ORDER_stop)
                set cancel_enabled_p = true
                call .destroy()
            endif
        else
             
            set cancel_enabled_p = false
            call IssueImmediateOrderById(.whichUnit, ORDER_stop)
            set cancel_enabled_p = true
            call .destroy()
           
        endif
    endmethod
   
    // start periodic callback
    private static constant group GROUP = CreateGroup()
    private static method create takes unit u, item it returns thistype
        local thistype this = GetUnitId(u)
        set .whichUnit = u
        set .whichItem = it
        set .x = GetItemX(it)
        set .y = GetItemY(it)
       
        set cancel_enabled_p = false
        call IssuePointOrderById(u, ORDER_move, .x, .y)
        set cancel_enabled_p = true
       
        if not IsUnitInGroup(u, GROUP) then
            set .clock = NewTimerEx(this)
            call TimerStart(.clock, INTERVAL, true, function thistype.callback)
            call GroupAddUnit(GROUP, u)
        endif
       
        return this
    endmethod
   
    // kein patrol, no
    private static method isMoveOrder takes integer orderId returns boolean
        return orderId == ORDER_smart or orderId == ORDER_move
    endmethod
   
    // onTargetOrder actually
    private static method onOrder takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local item it = GetOrderTargetItem()
        if it != null and isMoveOrder(GetIssuedOrderId()) and (UnitInventory.getFreeSlot(u) == -1) then
            // order was on item, and inventory is full
           
            if (it != thistype(GetUnitId(u)).whichItem) then
                if IsUnitInGroup(u, Inventory.GROUP) then
                    if (Inventory[u].hasFreeSlot() or Inventory[u].hasStackableItem(it)) then
                        // start callback
                        call create(u, it)
                    else
                        set Unit = u
                        call TriggerEvaluate(Handler) 
                    endif
                elseif (UnitInventory.getItemStackableSlot(u, it) != -1) then
                     // start callback
                    call create(u, it)
                else
                    set Unit = u
                    call TriggerEvaluate(Handler) 
                endif
               
            else
                // targeted same item, do nothing
               
                // actually we must order unit to move again,
                // because by default it would stop, as it received a move order on an item with having ful inventory
                set cancel_enabled_p = false
                call IssuePointOrderById(u, ORDER_move, GetItemX(it), GetItemY(it))
                set cancel_enabled_p = true
            endif
        elseif IsUnitInGroup(u, GROUP) then
       
            // targeted something different, stop callback
            call thistype(GetUnitId(u)).destroy()
        endif
       
        set u = null
        set it = null
    endmethod
   
    /*
        onCancel will cancel the periodic check for any new no-target, or point-orders the unit gets
        because then, the unit obviously doesn't wanna pick up the item anymore.
   
        problem:
        The problem is that the unit has currently a "move" order onto a point,
        instead of a default itemTarget-order.
        So, when a Queue-Order like Avatar finished, then it will automaticaly order this normal move-point order again.
        The problem is, by nature we don't trust move orders, and usually cancel the periodic check.
        So the trick is, after we ordered a Queue-Order, we allow the same unit to make ONE move order,
        and if the move order is exactly the same as the old, means same x/y and onto a point, then we allow it.
        We also make a item validation check, to check if item visible at that very position, and pickable.
        These together hopefully ensure not to fail.
   
    */
   
    private static boolean cancel_enabled_p = true
    private boolean dirtyTrick
    private static method onCancel takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local integer orderId = GetIssuedOrderId()
        local thistype this
        if IsUnitInGroup(u, GROUP) and cancel_enabled_p and OrderType[orderId] != OrderType.INSTANT then
           
            set this = thistype(GetUnitId(u))
           
            if OrderType[orderId] == OrderType.QUEUE then
                set .dirtyTrick = true
            elseif not .dirtyTrick or not (orderId == ORDER_move) or not (GetOrderPointX() == .x) or not  (GetOrderPointY() == .y) and itemValidate(.whichItem) then
                set .dirtyTrick = false
                call .destroy()
            endif
        endif
        set u = null
    endmethod
   
    private static method onDeindex takes nothing returns boolean
        if IsUnitInGroup(GetIndexedUnit(), GROUP) then
            call thistype(GetUnitId(GetIndexedUnit())).destroy()
        endif
        return false
    endmethod

    private static method onInit takes nothing returns nothing
        //call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onCancel)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function thistype.onCancel)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.onCancel)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onCancel)
        call OnUnitDeindex(function thistype.onDeindex)
       
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function thistype.onOrder)
    endmethod
endstruct
endlibrary

JASS:
module ItemStackConfig /* v1.0a -- related: hiveworkshop.com/threads/bag-v1-6-1.252002/

    If no specific limit is defined the DEFAULT_STACK_LIMIT will be applied.
 
*/

    public static constant integer DEFAULT_STACK_LIMIT = 3
    
//! novjass

struct ItemStack

        static method operator [] takes integer itemType returns integer
            // returns stack limit for this item type
    
        static method exists takes integer itemType returns boolean
            // returns if a stack limit was defined
    
         static method SetStackLimit takes integer itemType, integer limit returns nothing
            // only here in this onInit allowed to be used
//! endnovjass

    private static method onInit takes nothing returns nothing

        // Example for big healing potion:
        // call SetStackLimit('pghe', 4)
    
        call SetStackLimit('pghe', 4)
    endmethod
endmodule

JASS:
module ItemSplitConfig /* v1.0a -- related: hiveworkshop.com/threads/bag-v1-6-1.252002/

    If no specific limit is defined the default split amount will be "1".
    Also, if the item does have less stacks than the defined amount it will also result in an "1" amount split.
*/

//! novjass

struct ItemSplit
    
        static method operator [] takes integer itemType returns integer
            // returns split amount for this item type
    
        static method exists takes integer itemType returns boolean
            // returns if a split amount was defined

        static method SetSplitAmount takes integer itemType, integer amount returns nothing
            // only here in this onInit allowed to be used
//! endnovjass

    private static method onInit takes nothing returns nothing

        // Example for big healing potion:
        //call SetSplitAmount('pghe', 2)
    
        call SetSplitAmount('pghe', 2)
    endmethod
endmodule

Changelog

v2.1
- fixes
- some new functions

v2.0a tiny change
v2.0 Inventory -- a completly new system

v1.0 Bag - until v1.6
Previews
Contents

Bag v1.6.1 (Map)

Inventory v2.1a (Map)

Reviews
KILLCIDE
Awesome system you have here, IcemanBo. Alongside your clean and well documented code, the modules you added are a plus. The overall usage of the system ingame feels smooth. Needs Fixed Nothing Suggestions It would be great if you can get this to...

Moderator

M

Moderator

12:19, 29th Aug 2014
PurgeandFire: (old) Long awaited review:
http://www.hiveworkshop.com/forums/2579621-post20.html

Approved.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
JASS:
**                        API                                            **
**                                                                       **
**  AddBag takes unit, integer returns nothing                           **                                                                      
**      unit     : whichunit                                             **
**      integer  : bags       (also negative value possible)             **
**  Adds/Removes the wanted amount of bags.                              **
**                                                                       **
**  SetBag takes unit, integer returns nothing                           **
**      unit     : whichunit                                             **
**      integer  : bags                                                  **
**  Directly sets the wanted amount of bags.                             **
**                                                                       **
**  FlushBagUnit takes unit returns nothing                              **
**      unit     : whichunit
honestly, that's quite unreadable :c

private constant integer SMART = 851971 // smart-order (right click)
afaik, units can also be ordered to pick item by ordering them to "move" to the target item (I mean by clicking move icon then target the item) so you should define that even too

other than that, looks good and obviously useful.
 
If more will complain about readability, I'm willing to change the description.

afaik, units can also be ordered to pick item by ordering them to "move" to the target item (I mean by clicking move icon then target the item) so you should define that even too
Hm, yes... about the move-order you might be right. :S ...

Order check is only needed for picking up items with full inventory. And as using the move-order for this would only mean more effort for the user, I don't know if this optimization is very needed.

This also would maybe imply a bit that the user would want the system not to work correctly on purpose... I think it does it's job atm. I wait what other will say.

But for now, thank you for this remark. :)

Edit:

I've added the information about move order into opening post.
 
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
A great idea, but there are some bugs. Well, you get 15 claws of attack. Of course 1 goes to the 1st bag. You click on the ability and you open the 1st bag: there is 1 type of that claws. You drop that item. Then return to your main inventory. the 6th claw isn't there. You go to the 1st bag again and automatically the item you had dropped (from the 1st bag) returns to the bag.
Fix it! :thumbs_up:
I like the idea very well.
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
Yes it was my bug (???) You meant to check the map again?
Looks like i found the the sequence of the problem:
Make the unit with 3 bags.
You get 6 claws of attack +15, and then another one, which when you click on it displays the message and sound error about the full inventory. Click on bag ability, you're in "1st" bag, drop the item on the ground, notice where you dropped it. Click on bag again 2 times, you're in "3rd" bag, you notice that there is an item that wasn't there, which was the item you had dropped. It seems that this happens always after 2 bags (from the first to third).
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
  • JASS:
    private constant integer BAG_SPELL_ID     = 'A000'   // Bag ability is an optional feature. This is only needed if you want
    If it is optional, then you could just remove it and add a function called "open next/previous page" so user can manually specify the event. Then add a demo trigger that shows how to do.
  • JASS:
    **  FlushBagUnit takes unit returns nothing                              
    **      - Use this for example if you want to remove a unit from the game,
    **        or want completly remove it as "Bag-Unit"
    **      - It will remove all bags from the unit, and flushes the hashtable
    You should utilize any UnitIndexer and do that Flush-ing at deindex event. So user dont have to do it by their own. For user convenience.
  • JASS:
        private constant boolean TEXT_ONE         = true     // Will print info about picked up items
        private constant boolean TEXT_TWO         = true     // Will print when unit switches bags
    I think that is unnecessary. Mostly user would set it to false :p You could just add a function that returns the last picked item's info or something GetLastPickedItem(), but it's still just a code bloat.
  • JASS:
    function SetBag takes unit u, integer bags returns nothing
        if bags <= 0 then
            call AddBag(u, -MAX_BAG_AMOUNT)
        else
            if bags > MAX_BAG_AMOUNT then
                set bags = MAX_BAG_AMOUNT
            endif
            call AddBag(u, bags - LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY))
        endif
    =>
    JASS:
    function SetBag takes unit u, integer bags returns nothing
        call AddBag(u,  bags - LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY))
    endfunction
    You don't need additional ITE since I think your AddBag function has considered any kind of errors properly.
  • Optional:
    JASS:
    constant function MaxBags takes nothing returns integer
        return MAX_BAG_AMOUNT
    endfunction
    Remove it and make MAX_BAG_AMOUNT become a public
 
Review

  • You have a lot of loops that determine where the item is in the inventory (or whether it exists at all).
    Always keep the DRY rule in mind (Don't Repeat Yourself). Delegate the process to a separate function instead:
    JASS:
    private function GetUnitItemSlot takes unit u, item check returns integer
        local integer i = 0
        loop
            exitwhen i == 6
            if UnitItemInSlot(u, i) == check then
                return i
            endif
            set i = i + 1
        endloop
        return -1
    endfunction

    That will simplify a lot of blocks, and it'll make things easier to read. For example:
    JASS:
    set i = 0
                    loop
                        if UnitItemInSlot(u, i) == it then
                            call SaveItemHandle(hash, handleId, bagUsed*6 + i, it)
                            set u = null
                            set it = null
                            set it2 = null
                            return false
                        endif
                        set i = i + 1
                        exitwhen i > 5
                    endloop
    Becomes:
    JASS:
    set slot = GetUnitItemSlot(u, it)
    if slot != -1 then // -1 signifies that the unit does not have the item
        call SaveItemHandle(hash, handleId, bagUsed*6 + i, it)
        set u = null
        set it = null
        set it2 = null
        return false
    endif
    It may not seem like that much of a difference, but it compacts the code and makes its purpose clear.
  • In the function "Order", you should set bagUsed immediately to bagUsed*6:
    JASS:
    set bagUsed = LoadInteger(hash, handleId, CURRENT_BAG_KEY)*6
    (only the first assignment) That way, you don't have to calculate the *6 each time you use it for the hashtable.
  • In the function "Order", set maxBag immediately to (maxBag + 1)*6, like so:
    JASS:
    set maxBag = (LoadInteger(hash, handleId, MAX_BAG_KEY)+1)*6
  • In "CheckDistance", take advantage of the native "IsUnitInRange". It'll also allow you to eliminate some local variables (x, y, itX, itY):
    JASS:
    if SquareRoot(((itX - x)*(itX - x)) + ((itY - y)*(itY - y))) < PICKUP_RANGE then
    ->
    JASS:
    if IsUnitInRange(u, LoadReal(hash, handleId, X_KEY), LoadReal(hash, handleId, Y_KEY), PICKUP_RANGE) then
    That line is a little long, so you can still store itX and itY in locals if you'd like. Although, it would chop off 4 lines if
    you use it the way above.
  • In "Drop", you don't need maxBag.
  • In "Drop", this is a little unnecessary:
    JASS:
        loop
            exitwhen it == LoadItemHandle(hash, handleId, i)
            set i = i + 1
        endloop
    That has O(N) complexity, where N = number of items stored in the hashtable. Instead, whenever you store the item
    in a hashtable, just store the index under the handleID of the item. e.g.
    JASS:
    call SaveInteger(hash, GetHandleId(it), 0, i)
    That way, instead of the loop, you can just load it as so:
    JASS:
    call RemoveSavedHandle(hash, handleId, LoadInteger(hash, GetHandleId(it), 0))
    (something like that) It is a bit complex to explain. Feel free to PM me if you need a better explanation.
    Since complexity is a difficult concept to grass, and often unnecessary for practical applications, this change is optional.
  • Also check out Dalvengyr's review in the post above mine.

Other than that, it is a pretty nice system. It seems to work just fine, it only needs coding fixes.
This could be improved with structs, but use whatever you're most comfortable with. :)
 
JASS:
private constant integer BAG_SPELL_ID     = 'A000'   // Bag ability is an optional feature. This is only needed if you want
If it is optional, then you could just remove it and add a function called "open next/previous page" so user can manually specify the event. Then add a demo trigger that shows how to do.
I would like to keep it just with this ability.
JASS:
**  FlushBagUnit takes unit returns nothing                              
**      - Use this for example if you want to remove a unit from the game,
**        or want completly remove it as "Bag-Unit"
**      - It will remove all bags from the unit, and flushes the hashtable
You should utilize any UnitIndexer and do that Flush-ing at deindex event. So user dont have to do it by their own. For user convenience.
Actually this is not necessarily binded with deindexing, but you are right if user uses an unit indexer then it would be comfortable to bind it.
But still, no indexer is needed to use this function, because you also can remove it as "Bag-Unit" without removing it from the game.

JASS:
function SetBag takes unit u, integer bags returns nothing
    if bags <= 0 then
        call AddBag(u, -MAX_BAG_AMOUNT)
    else
        if bags > MAX_BAG_AMOUNT then
            set bags = MAX_BAG_AMOUNT
        endif
        call AddBag(u, bags - LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY))
    endif
=>
JASS:
function SetBag takes unit u, integer bags returns nothing
    call AddBag(u,  bags - LoadInteger(hash, GetHandleId(u), MAX_BAG_KEY))
endfunction
You don't need additional ITE since I think your AddBag function has considered any kind of errors properly.
Oh yeah.. you're absolutly right, bro.

Optional:
JASS:
constant function MaxBags takes nothing returns integer
    return MAX_BAG_AMOUNT
endfunction
Remove it and make MAX_BAG_AMOUNT become a public
Well, I prefer it this way. (ugly constant name :p )

Thank you for your points. :csmile:



Yes, you are right about using a custom function GetUnitItemSlot not to repeat myself. It doesn't help a lot in my case, but I changed it anyway.
I only use it (twice) now, if I can't ensure that unit has the item picked up, else I still prefer that way:
JASS:
set i = 0
loop
    exitwhen UnitItemInSlot(u, i) == it
    set i = i + 1
endloop
^It is still very short, and faster. After loop ends I can immediatly work with i as slot index.

I have not changed it to structs, else everything is changed that you mentioned, thanks. :)

Thanks both.

Changes made and updated.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
The only downsides of this system are:
- Can't add items to specific bag
- Can't manipulate items (move, swap, drop, etc.) in not displayed bag (using code)
- Can't move to (or display) previous bag, only the next one
- No API to switch between bag (the bag ability is required)

I also think the debug message should be removed. You can provide on pick up event instead.
 
Yes.

Big update, everything was rewritten.
@Quilnez , your API critique should be covered, too, in this version.;)

@TheHerb , was the stack config enough for you?

@KILLCIDE @Tank-Commander @Pyrogasm -- I wanna request to put it back to "Pending", because it's a totaly new system. The old code is still here, to look at.

edit: update soon.
 
Last edited:
Level 1
Joined
Jan 23, 2015
Messages
160
Hello, could you elaborate the issue? Possibly with error description, or demo.

Looks like my problem is from opening a pre 1.29 map with 1.29, it will reject all triggers. The fault is not with the system, should have followed up to state that. My bad!
 
Level 1
Joined
Jan 23, 2015
Messages
160
No, your system will still work in 1.29. What I mean is the problem for me was opening my map in 1.29 World Editor, when I was using a map I was working on with 1.28. The save works for no triggers. There is nothing wrong with this system and it is amazing
 
That's true, but not really unwanted. Implementation of something like BonusMod should probably happen in a seperate addon, but I never came to it. ;s

But for such big inventories where it is really also visually enhanced, TriggerHappy made then a nice system, Inventory & Equipment UI System v1.4.3, so not sure if it's very needed actually.
 
Level 4
Joined
Aug 17, 2014
Messages
102
I'm a complete noob and I'm struggling to make it work on my map. I changed the ID of both spells in the triggers to the appropriate labels and have been trying to see if it works with the demo on my map, but I only get 1 bag and cannot go past that, im not sure if it's something wrong in the bag swith trigger or if it is not adding any more bags
 
Top