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

[Solved] Hashtables Help Please?

Status
Not open for further replies.
Level 4
Joined
Apr 16, 2018
Messages
47
Really hate to ask, I have been trying and trying for 5+ hours to figure it out and not have to ask, but I am nearing a head ache. Have not felt so scrambled before, literally my head has been struggling to put the logic on straight and my mind has literally become unfocused Its just idk how to describe. Never been this mentally taxed I dont think.

Partially think it is because it is first time using them, and I have run into mixed responses or not quite applicable to what I need uses. I have though oh yea I get it so many times, but failed idk... idk omg Think I am going to lay down after this post. Finally motivated to get things done and getting constant roadblocks...

Essentially I need to revive a hero that has just died after a period of time designated by a timer/timer window. be able to see which timer expired as I am using an array of timers in case other players heroes dies, and revive them in the correct teams respawn by maybe checking if they are allied to the computer force of their team. I just cant seem to figure out a method that works, and cant figure out how hashtables that were recommended work.

Expire Timer
  • Respawn Hero
    • Events
      • Time - HeroRespawn_Timer[1] expires
      • Time - HeroRespawn_Timer[2] expires
      • Time - HeroRespawn_Timer[3] expires
      • Time - HeroRespawn_Timer[4] expires
      • Time - HeroRespawn_Timer[5] expires
      • Time - HeroRespawn_Timer[6] expires
      • Time - HeroRespawn_Timer[7] expires
      • Time - HeroRespawn_Timer[8] expires
      • Time - HeroRespawn_Timer[9] expires
      • Time - HeroRespawn_Timer[10] expires
    • Conditions
    • Actions
I assume I need to use these to detect a timer expires, but no idea how to tell which timer has expired. I assigned them by player number of units owner that initially died. If I cant find out which is for which hero and which specifically is expiring. xD idk. how how to implement this data properly to be used in the other trigger or vise versa I hope I am explaining proper. Literally struggling to keep my line of thought now wtf... Never been this bad.

Death Timer
  • Start Respawn Timer
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is A Hero) Equal to True
    • Actions
      • Set VariableSet HeroRespawn_Hero[(Player number of (Owner of (Triggering unit)))] = (Triggering unit)
      • Countdown Timer - Start HeroRespawn_Timer[(Player number of (Owner of (Triggering unit)))] as a One-shot timer that will expire in HeroRespawn_Time seconds
      • Countdown Timer - Create a timer window for (Last started timer) with title Respawn Time
      • Countdown Timer - Show HeroRespawn_TimerWindow[(Player number of (Owner of (Triggering unit)))] for (Owner of (Triggering unit))
      • Hashtable - Save Handle Of(Last started timer) as 0 of (Player number of (Owner of (Triggering unit))) in HeroRespawn_Hash.
The action part may need some changes, I just left it in disarray after failing and trying to reset for another attempt.

