• 🏆 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 System Hashtable Returning Null

Status
Not open for further replies.
Level 16
Joined
Mar 27, 2011
Messages
1,349
Hi guys,

I'm trying to create my own basic inventory system. It uses a unit's normal inventory. He has a couple of "bags" (abilities) which swap his inventory. The items are saved into a Hashtable. There is only 1 unit with a "bag" per player.

Inventory System:

  • Inventory Update
    • Events
      • Unit - A unit Acquires an item
      • Unit - A unit Loses an item
      • Unit - A unit Uses an item
    • Conditions
      • (Unit-type of (Triggering unit)) Equal to Pokemon Trainer
    • Actions
      • For each (Integer InvenLoop1) from 1 to 6, do (Actions)
        • Loop - Actions
          • Hashtable - Save Handle Of(Item carried by (Triggering unit) in slot InvenLoop1) as InvenLoop1 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory
  • Inventory Change
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Pokemon Bag
        • Then - Actions
          • Game - Display to (All players) for 3.00 seconds the text: Opened Pokemon Bag
          • Set InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] = 1
          • For each (Integer InvenLoop2) from 1 to 6, do (Actions)
            • Loop - Actions
              • Item - Remove (Item carried by (Triggering unit) in slot InvenLoop2)
              • Hero - Create (Item-type of (Load InvenLoop2 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory)) and give it to (Triggering unit)
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Ability being cast) Equal to Item Bag 1
            • Then - Actions
              • Game - Display to (All players) for 3.00 seconds the text: Opened Item Bag 1
              • Set InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] = 2
              • For each (Integer InvenLoop2) from 1 to 6, do (Actions)
                • Loop - Actions
                  • Item - Remove (Item carried by (Triggering unit) in slot InvenLoop2)
                  • Hero - Create (Item-type of (Load InvenLoop2 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory)) and give it to (Triggering unit)
            • Else - Actions
  • Inventory Test
    • Events
      • Player - Player 1 (Red) types a chat message containing -inven as An exact match
    • Conditions
    • Actions
      • Game - Display to (All players) for 3.00 seconds the text: (InventoryActiveBag = + (String(InventoryActiveBag[(Player number of (Triggering player))])))
      • For each (Integer InvenLoop1) from 1 to 6, do (Actions)
        • Loop - Actions
          • Game - Display to (All players) for 3.00 seconds the text: (Load InvenLoop1 of InventoryActiveBag[(Player number of (Triggering player))] from Inventory)
Unfortunately the test trigger returns null for every value and hence no items are restored when switching between the bags. I'm new to hashtables. Can anyone work out why?

Edit: Actually I think I just worked it out. The Inventory Update fires upon an item being removed in the "Inventory Change" trigger. Lemme test if this is the case...
Edit 2: Nope. That might have caused an issue, but still, the test trigger shouldn't be returning null. Any ideas?
 
Last edited:
Level 16
Joined
Mar 27, 2011
Messages
1,349
Yeah, they are created here


  • Initialization
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Trigger - Run Init Variables <gen> (ignoring conditions)
  • Init Variables
    • Events
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set Inventory = (Last created hashtable)


Edit: More info. Default value of InventoryActiveBag = 1. The unit's items are first received via this trigger when the player is first created.


  • Start Name
    • Events
      • Player - Player 1 (Red) types a chat message containing <Empty String> as A substring
      • Player - Player 2 (Blue) types a chat message containing <Empty String> as A substring
      • Player - Player 3 (Teal) types a chat message containing <Empty String> as A substring
      • Player - Player 4 (Purple) types a chat message containing <Empty String> as A substring
      • Player - Player 5 (Yellow) types a chat message containing <Empty String> as A substring
      • Player - Player 6 (Orange) types a chat message containing <Empty String> as A substring
      • Player - Player 7 (Green) types a chat message containing <Empty String> as A substring
      • Player - Player 8 (Pink) types a chat message containing <Empty String> as A substring
    • Conditions
      • CharacterCreated[(Player number of (Triggering player))] Equal to False
    • Actions
      • Set CharacterCreated[(Player number of (Triggering player))] = True
      • Set TempPoint1 = (Center of Pallet Town Home <gen>)
      • Unit - Create 1 Pokemon Trainer for (Triggering player) at TempPoint1 facing Default building facing degrees
      • For each (Integer A) from 1 to 6, do (Actions)
        • Loop - Actions
          • Hero - Create Empty Socket and give it to (Last created unit)
      • Unit - Set Name of (Last created unit) to (Entered chat string)
      • Camera - Lock camera target for (Triggering player) to (Last created unit), offset by (0.00, 0.00) using Default rotation
      • Custom script: call RemoveLocation (udg_TempPoint1)
 
