1. The Melee Mapping Contest #4: 2v2 - Results are out! Step by to congratulate the winners!
    Dismiss Notice
  2. We're hosting the 15th Mini-Mapping Contest with YouTuber Abelhawk! The contestants are to create a custom map that uses the hidden content within Warcraft 3 or is inspired by any of the many secrets within the game.
    Dismiss Notice
  3. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice

Hashtables and MUI

Discussion in 'Trigger (GUI) Editor Tutorials' started by wyrmlord, Jun 26, 2009.

  1. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    Before starting this tutorial, you should be comfortable with using GUI. This tutorial was not made to target those just starting out with GUI and may be confusing for those that are.

    What is a hashtable?


    For starters, it's a data structure capable of holding almost any kind of data in Wc3. It can hold integers, unit, special effects, and anything else you would ever need to store.

    How do we use it?


    First thing is first, we need to create a hashtable in order to use it. While we're at it, we'll use a variable to keep track of it.
    • Init
      • Events
        • Map initialization
      • Conditions
      • Actions
        • Hashtable - Create a hashtable
        • Set hashTable = (Last created hashtable)

    Now that we have a hashtable, we can use it in a way that's very similar to using an array. The difference? It requires 2 integers instead of 1 and can hold any type we desire. There's also no limit as to how big or small these integers can be. Here's an example:
    • Example
      • Events
      • Conditions
      • Actions
        • -------- Store the value --------
        • Hashtable - Save 5.00 as 0 of 0 in hashTable
        • -------- Load the value --------
        • Set LoadedValue = (Load 0 of 0 from hashTable)
        • -------- Will show the text: 5.00 --------
        • Game - Display to (All players) the text: (String(LoadedValue))

    As you can see, a hashtable is a structure that lets you store things in it. You give it a value to store and 2 integer "keys" that will later be used to retrieve the object you stored. In this example, we could just as easily have used an integer, string, unit, floating text, etc. in place of the real value and they could have all be stored in the hashtable.

    Why are Hashtables Useful?


    I know what you're thinking. "If hashtables are just giant arrays, why do we need them?" Hashtables by themselves are not terribly useful, but when we use them with Handle IDs they can actually be incredibly useful. They allow us to "attach" data to handles that can be retrieved later. We're going to cover this now.

    What is a Handle?


    For those needing it, here's a simple overview of what a handle is. There are many types in Warcraft 3: integers, reals, strings, units, players, items, special effects, etc. A handle is any type that is not either an integer, real, boolean, or string. An example of some commonly used handles are: units, destructables, items, players, special effects, floating text, and locations (referred to as "points" in GUI). There are many, many more handles than the list I gave, but these are just some of the more commonly used ones.

    Handle IDs and why they're useful


    This, although you might not realize it now, is incredibly useful when combined with Hashtables. With the new patch, you are now able to get the Handle ID (it's an integer) of things such as units. As far as you should be concerned, these IDs are unique to each handle. Now, as previously mentioned, hashtables require two integers when storing data inside of it. One of these integers could easily be the Handle ID of a unit that's part of a MUI spell.

    Since you might still not understand its usefulness, here's an example of a spell that heals 10 life per second for 10 seconds:

    While I'm at it, please use "Unit - A unit Starts the effect of an ability" instead of "Unit - A unit Begins casting an ability".
    • Start
      • Events
        • Unit - A unit Starts the effect of an ability
      • Conditions
        • (Ability being cast) Equal to Heal
      • Actions
        • -------- Store the duration in the Hashtable under the unit's Handle ID --------
        • Hashtable - Save 10.00 as 0 of (Key (Target unit of ability being cast)) in hashTable
        • -------- Add the unit to the group of units that are being healed over time --------
        • Unit Group - Add (Target unit of ability being cast) to HealOverTimeUnits


    Every second, this trigger goes through all the units being healed and heals them.
    • Healing
      • Events
        • Time - Every 1.00 seconds of game time
      • Conditions
      • Actions
        • Unit Group - Pick every unit in HealOverTimeUnits and do (Actions)
          • Loop - Actions
            • -------- Get the remaining time in seconds --------
            • Set RemainingTime = (Load 0 of (Key (Picked unit)) from hashTable)
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • RemainingTime Greater than 0.00
              • Then - Actions
                • -------- Heal the unit and update the time remaining --------
                • Unit - Set life of (Picked unit) to ((Life of (Picked unit)) + 10.00)
                • Hashtable - Save (RemainingTime - 1.00) as 0 of (Key (Picked unit)) in hashTable
              • Else - Actions
                • -------- The healing is done, clean up --------
                • Unit Group - Remove (Picked unit) from HealOverTimeUnits
                • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in hashTable


    I hope you're able to understand all of this. The only thing you might be wondering about is the "Hashtable - Clear all child hashtables of child (Key (Picked unit)) in hashTable". When you're done using the hashTable for a specific unit, you need to clean up just like you would clean up memory leaks. This function cleans up all data stored with the Handle ID of the unit that was being healed.

    This might be more work than using a bunch of waits inside a single trigger, but this way of doing things is completely MUI.

    Now, another popular example. We're going to make a single target knockback spell that uses Hashtables. It will work just like the HealOverTime trigger because it will also use a group to hold all the units being knocked back. The difference is that more values will be stored in the hashTable and of course the unit will be knocked back instead of healed.

    Here's another thing to keep in mind. Unless you handle your values carefully, spells that use the same hashtable can easily overwrite values in other spells. So, I recommend creating a hashtable global for each spell that needs to use it unless you know what you are doing and can prevent conflicts. Anyways, here are the knockback functions:

    • KnockbackTarget
      • Events
        • Unit - A unit Starts the effect of an ability
      • Conditions
        • (Ability being cast) Equal to Knockback
      • Actions
        • -------- Just for configuration/readability --------
        • Set KnockbackAngle = (Angle from (Position of (Triggering unit)) to (Position of (Target unit of ability being cast)))
        • -------- Distance unit is knocked back per second --------
        • -------- .04 is how often the units are moved, so we multiply the distance by .04 --------
        • Set KnockbackDistance = (400.00 x 0.04)
        • -------- How long the unit is knocked back for --------
        • Set RemainingTime = 2.00
        • -------- Store the values --------
        • Hashtable - Save KnockbackDistance as 0 of (Key (Target unit of ability being cast)) in knockbackTable
        • Hashtable - Save KnockbackAngle as 1 of (Key (Target unit of ability being cast)) in knockbackTable
        • Hashtable - Save RemainingTime as 2 of (Key (Target unit of ability being cast)) in knockbackTable
        • Unit Group - Add (Target unit of ability being cast) to KnockbackUnits


    This trigger moves all the units currently being knocked back.
    • KnockbackUnits
      • Events
        • Time - Every 0.04 seconds of game time
      • Conditions
      • Actions
        • Unit Group - Pick every unit in KnockbackUnits and do (Actions)
          • Loop - Actions
            • -------- Load all necessary variables stored in the Hashtable --------
            • Set KnockbackDistance = (Load 0 of (Key (Picked unit)) from knockbackTable)
            • Set KnockbackAngle = (Load 1 of (Key (Picked unit)) from knockbackTable)
            • Set RemainingTime = (Load 2 of (Key (Picked unit)) from knockbackTable)
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • RemainingTime Greater than 0.00
              • Then - Actions
                • -------- Move the unit and update time remaining --------
                • Unit - Move (Picked unit) instantly to ((Position of (Picked unit)) offset by KnockbackDistance towards KnockbackAngle degrees)
                • Hashtable - Save (RemainingTime - 0.04) as 2 of (Key (Picked unit)) in knockbackTable
              • Else - Actions
                • -------- Knockback is done, clean up --------
                • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in knockbackTable
                • Unit Group - Remove (Picked unit) from KnockbackUnits


    As you noticed, the usage of a unit group is very similar to the Heal example. What you should also notice is that a different key is used for each value be stored (0 for distance, 1 for angle, 2 for time remaining). It's important that each of these keys are different because otherwise the data would be overwritten.

    While remembering each key in an example like this isn't tricky, sometimes with larger systems they can become mixed up. There's a handy function that will get a String ID of a string just like you can get the Handle ID of a unit. So instead of using 0 for distance, we could have gotten the String ID of "distance". Here's what the first trigger might look like if we used String IDs instead:

    • KnockbackTarget
      • Events
        • Unit - A unit Starts the effect of an ability
      • Conditions
        • (Ability being cast) Equal to Knockback
      • Actions
        • -------- Just for configuration/readability --------
        • Set KnockbackAngle = (Angle from (Position of (Triggering unit)) to (Position of (Target unit of ability being cast)))
        • -------- Distance unit is knocked back per second --------
        • -------- .04 is how often the units are moved, so we multiply the distance by .04 --------
        • Set KnockbackDistance = (400.00 x 0.04)
        • -------- How long the unit is knocked back for --------
        • Set RemainingTime = 2.00
        • -------- Store the values --------
        • Hashtable - Save KnockbackDistance as (Key distance) of (Key (Target unit of ability being cast)) in knockbackTable
        • Hashtable - Save KnockbackAngle as (Key angle) of (Key (Target unit of ability being cast)) in knockbackTable
        • Hashtable - Save RemainingTime as (Key time) of (Key (Target unit of ability being cast)) in knockbackTable
        • Unit Group - Add (Target unit of ability being cast) to KnockbackUnits


    The KnockbackUnits trigger would need to be updated to use the String IDs as well, but that should be straightforward enough that I shouldn't have to show it here. The example in the demo map uses String IDs, so you can look there for more information.

    As a final note, these triggers are merely examples. As you may or may have not noticed, the knockback trigger would require memory leak fixing since it leaks 25 locations each second for every unit that is being knocked backward. I intentionally left the triggers how they are so they would be easier to understand. Those who wish to use these kinds of triggers in their maps should make sure to fix any memory leaks that were not cleaned up in the examples.
     

    Attached Files:

    Last edited: Aug 31, 2009
  2. PurplePoot

    PurplePoot

    Joined:
    Dec 14, 2005
    Messages:
    11,162
    Resources:
    3
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    Resources:
    3
    Seems fairly straightforward, although I'd like a few GUI users to have a look at this and see if they get it.
     
  3. dansaDisco

    dansaDisco

    Joined:
    Jun 25, 2008
    Messages:
    963
    Resources:
    15
    Models:
    5
    Icons:
    8
    Skins:
    2
    Resources:
    15
    I understand it perfectly, thank you very much for this tutorial.
    I reckon hashtables and handles will be extremely useful when the new patch is finally stable :)

    And, in contrast to many other tutorials, I only had to read this once to get how it works :D

    Awesome job wyrmlord!
     
  4. Pyritie

    Pyritie

    Joined:
    Nov 26, 2006
    Messages:
    11,357
    Resources:
    60
    Models:
    30
    Icons:
    9
    Packs:
    3
    Skins:
    12
    Tools:
    1
    Maps:
    1
    Tutorials:
    4
    Resources:
    60
    This ready to be approved?
     
  5. dansaDisco

    dansaDisco

    Joined:
    Jun 25, 2008
    Messages:
    963
    Resources:
    15
    Models:
    5
    Icons:
    8
    Skins:
    2
    Resources:
    15
    I would say yes. I don't see anything wrong with it and it explains everything in a easy manor.
     
  6. Boreal

    Boreal

    Joined:
    Jun 30, 2009
    Messages:
    1
    Resources:
    0
    Resources:
    0
    While I would have preferred more information on how hash tables work (which would probably ruin the tutorial for everyone else), I still think this is a excellent tutorial.
     
  7. Kwah

    Kwah

    Joined:
    May 9, 2007
    Messages:
    3,391
    Resources:
    8
    Icons:
    5
    Maps:
    1
    Tutorials:
    2
    Resources:
    8
    Py it's wyrm. Just approve it.
     
  8. redmarine

    redmarine

    Joined:
    Sep 4, 2007
    Messages:
    2,781
    Resources:
    1
    Maps:
    1
    Resources:
    1
    I somewhat understand the concept. It's kinda unclear but once I've actually try it out I believe I should fully understand it.
     
    Last edited: Jul 5, 2009
  9. Reaper2008

    Reaper2008

    Joined:
    Jul 27, 2008
    Messages:
    1,117
    Resources:
    1
    Spells:
    1
    Resources:
    1
    Nice tut!
     
  10. Rmx

    Rmx

    Joined:
    Aug 27, 2007
    Messages:
    1,088
    Resources:
    18
    Icons:
    3
    Spells:
    15
    Resources:
    18
    Easy to understand but in big triggers and complicated ones...

    Hashtable will be extremely complicated, but some easy triggering and jumps slides ext..

    Hashtable is F T W ( for the win ) ^^
     
  11. Lightstalker

    Lightstalker

    Joined:
    Jul 8, 2008
    Messages:
    150
    Resources:
    1
    Maps:
    1
    Resources:
    1
    I think it's a great tutorial but lacking a bit in information...

    For example, if you save a value of 1, then save another with the same value, the previous will be overwritten.

    Pretend a Paladin with "Heal Over Time" is fighting a Death Knight with "Damage Over Time" (the exact opposite of your spell). Now, the Paladin will cast "Heal Over Time" on himself and the duration will be saved of 0. Now, the Death Knight casts "Damage over Time" on the Paladin and the duration will be saved of "0". Won't the Paladin's spell stop healing him?

    Also, do points and other things that leak need to be assigned to a variable and then destroyed, or is clearing the child hashtable enough?
     
  12. Anachron

    Anachron

    Joined:
    Sep 9, 2007
    Messages:
    6,167
    Resources:
    66
    Icons:
    49
    Packs:
    2
    Tools:
    1
    Maps:
    3
    Spells:
    9
    Tutorials:
    1
    JASS:
    1
    Resources:
    66
    Good job, quite like this, I guess it was nice explained and well written.
     
  13. Linaze

    Linaze

    Joined:
    Feb 28, 2007
    Messages:
    2,982
    Resources:
    2
    Models:
    1
    Tutorials:
    1
    Resources:
    2
    I must admit, I actually learned something new. As soon as the patch comes out, I'm gonna try this for sure.
    However, you need to add a title ([h1][/h1]) and your headings must use the [h2], [h3] etc. tags.
     
  14. Element of Water

    Element of Water

    Joined:
    Aug 3, 2008
    Messages:
    2,298
    Resources:
    5
    Spells:
    3
    Tutorials:
    1
    JASS:
    1
    Resources:
    5
    If you use the same hashtable for both spells then yes, it will bug, but if, as you should, you use different global hashtables for each spell, then there will be no problem.
     
  15. Pyritie

    Pyritie

    Joined:
    Nov 26, 2006
    Messages:
    11,357
    Resources:
    60
    Models:
    30
    Icons:
    9
    Packs:
    3
    Skins:
    12
    Tools:
    1
    Maps:
    1
    Tutorials:
    4
    Resources:
    60
    Changed and approved
     
  16. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    I will make sure this point is emphasized in the tutorial, thanks for pointing it out.

    I would still assign the values to variables and destroy them manually just to be sure.
     
  17. Lightstalker

    Lightstalker

    Joined:
    Jul 8, 2008
    Messages:
    150
    Resources:
    1
    Maps:
    1
    Resources:
    1
    And then what after I've created a global hashtables? If I use a spell that has a periodic event, how will I find that hashtable again, since there is no "Save hashtable" function? Do I have to create a hashtable array variable, and at the beginning of my action have an integer variable and add + 1 to it, and set it as the last created hashttable?

    Example:

    Create a Hashtable
    If
    MUICount Less than 1000
    Then
    Set MUICount = MUICount + 1
    Else
    Set MUICount = 0
    Set HashT[MUICount] = Last Created Hashtable

    Is that what I should do?
     
  18. wyrmlord

    wyrmlord

    Joined:
    Oct 13, 2005
    Messages:
    252
    Resources:
    5
    Tools:
    1
    Maps:
    1
    Tutorials:
    3
    Resources:
    5
    I'm extremely confused as to what you're trying to do. You should only need one hashtable per spell. The primary use of a hashtable is to "attach" data to handles. In my examples, I attached the information to units to keep track of how much more healing they needed and knockback information.

    When using hashtables, you should not need to use global array variables as well, only the hashtable. If you can explain what you're trying to accomplish, I can try to point you in the right direction.
     
  19. redmarine

    redmarine

    Joined:
    Sep 4, 2007
    Messages:
    2,781
    Resources:
    1
    Maps:
    1
    Resources:
    1
    Are hashtables also useful for storing multiple data for individual units in a group? By that I mean can they also contain references for those units?
     
  20. SlayerII

    SlayerII

    Joined:
    Aug 21, 2008
    Messages:
    526
    Resources:
    2
    Maps:
    1
    Tutorials:
    1
    Resources:
    2
    hmm
    could this mean i can use hashtables like 2d arrays? the one from vjass are bugged-.-