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

Bag System and Advanced Item Handling System

These are two very useful systems for RGP makers, they are MUI, leakfree and use vJass so you need JNGP to make them work well!!

Backpack System
the system can add as much 4-slot-bags as you want to as many units as you want.

the unit will get two special items in the last 2 slots:
next bag and previous bag

by using these items you change the active bag for the specific unit.

You can switch between 2 indication modes:
current bag number or next/previous bag number shown on the next and previous bag items...

items with bonuses won't work if they aren't in the active bag!!
it's more realistic:
if you have a sword in your backpack and not in your hand it won't help you!
Advanced Item System
The Advanced Item system improves the warcraft III item handling

if a unit gets an item with charges which it already has it stacks

in addition and thats what makes this system special, this works even if the inventory is full!!!

if you double-right-click an item in your inventory with charges it splits up
and if you move a item onto an equal item and they have charges they stack
the systems work without each other but i suggest using the Advanced Item system if you are using the Bag System for better inventory usage

give credits when used and +rep if you think it's worth it ;)


UPDATE 2.0: Fixed a bug

UPDATE 3.0: The system was never really MUI!! now it is

UPDATE 4.0: The System is now 1.24 compatible

UPDATE 5.0: A bug is fixed, removing a stacked item when double- rightclicking an item in full inventory

UPDATE 6.0: complete code reworked and hashtables integrated.
split into two seperate librarys
coding improved
bugs fixed

UPDATE 7.0: again code improvements and small fixes

UPDATE 8.0: fixed TriggerHappys suggestions, added a feature to switch between current bag number or next/previous bag number shown on the items, improved code

UPDATE 9.0: after a few years and longer inactivity someone complained over outdated/bugged code, so (to prove everyone and myself that i am still there) i rewrote the whole code to newest standard


JASS:
library BagSystem requires Alloc, Table, RegisterPlayerUnitEvent
//
// The_Witcher 's
//                 Bag System
//
// With this System you're able to give as many bags as you want to EVERY unit you want!
// The unit only needs to have the Inventory(hero) ability for an inventory with 6 slots
// you just need to create two useable items: one for next bag and one for previous bag
//
// you can add and remove bags whenever you want
//
// Requirements:
//  Alloc
//  RegisterPlayerUnitEvent
//  Table
//
// struct Bag:
// create takes unit u, integer maxbags
//       if you want a unit to use bags, you need to init that with this function
//
// addBags takes integer bags
//      with this function you can increase or decrease the amount of bags (add a negative number for the 'bags' value to lower it)
//      if there are items in the removed bag they will be dropped
//
// destroy
//     this function simply removes every bag from the unit
//     if you want the unit to use bags again, you have to use create once again
//
// Bag[myUnit]
//      the [] operator returns the Bag instance bound to the given unit


//---------------------------------------------------------
//----------------          SETUP            --------------
//---------------------------------------------------------
    globals
        // this is the rawcode of the item for next bag
        private constant integer NEXT_BAG_ID = 'I001'
     
        // this is the rawcode of the item for previous bag
        private constant integer PREV_BAG_ID = 'I002'
    
        // false= on previous bag is the number of the previous and on next bag the number of the next bag
        // true = on previous and next bag is the number of the current bag
        private constant boolean DISPLAY_CURRENT_BAG = true
        
        // whether to use a hook to destroy Bags automatically if the owner is removed
        private constant boolean REMOVE_HOOK = true
    endglobals