Last edited:
Level 16
Joined
Mar 27, 2011
Messages
1,349
Thanks for writing this system for me. Your approach saves items upon changing bags which is probably a better way to do it. My main question follows below:

I used a hastable like this:

Save ID (Item) in key (inventory number) of key (InventoryBagNumber[Player Number(Owner of Triggering Unit)]) into Hashtable (Inventory)

You use a Hashtable like this:

Save ID (Item) in key (unit) of key (InventoryBagNumber) into Hashtable (Inventory)

Hashtables are new to me. To my research, it seems I've tried to substitute them for a 3D array. Why did you choose to use a Hashtable like this? Does it have something to do with why my system didn't work? I should add that there is only 1 trainer per player at the moment.



Edit:
I modified my code as per below with no need for a separate "Update" trigger. Interestingly, the save message I added reports the items as expected, however, items are not retained during bag (inventory) changes.

  • Inventory Change
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Pokemon Bag
        • Then - Actions
          • Game - Display to (All players) for 3.00 seconds the text: Saving Pokemon bag ...
          • For each (Integer InvenLoop1) from 1 to 6, do (Actions)
            • Loop - Actions
              • Hashtable - Save Handle Of(Item carried by (Triggering unit) in slot InvenLoop1) as InvenLoop1 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory
              • Game - Display to (All players) for 3.00 seconds the text: (Name of (Load InvenLoop1 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory))
          • Set InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] = 1
          • For each (Integer InvenLoop2) from 1 to 6, do (Actions)
            • Loop - Actions
              • Item - Remove (Item carried by (Triggering unit) in slot InvenLoop2)
              • Hero - Create (Item-type of (Load InvenLoop2 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory)) and give it to (Triggering unit)
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Ability being cast) Equal to Item Bag 1
            • Then - Actions
              • Game - Display to (All players) for 3.00 seconds the text: Saving Item Bag 1 I...
              • For each (Integer InvenLoop1) from 1 to 6, do (Actions)
                • Loop - Actions
                  • Hashtable - Save Handle Of(Item carried by (Triggering unit) in slot InvenLoop1) as InvenLoop1 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory
                  • Game - Display to (All players) for 3.00 seconds the text: (Name of (Load InvenLoop1 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory))
              • Game - Display to (All players) for 3.00 seconds the text: Opened Item Bag 1
              • Set InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] = 2
              • For each (Integer InvenLoop2) from 1 to 6, do (Actions)
                • Loop - Actions
                  • Item - Remove (Item carried by (Triggering unit) in slot InvenLoop2)
                  • Hero - Create (Item-type of (Load InvenLoop2 of InventoryActiveBag[(Player number of (Owner of (Triggering unit)))] in Inventory)) and give it to (Triggering unit)
            • Else - Actions
Sorry for the hassle. I could just use your system, but I need to understand where I'm going wrong.
 
Last edited:
Level 18
Joined
Nov 21, 2012
Messages
835
In GUI:
  • Hashtable - Save Handle Of(Item carried by tempU in slot 1) as 2[childKey] of 3[parentKey] in Inventory
in jass:
SaveItemHandle(table, parentKey, childKey, whichItem)

you're using as parentKey integer ActiveBagNumber. This limits your system to one unit per player. Also you cannot have 2 diffrent items saved in the same slot, from 2 separate bags. You have to load one item first and then you can write new item.
I used a trick set childKey=(currentBag*10)+slot which allows to use multiple bags (not only two).
By using as parentKey handleId of unit it makes system MUI. You can use many units for every player that can benefit from the system

Diffrent thing is when you remove item and then create item-type you will lose cooldown on removed useable items, also you will lose charges information.
 
Level 16
Joined
Mar 27, 2011
Messages
1,349
Ahh it just clicked! My parentkey in its current state was being overwritten! Your parent key is unique to the unit so it's easier to work with (and MUI). The childkey contains all the Inventory information. 1000 contains the unit's bag number (integer data type), then slot 11, 12, 13, etc 21, 22, 23, etc contains the item information (item data type)

Even though each player only has 1 unit, I might just make my system MUI for good practice and so it can potentially expand later on. Charges and cooldowns is something I hadn't thought of so I'll have to hide and show items as you have.

I intend to implement a save load system after my other systems are finished. Now you've brought it to my attention, I'll need to save charge count information. I think I know how to do this.

A couple of jass questions (sorry, lol)

Why do you remove the item then hide it? If the item is saved in the hashtable, is the item cooldown remembered? Would unhiding or performing other actions on an itm after it's removed cause some kind of error?