Could I get help finding ways to do this between these two trigger. I would really appreciate.
Yea.. laying down, I can't even think straight anymore...
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
Before doing anything else, first check to make sure that your Timer array has the correct Array Size. Timers, Unit Groups, and Player Groups are all special in that they require their Array Sizes to be initialized instead of using the Default Size of 1. This means that the Size needs to be as large as the highest [index] possible, which in your case would be 10 (Player 10).
  • Hero Dies
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is A Hero) Equal to True
    • Actions
      • Set VariableSet PN_Death = (Player number of (Owner of (Triggering unit)))
      • -------- --------
      • -------- Start timer and save the Player's Number to it --------
      • Countdown Timer - Start Respawn_Timer[PN_Death] as a One-shot timer that will expire in 30.00 seconds
      • Hashtable - Save PN_Death as 0 of (Key (Last started timer).) in Respawn_Hash.
      • -------- --------
      • -------- Create the timer window and track it using the player's number --------
      • Countdown Timer - Create a timer window for Respawn_Timer[PN_Death] with title Respawn
      • Set VariableSet Respawn_Window[PN_Death] = (Last created timer window)
      • Countdown Timer - Hide Respawn_Window[PN_Death]
      • Countdown Timer - Show Respawn_Window[PN_Death] for (Owner of (Triggering unit))
      • -------- --------
      • -------- Track the hero using the hashtable (although you should probably already have the heroes tracked in a unit array all throughout the game) --------
      • Hashtable - Save Handle Of(Triggering unit) as 1 of (Key (Expiring timer).) in Respawn_Hash.
  • Hero Revives
    • Events
      • Time - Respawn_Timer[1] expires
      • Time - Respawn_Timer[2] expires
      • Time - Respawn_Timer[3] expires
    • Conditions
    • Actions
      • -------- Remember to set the Respawn_Timer Array Size to be large enough in the Variable Editor! --------
      • Set VariableSet PN_Death = (Load 0 of (Key (Expiring timer).) from Respawn_Hash.)
      • -------- --------
      • -------- Instead of saving the dying Hero in the Hashtable you could use PN_Death to get your Hero from a Unit array. --------
      • Hero - Instantly revive (Load 1 of (Key (Expiring timer).) in Respawn_Hash.) at (Center of (Playable map area)), Show revival graphics
      • -------- --------
      • -------- Clean up time --------
      • Countdown Timer - Destroy Respawn_Window[PN_Death]
      • Hashtable - Clear all child hashtables of child (Key (Expiring timer).) in Respawn_Hash.

As the comments in my triggers suggest you don't have to save the dying Hero to the Hashtable like I did, you could instead use your method of storing the Hero using a Unit array and the Player's Number as the Index. That being said, if you do your method I recommend setting this array prior to the death trigger. In other words, if each player controls a single hero then you should be tracking them throughout the entire game in your Unit array from the moment the hero comes into play. But maybe you have a good reason for setting the variable in response to the death event, I don't know how your map works.

Additionally, if you want the Respawns to differ depending on the player then you can use a Point array. In this example each Point is set to the center of some region:
  • Events
  • Map Initialization
  • Actions
  • Set Variable RespawnPoint[1] = (Center of P1Respawn)
  • Set Variable RespawnPoint[2] = (Center of P2Respawn)
  • Set Variable RespawnPoint[10] = (Center of ComputerRespawn)
  • etc...
Now in your Revive Trigger you simply reference RespawnPoint[PN_Death] to get that player's respawn point.
  • Hero - Instantly revive PlayerHero[PN_Death] at RespawnPoint[PN_Death], Show revival graphics

In a map where players share many of the same things these arrays and player numbers can go a very long way. Always be thinking, "How can I make this trigger easier on myself? How can I remove the repetition and redundancy from my triggers/variables?". The answer is almost always arrays. And then when those aren't good enough then you can rely on Hashtables or maybe use them together. Loops are your friend as well, since they can take advantage of the [Index] of an Array and work in conjunction with all of these things to make your triggers cleaner, more efficient, and highly optimized.
 

Attachments

  • Hero Respawn.w3m
    18 KB · Views: 8
Last edited:
Level 4
Joined
Apr 16, 2018
Messages
47
Thank you very much. Seeming much clearer.

Few problems and questions though.

-What exactly is the purpose of each of those values in hashtable, I cannot understand the clear all child hashtables of child for example. Is the first thing the value, but what are the two variables after before setting the hastable. Confusing how they all work together. xD

-My trigger is not destroying the timer window when it revives. Just stays at the top, starts counting again when I kill hero again; as well as making a new window that also counts down.

-Timer also shows up when computer hero is killed? Should be only showing to the computer player...

-Also seems to crash when I try to kill a computer players hero, which I was attempting for testing?