//---------------------------------------------------------
//-----------Don't modify anything below this line---------
//---------------------------------------------------------
    
    
    struct Bag extends array
        private static Table table
        readonly unit unit
        private Table items
        readonly integer max
        readonly integer bag
        
        implement Alloc
        
        static method operator [] takes unit u returns Bag
            return table[GetHandleId(u)]
        endmethod

        private method saveCurrentBag takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i >= 4
                set items[bag * 8 + i * 2] = GetItemTypeId(UnitItemInSlot(unit, i))
                set items[bag * 8 + i * 2 + 1] = GetItemCharges(UnitItemInSlot(unit, i))
                call RemoveItem(UnitItemInSlot(unit, i))
                set i = i + 1
            endloop
        endmethod
        
        private method refreshBagNumbers takes nothing returns nothing
            local integer next = bag + 1
            local integer prev = bag + 1
            call RemoveItem(UnitItemInSlot(unit, 4))
            call RemoveItem(UnitItemInSlot(unit, 5))
            call UnitAddItemToSlotById(unit, PREV_BAG_ID, 4)
            call UnitAddItemToSlotById(unit, NEXT_BAG_ID, 5)
            if not DISPLAY_CURRENT_BAG then
                if bag >= max - 1 then
                    set prev = IMaxBJ(1, bag)
                    set next = 1
                elseif bag <= 0 then
                    set prev = max
                    set next = 2
                else
                    set prev = bag
                    set next = bag + 2
                endif
            endif
            call SetItemCharges(UnitItemInSlot(unit, 4), prev)
            call SetItemCharges(UnitItemInSlot(unit, 5), next)
        endmethod
        
        private method dropLastBag takes nothing returns nothing
            local integer i = 0
            local item it
            local real x = GetUnitX(unit)
            local real y = GetUnitY(unit)
            loop
                exitwhen i >= 8
                set it = CreateItem(items[(max - 1) * 8 + i], x, y)
                call SetItemCharges(it, items[(max - 1) * 8 + i + 1])
                set items[(max - 1) * 8 + i] = 0
                set items[(max - 1) * 8 + i + 1] = 0
                set i = i + 2
            endloop
            set it = null
        endmethod

        method addBags takes integer bags returns nothing
            local integer s = max + bags
            if s < 1 then
                set s = 1
            endif
            if (bags < 0) then
                call saveCurrentBag()
                if (bag >= s) then
                    set bag = s - 1
                endif
                loop
                    exitwhen max == s
                    call dropLastBag()
                    set max = max - 1
                endloop
            else
                set max = max + bags
            endif
            call refreshBagNumbers()
        endmethod
        
        static method create takes unit u, integer maxbags returns Bag
            local Bag this = allocate()
            set unit = u
            set max = maxbags
            set bag = 0
            set items = Table.create()
            call refreshBagNumbers()
            set table[GetHandleId(u)] = this
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            local integer i = 0
            call saveCurrentBag()
            loop
                exitwhen i > 5
                call RemoveItem(UnitItemInSlot(unit, i))
                set i = i + 1
            endloop
            loop
                exitwhen max <= 0
                call dropLastBag()
                set max = max - 1
            endloop
            set table[GetHandleId(unit)] = 0
            call items.destroy()
            call deallocate()
        endmethod
        
        private static method onBagChange takes nothing returns nothing
            local unit u = GetTriggerUnit()
            local Bag b = table[GetHandleId(u)]
            local integer i = 0
            local integer itemID = GetItemTypeId(GetManipulatedItem())
            if (itemID == PREV_BAG_ID) or (itemID == NEXT_BAG_ID) then
                call b.saveCurrentBag()
                if itemID == PREV_BAG_ID then
                    set b.bag = b.bag - 1
                    if b.bag < 0 then
                        set b.bag = b.max - 1
                    endif
                else
                    set b.bag = b.bag + 1
                    if b.bag >= b.max then
                        set b.bag = 0
                    endif
                endif
                loop
                    exitwhen i >= 4
                    call UnitAddItemToSlotById(u, b.items[b.bag * 8 + i * 2], i)
                    call SetItemCharges(UnitItemInSlot(u, i), b.items[b.bag * 8 + i * 2 + 1])
                    set i = i + 1
                endloop
                call b.refreshBagNumbers()
            endif
            set u = null
        endmethod
        
        private static method onInit takes nothing returns nothing
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_USE_ITEM, function Bag.onBagChange)
            set table = Table.create()
        endmethod
         
    endstruct
    
    static if (REMOVE_HOOK) then
    
        private function removeUnitHook takes unit u returns nothing
            if (Bag[u] != 0) then
                call Bag[u].destroy()
            endif
        endfunction
    
        hook RemoveUnit removeUnitHook
        
    endif
    
endlibrary


//Code indented using The_Witcher's Script Language Aligner
//Download the newest version and report bugs at www.hiveworkshop.com