JASS:
function switchBag
call UnitRemoveItem(u, itm)
call SetItemVisible(itm, false)


function loadBag
call SetItemVisible(itm, true)
call UnitAddItem(u, itm)

Why use a timer to call a function since it has a 0.00 second delay on it instead of calling normally like call Timer_LoadBag?

JASS:
call TimerStart(t, 0.00, false, function Timer_LoadBag)

Edit: You are using the timer variable to somehow initialize the locals at the start of the function.
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,010
Why use a timer to call a function since it has a 0.00 second delay on it instead of calling normally like call Timer_LoadBag?
I didn't open his map to see specifically what that function does, but in general there are some things that the game won't update until it runs a new code thread. So something happens in Timer_LoadBag that would fail if it happened in the thread that calls that TimerStart(). A 0.00 timeout timer is similar to using ExecuteFunc() or yourfunctionname.execute(); sometimes one method will work but others will not, it depends on what you're doing.
 
Level 18
Joined
Nov 21, 2012
Messages
835
Why do you remove the item then hide it?
No, no, the native UnitRemoveItem means we drop an item from hero inventory and place this item on the ground near unit.

Why use a timer to call a function since it has a 0.00 second delay
As @Pyrogasm said not all actions can run in one thread. After I wrote functions for you and tested it appears that UnitDropItemSlot is not working in one function as 3rd call: UnitRemoveItem --> UnitAddItem --> UnitDropItemSlot. So I added 0.00sec timer.
UnitDropItemSlot is importand to have items in right slots after we change bags I guess.
You are using the timer variable to somehow initialize the locals at the start of the function.
Yeah, I use timer id as parentKey in hashtable

here's full code for easier reference
JASS:
function GetUnitBagNumber takes unit u returns integer
    return LoadInteger(udg_Inventory, GetHandleId(u), 1000)
endfunction
function SetUnitBagNumber takes unit u, integer bagNumber returns nothing
    call SaveInteger(udg_Inventory, GetHandleId(u), 1000, bagNumber)
endfunction

function Timer_LoadBag takes nothing returns nothing
    local timer t=GetExpiredTimer()
    local unit u = LoadUnitHandle(udg_Inventory, GetHandleId(t), 1001)
    local integer newBag = LoadInteger(udg_Inventory, GetHandleId(t), 1002)
    local integer slot=0
    local integer childKey
    local item itm=null
    //load items from bag "newBag" after 0 seconds
    loop
        exitwhen slot==bj_MAX_INVENTORY
        set childKey=(newBag*10)+slot
        if HaveSavedHandle(udg_Inventory, GetHandleId(u), childKey) then
            set itm=LoadItemHandle(udg_Inventory, GetHandleId(u), childKey)
            call SetItemVisible(itm, true)
            call UnitAddItem(u, itm)
            call UnitDropItemSlot(u, itm, slot)
            call BJDebugMsg("loading from bag "+I2S(newBag)+", slot "+I2S(slot)+" item "+GetItemName(itm))
        endif
        set slot=slot+1
    endloop
    call SetUnitBagNumber(u, newBag)
    //---
    call FlushChildHashtable(udg_Inventory, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
    set u=null
    set itm=null
endfunction
//-------------------------------------------------------------------------------------------------------------------------
function SwitchBag takes unit u, integer currentBag, integer newBag returns nothing
    local integer h_id=GetHandleId(u)
    local integer childKey
    local integer slot=0
    local item itm=null
    local timer t=CreateTimer()
   
    loop//save current items to the "currentBag"
        exitwhen slot==bj_MAX_INVENTORY       
        set childKey=(currentBag*10)+slot
        set itm=UnitItemInSlot(u, slot)
        if itm != null then
            call SaveItemHandle(udg_Inventory, h_id, childKey, itm)
            call UnitRemoveItem(u, itm)
            call BJDebugMsg("saving bag "+I2S(currentBag)+", slot "+I2S(slot)+" item "+GetItemName(itm))
            call SetItemVisible(itm, false)
        else
            call RemoveSavedHandle(udg_Inventory, h_id, childKey)
        endif
        set slot=slot+1
    endloop
   
    set h_id=GetHandleId(t)
    call SaveUnitHandle(udg_Inventory, h_id, 1001, u)
    call SaveInteger(udg_Inventory, h_id, 1002, newBag)
    call TimerStart(t, 0.00, false, function Timer_LoadBag)
   
    set itm=null
    set t=null
endfunction

//===========================================================================
function InitTrig_switchTo takes nothing returns nothing
endfunction
 
Status
Not open for further replies.
Top