Super sorry for asking so much, but this is probably one of the most confusing parts learning this that I have come across for me some reason. xD

  • Start Respawn Timer
    • Events
      • Unit - A unit Dies
    • Conditions
      • ((Triggering unit) is A Hero) Equal to True
    • Actions
      • Set VariableSet HeroRespawn_PlayerNumber = (Player number of (Owner of (Triggering unit)))
      • -------- --------
      • -------- Start timer and save the Player's number to it --------
      • Countdown Timer - Start HeroRespawn_Timer[HeroRespawn_PlayerNumber] as a One-shot timer that will expire in HeroRespawn_RespawnTime seconds
      • Hashtable - Save HeroRespawn_PlayerNumber as 0 of (Key (Last started timer).) in HeroRespawn_Hash.
      • -------- --------
      • -------- Create the timer window and track it using the player's number --------
      • Countdown Timer - Create a timer window for HeroRespawn_Timer[HeroRespawn_PlayerNumber] with title Respawn Time
      • Set VariableSet HeroRespawn_TimerWindow[HeroRespawn_PlayerNumber] = (Last created timer window)
      • Countdown Timer - Show HeroRespawn_TimerWindow[HeroRespawn_PlayerNumber] for (Owner of (Triggering unit))
      • -------- --------
      • -------- Track the hero using the hastable(Although better to set it into a unit ray when unit is made to track throughout entire match.) --------
      • Hashtable - Save Handle Of(Triggering unit) as 1 of (Key (Expiring timer).) in HeroRespawn_Hash.


  • Respawn Hero
    • Events
      • Time - HeroRespawn_Timer[1] expires
      • Time - HeroRespawn_Timer[2] expires
      • Time - HeroRespawn_Timer[3] expires
      • Time - HeroRespawn_Timer[4] expires
      • Time - HeroRespawn_Timer[5] expires
      • Time - HeroRespawn_Timer[6] expires
      • Time - HeroRespawn_Timer[7] expires
      • Time - HeroRespawn_Timer[8] expires
      • Time - HeroRespawn_Timer[9] expires
      • Time - HeroRespawn_Timer[10] expires
    • Conditions
    • Actions
      • -------- Remember to set the Respawn_Timer array size to be large enough in variable editor. --------
      • Set VariableSet HeroRespawn_PlayerNumber = (Key (Load 0 of (Key (Expiring timer).) in HeroRespawn_Hash.).)
      • -------- --------
      • -------- Instead of this hash, could use a unit array instead and use the retrieved player number I got earlier in trigger. --------
      • Hero - Instantly revive (Load 1 of HeroRespawn_PlayerNumber in HeroRespawn_Hash.) at (Center of West Spawn <gen>), Show revival graphics
      • -------- --------
      • -------- Clean up time --------
      • Countdown Timer - Destroy HeroRespawn_TimerWindow[HeroRespawn_PlayerNumber]
      • Hashtable - Clear all child hashtables of child HeroRespawn_PlayerNumber in HeroRespawn_Hash.
 
Last edited:
Level 4
Joined
Apr 16, 2018
Messages
47
Yea, Think I finally worked it out in my head somewhat, but now I have one other problem. Why in the world is my timer window not destroying? Just remains up there... The logic looks perfectly fine, I did the thing he suggested about making the array exactly as much as is need and not just 1....
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
If you compare our triggers you can see some differences.
For example:
  • Set VariableSet HeroRespawn_PlayerNumber = (Key (Load 0 of (Key (Expiring timer).) in HeroRespawn_Hash.).)
You're using Key here twice which is wrong. Look at how I did it in my trigger.

Also, this one was a mistake on my part but Timer Windows are visible to everyone by default. So when you "Show timer window for player" all you're doing is making an already visible timer, visible again. You need to Hide it first before showing it:
  • Countdown Timer - Hide HeroRespawn_TimerWindow[HeroRespawn_PlayerNumber]
  • Countdown Timer - Show HeroRespawn_TimerWindow[HeroRespawn_PlayerNumber] for (Owner of (Triggering unit))

Clearing the Hashtable is for avoiding memory leaks. What it does is remove any and all data stored at the given Parent Key. In this case our Parent Key is the (Expiring timer). The wording is weird here in GUI and it makes more sense in code form.