JASS:
library AdvancedItemHandlingSystem requires Alloc, RegisterPlayerUnitEvent, Table
// The_Witcher's
//
// <<advanced item system>>
//
// Requirements:
//  Alloc
//  RegisterPlayerUnitEvent
//  Table
//
// This system improves the Warcraft III item engine
//
// If a unit gets an item which it already has it stacks (only if item has charges!)
// In addition this works even if the inventory is full!!!
//
// If you double-right-click an item with charges in your inventory it splits up
// and if you move an item onto an equal one and they are stackable they stack
//
// No Setup Part! Don't touch anything -> fully automatic :)


    private struct DataStruct extends array
        private static DataStruct array all
        private static integer total = 0
        private static Table table
        private static boolean systemOrder = false
        private static timer timer
        
        unit u
        item ite
        integer index
        real x
        real y
        implement Alloc
        
        private method destroy takes nothing returns nothing
            set table[GetHandleId(u)] = 0
            set total = total - 1
            set all[index] = all[total]
            set all[index].index = index
            call deallocate()
            if (total == 0) then
                call PauseTimer(timer)
            endif
        endmethod

        private static method isInventoryFull takes unit u returns boolean
            local integer i = 0
            loop
                exitwhen i == 6
                if UnitItemInSlot(u, i) == null then
                    return false
                endif
                set i = i + 1
            endloop
            return true
        endmethod

        private static method itemWalk takes nothing returns nothing
            local integer i = 0
            local unit u = GetTriggerUnit()
            local item ite = GetOrderTargetItem()
            local DataStruct this
            if isInventoryFull(u) and GetItemCharges(ite) != 0 then
                loop
                    exitwhen i > 5
                    if GetItemTypeId(ite) == GetItemTypeId(UnitItemInSlot(u, i)) and UnitHasItem(u, ite) == false then
                        call IssuePointOrder( u, "move", GetItemX(ite), GetItemY(ite) )
                        if table[GetHandleId(u)] == 0 then
                            set this = allocate()
                            set all[total] = this
                            set index = total
                            set total = total + 1
                            set .u = u
                            set .ite = ite
                            set x = GetItemX(ite)
                            set y = GetItemY(ite)
                            set table[GetHandleId(u)] = this
                            if (total == 1) then
                                call TimerStart( timer, 0.05, true, function DataStruct.itemTake )
                            endif
                        else
                            set this = table[GetHandleId(u)]
                            set .u = u
                            set .ite = ite
                            set x = GetItemX(ite)
                            set y = GetItemY(ite)
                        endif
                        set i = 100
                    endif
                    set i = i + 1
                endloop
                set this = table[GetHandleId(u)]
                if i < 10 and this != 0 then
                    call destroy()
                endif
            endif
            set u = null
            set ite = null
        endmethod

        private static method itemStack takes nothing returns nothing
            local integer i = 0
            local integer id
            local item k
            local item m = GetManipulatedItem()
            local unit u = GetTriggerUnit()
            local integer ite = GetItemTypeId(m)
            local integer c = GetItemCharges(m)
            local integer it
            if not systemOrder and GetItemCharges(GetManipulatedItem()) > 0 then
                loop
                    exitwhen i > 6
                    set it = GetItemTypeId(UnitItemInSlot(u, i - 1))
                    set k = UnitItemInSlot(u, i - 1)
                    if ( ( it == ite ) and( m != k ) ) then
                        call SetItemCharges( k, GetItemCharges(k) + c )
                        call RemoveItem( m )
                        set i = 10
                    endif
                    set i = i + 1
                endloop
            endif
            set k = null
            set m = null
            set u = null
        endmethod

        private static method itemTake takes nothing returns nothing
            local real dx
            local real dy
            local item it
            local integer i = 0
            local integer j
            local DataStruct this
            loop
                exitwhen i >= total
                set this = all[i]
                set dx = GetItemX(ite) - GetUnitX(u)
                set dy = GetItemY(ite) - GetUnitY(u)
                if ( dx * dx + dy * dy < 10000 ) and IsItemOwned(ite) == false then
                    set systemOrder = true
                    call IssueImmediateOrder(u, "stop")
                    set systemOrder = false
                    set j = 0
                    loop
                        exitwhen j > 5
                        set it = UnitItemInSlot(u, j)
                        if GetItemTypeId(ite) == GetItemTypeId(it) then
                            set j = 5
                        endif
                        set j = j + 1
                    endloop
                    call SetItemCharges( it, GetItemCharges(it) + GetItemCharges(ite) )
                    call RemoveItem( ite )
                    call destroy()
                    set i = i - 1
                endif
                set i = i + 1
            endloop
            set it = null
        endmethod

        private static method itemWalkAbort1 takes nothing returns nothing
            local DataStruct this = table[GetHandleId(GetTriggerUnit())]
            if this != 0 and( GetOrderPointX() != x or GetOrderPointY() != y ) then
                call destroy()
            endif
        endmethod

        private static method itemWalkAbort2 takes nothing returns nothing
            local DataStruct this = table[GetHandleId(GetTriggerUnit())]
            if not systemOrder and this != 0 then
                call destroy()
            endif
        endmethod

        private static method itemSplit takes nothing returns nothing
            local item it = GetOrderTargetItem()
            local integer c = GetItemCharges(it)
            local integer oId = GetIssuedOrderId()
            local unit u = GetOrderedUnit()
            if (oId > 852001 and oId < 852008) then
                if GetOrderTargetItem() == UnitItemInSlot(u, oId - 852002) then
                    if c > 1 then
                        set c = c / 2
                        call SetItemCharges(it, GetItemCharges(it) - c)
                        set systemOrder = true
                        set it = CreateItem(GetItemTypeId(it), GetUnitX(u), GetUnitY(u))
                        call UnitAddItem(u, it)
                        set systemOrder = false
                        call SetItemCharges(it, c)
                    endif
                else
                    if c > 0 and GetItemTypeId(it) == GetItemTypeId(UnitItemInSlot(u, oId - 852002)) then
                        call SetItemCharges(it, GetItemCharges(it) + GetItemCharges(UnitItemInSlot(u, oId - 852002)))
                        call RemoveItem(UnitItemInSlot(u, oId - 852002))
                    endif
                endif
            endif
            set it = null
            set u = null
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function DataStruct.itemWalkAbort1)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function DataStruct.itemWalkAbort2)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function DataStruct.itemWalk)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function DataStruct.itemSplit)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function DataStruct.itemStack)
            set table = Table.create()
            set timer = CreateTimer()
        endmethod

    endstruct