Also, Timer Windows dont need their Array Sizes initialized so keep them at the Default of 1.


To understand Hashtables I find it really useful to grasp the concept of Parents and Children. Think of it like a family tree where the Hashtable is the Grandparent, the Parents belong to the Grandparent, and the Children belong to each individual Parent.

So you're working with a single Grandparent that can own many Parents. These individual Parents can own many Children. Here's a diagram:

HeroRespawn_Hash (Grandparent):
-> Parent 1
-----> Child 1
-----> Child 2
-----> Child 3

-> Parent 2
-----> Child 1
-----> Child 2
-----> Child 3

-> Parent 3
-----> Child 1
-----> Child 2
-----> Child 3

So these things are all linked together in the form of relationships. By clearing the Parent you get rid of all of it's Children. By Clearing the Grandparent (Hashtable) you're clearing everything because you'd clear the Parents which then clears their Children.

Also, the concept of Keys (Handle ids) is very important to understand as well. You don't NEED to use Keys (Handle ids) in every case. This is just to get the Integer id of a game object. So if you wanted the integer id of a Timer for example you'd need to get it's Key (Handle id). Understand that a Key is a Handle id, they're the same thing.

For cases where you aren't using a game object (unit, player, item, timer, etc.) you can simply save/load data without using Keys (Handle ids):
  • Hashtable - Save 100 as 1 of 5 in YourHashtable
  • Hashtable - Save 200 as 2 of 5 in YourHashtable
  • Hashtable - Save 300 as 3 of 5 in YourHashtable
Using my example diagram above, this saved data looks like this:

YourHashtable (Grandparent):
-> Parent 1 = 5
-----> Child 1 = 100
-----> Child 2 = 200
-----> Child 3 = 300

So if you wanted to access the values 100, 200, and 300 you would need to Load them from their Parent which is saved as 5. So you'd load it like this:
  • Set Variable Integer = Load 1 (CHILD) of 5 (PARENT) in YourHashtable (GPARENT) <-- This gets us the value 100
  • Set Variable Integer = Load 2 (CHILD) of 5 (PARENT) in YourHashtable (GPARENT) <-- This gets us the value 200
  • Set Variable Integer = Load 3 (CHILD) of 5 (PARENT) in YourHashtable (GPARENT) <-- This gets us the value 300

Note that you can accidentally swap the Parent/Children data and reverse the relationship so that Children own Parents instead of Parents owning Children. It's important that you don't do this and maintain the GParent>Parent>Child hierarchy, this way when it comes time to remove memory leaks you can clear all of the desired data in one single Action by clearing a Parent. Remember, you can only clear two things, the entire Hashtable (Grandparent) or a single specific Parent (which clears the Children that belong to it). At least this is the case in GUI.

Another way to look at a Hashtable is to think of it as a 2-Dimensional Array. So you're given an extra [Index] in your Array:
  • Set Variable YourHashtable[Parent][Child] = Some value
Relating this to my previous example:
  • Set Variable YourHashtable[5][1] = 100
  • Set Variable YourHashtable[5][2] = 200
  • Set Variable YourHashtable[5][3] = 300
 
Last edited:
Level 4
Joined
Apr 16, 2018
Messages
47
It finally works! Hehehe. Thank you so much for such a detailed reply. You also lay things out in a very digestible format.

I 'believe' I understand it now; I hope. My goodness those are mind melding at first.

Did not realize I did the key twice... Completely obvious now that I am starting to get an idea of things... Also glad you chimed in with your own fixes to things like the timer window hiding, because I was about to ask about that as I was having them show up still as well lol...

Only 2 questions I have left
1. Is it ok or even possible to assign different variable types into different children of the same parent?

2. Is it good or bad to use hashtables often, is it a problem to have many of them or what ever else with their usage. Don't want to cause problems if I end up liking and using them a bunch only to have some sort of issue ruin it all because of it. xD