endlibrary


//Code indented using The_Witcher's Script Language Aligner
//Download the newest version and report bugs at www.hiveworkshop.com

Keywords:
bag, system, item, stack, drop, vjass, jngp, rpg, campaign, mui, split, backpack, inventory, handle, improve, full
Contents

Bag System + Item Stack (Map)

Reviews
14:11, 17th Jun 2010 TriggerHappy: Very nice, though I think you should make NextBag/Previous bags charges show the page it will take you to, not the current page. In the future, indent your code better.
Level 19
Joined
Mar 18, 2012
Messages
1,716
The Bag system has some major flaws for RemoveBackpack. Also using this system for units which don't last over the whole game ( those who might be removed) will result in malfunction. Judging on your activity, I guess you won't update your resources anymore. Based on your inventory system I know what your are capable of and therefor I have to say this could be coded better.

Nevertheless it is a very useful system.
 
The Bag system has some major flaws for RemoveBackpack. Also using this system for units which don't last over the whole game ( those who might be removed) will result in malfunction. Judging on your activity, I guess you won't update your resources anymore. Based on your inventory system I know what your are capable of and therefor I have to say this could be coded better.

Nevertheless it is a very useful system.

Guess this is not what you expected :p

I am not dead and hope you and everyone else enjoys this fresh version of my system.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Haha, nice :thumbs_up:.
Well I guess it's now not important anymore but:

I liked the old version aswell, you just didn't set the struct members to 0 when destroying an instance. Meaning if you "re-allocated" the same instance again that new unit would have got the old inventory.

+ some unessesary lines of code.

The hashtable could have been ditched with UnitIndexer + UnitUserData.

I'll look into this tomorrow, when I'm back home and not using a smartphone :/.