Nothing else other than that. again. THANK YOU! I learn a bunch.

Love the Uncle reference btw. =P
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
1: Yes, you can save something different at each child.

2: I believe the Hashtable limit is 255. A single Hashtable could technically save data for everything in your map, but this would require some serious organization on your part. I would avoid using too many Hashtables if able but I'm sure having around 20 or so of them wouldn't cause any noticeable harm.

What I would do to get the most out of the Hashtables is break them up into different categories, IE:
SpellHashtable
UnitHashtable
ItemHashtable

Then try to fill these Hashtables with as much of the relevant data as possible. All of your spells could use the SpellHashtable. Any data that you want to save to units that isn't spell related could go into the UnitHashtable. Item stuff in the ItemHashtable, etc.

After all, it's rather inefficient to dedicate a single Hashtable which can have millions of Parents/Children to saving just ~10 Parents with ~2 Children each. Organization and reusability can go a long way and you'll thank yourself in the future when you only have 10 Hashtables and not 100 in your map.

With that in mind, I personally would rename HeroRespawn_Hash to UnitHashtable and consider the reviving of heroes a Unit related thing. It's a simple name change but this can help keep your map organized and logical. When reviving a Hero, which is a unit, you obviously know that the relevant data is in the UnitHashtable and not the Spell/Item Hashtable and you can then use this UnitHashtable for other saved data that isn't respawn related.


Another thing, it's important to understand the concept of Keys (Handle ids) when used in Hashtables. The Key (Handle id) of game objects is a rather long integer, imagine something like 123456789. Because they're integers there is potential to accidentally overwrite this data in cases where you manually type in an Integer value for your Parent instead of relying on a Key. Oh and when I say game object I mean things like units, items, players, etc.

Here's an example. Let's say we wanted to save and retain two different values in our Hashtable:
  • Hashtable - Save 100 as 0 of Key(Some Unit) in YourHashtable
  • Hashtable - Save 200 as 0 of 123456789 in YourHashtable
If the Key (Handle id) of Some Unit is equal to 123456789 then we just ran into a problem. We'd be saving a value to the same Child (0) of the same Parent (123456789) so the 100 would get replaced with 200.

That being said, this issue is easily avoided by never giving Parents a large integer value like that. You can even use negative Integers as your Parents so there's A LOT of room to work with in a single Hashtable. Also, no two game objects will EVER share the same Key (Handle id), so if ALL of your Parents are Keys then you'll never have to worry about this issue. I only bring this up to address cases where you're manually typing in an Integer value for your Parent (or referencing an Integer variable as your Parent).

Of course there's still the possibility that you accidentally overwrite data the normal way. IE:
  • Hashtable - Save 100 as 0 of Key(Some Unit) in YourHashtable
  • Hashtable - Save 200 as 0 of Key(Some Unit) in YourHashtable
Since these Actions are using the same Child (0) AND Parent (Some Unit) you'll end up replacing 100 with 200. Obviously there will be times when you want to do this, which is of course fine then.


A neat technique that can be useful in certain situations is to create your own "Keys" in the form of Integers. Each "Key" MUST have a unique value as to set it apart from the others. You can then use these Integers as the Parents in your Hashtable:
  • Set Variable Key_Fireball = 0
  • Set Variable Key_Blizzard = 1
  • Set Variable Key_Nova = 2
  • Hashtable - Save 100 as 0 of Key_Fireball in SpellHashtable
  • Hashtable - Save 100 as 0 of Key_Blizzard in SpellHashtable
  • Hashtable - Save 100 as 0 of Key_Nova in SpellHashtable
So they aren't actually Keys, I just named them that to maintain the naming conventions, but they serve the same exact purpose. They're really just there so that you don't have to rely on your memory, you can simply reference Key_Nova instead of having to remember that your Nova spell uses the Parent 3 in the SpellHashtable.
 
Last edited:
Status
Not open for further replies.
Top