Pm me if you plan to change your inventory system (Don't it is good :)), because I published a resource based on it some time ago. ItemPower & ItemSet

Edit: Hooks are ugly features. They inject everything including places in you code where your don't need / want them. You could also run the auto bag removal on Deindex event if you use a UnitIndexer.
 
Last edited:
Level 5
Joined
Dec 21, 2012
Messages
89
Your old version was good allready. It was be better if maybe can be use spellbook ability to save one more space for item in the inventory but I don't know if is possible. 5 items in use is better then 4
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Adding negative bag values results in a bug. The last item page will be deleted and you drop the one before the last.
I.e. You have 4 bags and remove one --> items in bag 4 will be gone forever and items in bag 3 will be dropped.

Remove the hook, it is an absolute no go. Just imagine one doesn't use a dummy resource and has custom spells with 50 projectiles per spell instance or a huge map with a vast battlegrounds.
And this resources tries to check for potential bag removals via hook RemoveUnit? ^^

Like I already said, if you want to add an automatic deallocation use any UnitIndexer's onDeindexEvent and a Filter.
With UnitIndexer you would get rid of the static Table table and could use an static thistype array instead, saving the instance on the UnitUserData instead of the UnitHandle.

readonly unit unit no comment on that ...

set table[GetHandleId(unit)] = 0 --> call table.handle.remove(GetHandleId(unit))

Add a static if so users can either use PREV and NEXT bag or just NEXT bag to save one itemslot.

I wouldn't destroy the Table, but rather flush it and check in create if a Table of this instance does already exist.

Edit: The usage of Table and your algorithm for the childkey is neat.
[bag * 10 + i * 2], i) instead of [bag * 8 + i * 2], i) would allow you add a fifth inventory slot.
 
Last edited:
Adding negative bag values results in a bug. The last item page will be deleted and you drop the one before the last.
I.e. You have 4 bags and remove one --> items in bag 4 will be gone forever and items in bag 3 will be dropped.
I will have a look at that

Remove the hook, it is an absolute no go. Just imagine one doesn't use a dummy resource and has custom spells with 50 projectiles per spell instance or a huge map with a vast battlegrounds.
And this resources tries to check for potential bag removals via hook RemoveUnit? ^^
thats why there's a static if with the corresponding boolean in the setup section, users can choose whether to use it or not

readonly unit unit no comment on that ...
i totally agree with you that it's great to use this for much better readability in the rest of the code! God bless JNGP

set table[GetHandleId(unit)] = 0 --> call table.handle.remove(GetHandleId(unit))

Add a static if so users can either use PREV and NEXT bag or just NEXT bag to save one itemslot.

I wouldn't destroy the Table, but rather flush it and check in create if a Table of this instance does already exist.

Edit: The usage of Table and your algorithm for the childkey is neat.
[bag * 10 + i * 2], i) instead of [bag * 8 + i * 2], i) would allow you add a fifth inventory slot.
thats right, it could support 5 items per slot easily, but i wasnt asked for that yet... of course it's simple to change that, so i might include this option.
 
i totally agree with you that it's great to use this for much better readability in the rest of the code! God bless JNGP
Actually he meant that it's bad to do this. I can't understand why anyone would do this? He gets a member variable that is printed as handletypes. It just confuses everyone using your system.
 
Actually he meant that it's bad to do this. I can't understand why anyone would do this? He gets a member variable that is printed as handletypes. It just confuses everyone using your system.
:D i know, i used irony here.
Scince i mainly use languages as C++ and Java, i am used to have types always starting with a large letter ( MyType ) and variable names which are members of a class in camelCase ( myType ).
This leads to a very good readabylity throughout the code because the types name is in general very close to its purpose.
Like
JASS:
local Matrix matrix
matrix.add(input)
is very readable.
This also applies for WC3 where the only problem is, that native types are all lowercase. But scince the JNGP Compiler still can compile code like
JASS:
struct foo
     unit unit
     real real
endstruct
I really like to use it.
(foo.real = 2.0 and foo.unit = CreateUnit(...) looks nice to me)
 
Level 2
Joined
Sep 21, 2015
Messages
15
when i use this trigger in my map for the item handling it says i need an initialization trigger for adv_itemhandling...... and it deactivates and doesnt work. any idea on a fix?
 
Level 6
Joined
Feb 27, 2009
Messages
30
I'm really considering on using this very useful system. However, there are some things i feel should be added into it. Instead of 'Previous' or 'Next' perhaps put an option to use 'Next' to save one more item slot. Also a why to have every item in your bags to be active at once instead of having to switch between bags to gain item bonuses. Lastly, an option for this to be acquired at some point in a game. Like starting with only bag and later acquiring more and more for multiple players. (Thinking in terms of my own game)
 
Top