• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] Simple Question

Status
Not open for further replies.
Level 9
Joined
Jun 7, 2008
Messages
440
Hey fellow hivers! I have a question:

I am looking to save multiple dummy units in a hash table. My question is: Can anyone show me -start to finish - on how to save units in a hash table, as well as call them forth at a later time?
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Okay, I'll assume you're using JASS since the thread has
JASS:
[code=jass]
tags.

It's actually pretty simple. Let's say you have your global hashtable, named table.

JASS:
globals
    hashtable table = InitHashtable()
endglobals

Now that you've got that setup, you would use the following code to save a unit-handle to the hashtable:

JASS:
...
    call SaveUnitHandle(table, 1, 1, yourUnitVar)
...

The (1, 1) values indicate the position that yourUnitVar will be saved in the hashtable (so that it can be referenced later).

This is how you would reference it:

JASS:
...
    set unitVar = LoadUnitHandle(table, 1, 1)
...

Notice the same (1, 1) position is referenced. This is important. Keep in mind that the GetHandleId native is available to gather a unique integer ID from any handle, useful for storing things to handles.

The last thing you should note are the following functions:

JASS:
native RemoveSavedHandle takes hashtable table, integer parentKey, integer childKey returns nothing

This will remove a saved handle from storage. I recommend doing this for individual table elements that you no longer require. There are also the following two functions:

JASS:
native FlushParentHashtable takes hashtable table returns nothing

This flushes all values that are stored in a hashtable. Don't use this unless it's absolutely necessary.

JASS:
native FlushChildHashtable takes hashtable table, integer parentKey returns nothing

This is used to flush one "row" of the hashtable, whichever row is designated by parentKey. Notice that a hashtable is setup like a 2-dimensional array. This native would allow you to flush all values in a row (as I said), I'll use a 2-dimensional array example to explain.

Child hashtable flushed (index 3).

HashArray[3][0] - flushed
HashArray[3][1] - flushed
HashArray[3][2] - flushed
HashArray[3][3] - flushed
HashArray[3][4] - flushed
...
And etc.

Hope this helps.
 
Level 9
Joined
Jun 7, 2008
Messages
440
So would this be correct then??
JASS:
globals
    hashtable table = InitHashtable()
endglobal

function CreateDummyRandomArea takes integer dummyId, real x, real y, real range returns unit
    local unit cast = GetTriggerUnit()
    local real randx = x+GetRandomReal(x,range)*Cos(GetRandomReal(bj_PI,2*bj_PI)) 
    local real randy = y+GetRandomReal(y,range)*Sin(GetRandomReal(bj_PI,2*bj_PI))
    return CreateUnit(GetOwningPlayer(cast), 'hoo8', randx, randy, 0.)
// I have to save the unit into the hashtable here I assume. 
// Or would it be easier to save the location and create the unit 
// at the saved point?
endfunction

function Pacifier_Callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    call CreateDummyRandomArea()
    set MyVar = LoadUnitHandle (table, 1, 1)
endfunction

As well, If I wanted to flush all variables in say 1,1 / 1,2 / 1,3 How would I go about it? Child Flush?
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well if you want to flush a specific address then you use RemoveSavedHandle. Look at it this way:

Say you have data in (3, 2) and in (3, 4), and you want to remove both of those stores entries. You would use the FlushChildHashtable native with the hashtable parameter and the value "3", so it will clean all values associated with the first index of "3" (both (3, 2) and (3, 4) included).

If you wanted to remove a specific data entry, such as (3, 2) then you would use RemoveSavedHandle, or the equivalent native for whatever type you're referencing. If you want to clear all entries in a hashtable, use FlushParentHashtable.

This is a completed version of your example:

JASS:
globals
    hashtable table = InitHashtable()
    unit leak__unitControl = null
endglobals

function CreateDummyRandomArea takes integer dummyId, real x, real y, real range returns unit
    local unit cast = GetTriggerUnit()
    local real randx = x+GetRandomReal(x,range)*Cos(GetRandomReal(bj_PI,2*bj_PI)) 
    local real randy = y+GetRandomReal(y,range)*Sin(GetRandomReal(bj_PI,2*bj_PI))
    //return CreateUnit(GetOwningPlayer(cast), 'hoo8', randx, randy, 0.)
    //In order to store the unit in the hashtable we're not going to return the function just yet. Also,
    //returning the function here will leak the unit-reference, "cast".
    set leak__unitControl = CreateUnit(GetOwningPlayer(cast), 'h008', randx, randy, 0)
    set cast = null
    
    call SaveUnitHandle(table, 1, 1, leak__unitControl)
    return leak__unitControl
// I have to save the unit into the hashtable here I assume.
// Or would it be easier to save the location and create the unit
// at the saved point?
endfunction

function Pacifier_Callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    call CreateDummyRandomArea()
    set MyVar = LoadUnitHandle (table, 1, 1)
endfunction
 
Last edited:
Just some info for hashtables.

For hashtables, there are a couple of components.

JASS:
native  Save$TYPE$Handle					takes hashtable table, integer parentKey, integer childKey, $TYPE$ whichHandle returns boolean
<hashtable> - This is the hashtable you will be assigning the data to. Think of it as a file cabinet.
<parentKey> - This is the "category" that you will assign the data to. Essentially, think of a drawer for a file cabinet. You want to open the right drawer to get the right files.
<childKey> - This is the "sub-category" to assign the data to. Think of this as labels to the folders inside the drawers. When you want to find the right files, you will use the labels.
<whichHandle> - This is the actual data. Once you've looked through the right file cabinet, the right drawer, the right label, you will be able to find your data. Note that there is one label for each data, else it will be overwritten. Essentially, this is like replacing the file.

Now, you understand the mechanism that is a hashtable. It allows you to store information relatively nicely.

Okay, now let's take a simple example:
JASS:
//This spell will attack the target unit periodically
//  and create an effect each attack for 5 seconds. 

globals
    hashtable SpellHash = InitHashtable()
endglobals

function DamagePeriodic takes nothing returns nothing
    //Now we retrieve the data. How?
endfunction

function SpellActions takes nothing returns nothing
    local unit caster    = GetTriggerUnit() //get the casting unit
    local unit target    = GetSpellTargetUnit() //get the target
    local timer periodic = CreateTimer() //create a timer for the periodic
    
    //Now we must store the data. How?
    
    call TimerStart(periodic,1,true,function DamagePeriodic) //start the periodic.
    //"DamagePeriodic" must be above this function, else it will bring an error. It will only
    //search upward from this point to look for a function, since that is the order it is compiled. 
    set caster   = null
    set target   = null
    set periodic = null
endfunction

Okay, so we have a caster to damage the target every second for five seconds, and create an effect on the attack. First, layout what you want to store:
  • The caster, to damage the target.
  • The target, to get damaged by the caster. Also to create the effect at his position.

Okay, now you might be thinking "OK, I have hashtables, now I can do something like this:"
JASS:
globals
    hashtable SpellHash = InitHashtable()
endglobals

function DamagePeriodic takes nothing returns nothing
    local unit caster = LoadUnitHandle(SpellHash,1,0) //Load the caster.
    local unit target = LoadUnitHandle(SpellHash,1,1) //Load the target.
endfunction

function SpellActions takes nothing returns nothing
    local unit caster    = GetTriggerUnit()
    local unit target    = GetSpellTargetUnit() 
    local timer periodic = CreateTimer() 
    
    call SaveUnitHandle(SpellHash,1,0,caster) //Save the caster under "1" (category), and "0" (label)
    call SaveUnitHandle(SpellHash,1,1,target) //Save the target under "1" (category), and "1" (label)
    
    call TimerStart(periodic,1,true,function DamagePeriodic)
    set caster   = null
    set target   = null
    set periodic = null
endfunction

Well, what if the spell is cast again? As I said above, once the spell is cast again it will overwrite the data under "1" and the two labels. The data will be mixed up, and not MUI at that point. So, we must think to classify the data under some unique ID. What is the answer to this? Handle IDs.

Handle IDs are assigned each time a handle is created. It is a unique integer, where it is 0x100000[icode=jass]+HandlesExisting for each handle created. (0x100000=hex, 1048576) All you need to grasp from this is that each handle has a unique ID. You can retrieve a unique ID using: [icode=jass]native GetHandleId takes handle h returns integer

Now you might think you can do this:
JASS:
globals
    hashtable SpellHash = InitHashtable()
endglobals

function DamagePeriodic takes nothing returns nothing
    local unit caster = LoadUnitHandle(SpellHash,???,0) //Now what?
    local unit target = LoadUnitHandle(SpellHash,???,1) //We still don't know the ID's.
endfunction

function SpellActions takes nothing returns nothing
    local unit caster    = GetTriggerUnit()
    local unit target    = GetSpellTargetUnit() 
    local timer periodic = CreateTimer() 
    
    call SaveUnitHandle(SpellHash,GetHandleId(caster),0,caster) //Save the caster under its ID (category), and "0" (label)
    call SaveUnitHandle(SpellHash,GetHandleId(target),1,target) //Save the target under its ID (category), and "1" (label)
    
    call TimerStart(periodic,1,true,function DamagePeriodic)
    set caster   = null
    set target   = null
    set periodic = null
endfunction

So that doesn't work. Okay, now we must think. We are starting a timer. What information IS transferred? Well, not much. Except for this:
native GetExpiredTimer takes nothing returns timer

Nice. Hopefully you might see the significance of this. :) If not, I'll explain. This allows us to retrieve the timer that was started. Now, each time the spell is cast, we create a new timer. We get a timer that has its unique ID, and then we can retrieve it in the callback. That means, we can assign things to its ID!!
JASS:
globals
    hashtable SpellHash = InitHashtable()
endglobals

function DamagePeriodic takes nothing returns nothing
    local timer periodic = GetExpiredTimer()
    local integer uniqueId = GetHandleId(periodic)  
    local unit caster = LoadUnitHandle(SpellHash,uniqueId,0)  //Now we can get the data!
    local unit target = LoadUnitHandle(SpellHash,uniqueId,1)     
    local integer counter = LoadInteger(SpellHash,uniqueId,2)
    
    call UnitDamageTarget(caster,target,25,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
    call DestroyEffect(AddSpecialEffectTarget("war3mapImported\\MyAwesomeEffect.mdl",target,"chest"))
    
    if counter>=5 then
        call PauseTimer(periodic)
        call DestroyTimer(periodic)
        call FlushChildHashtable(SpellHash,uniqueId)  //Clear the data under the ID.
    else
        call SaveInteger(SpellHash,uniqueId,2,counter+1)    
    endif
    
    set periodic = null
    set caster = null
    set target = null
endfunction

function SpellActions takes nothing returns nothing
    local unit caster    = GetTriggerUnit()
    local unit target    = GetSpellTargetUnit() 
    local timer periodic = CreateTimer()
    local integer uniqueId = GetHandleId(periodic) 
    
    call SaveUnitHandle(SpellHash,uniqueId,0,caster) //Save the caster under timerID (category), and "0" (label)
    call SaveUnitHandle(SpellHash,uniqueId,1,target) //Save the target under timerID (category), and "1" (label)
    //It won't be overwritten, the ID is unique.
    
    call SaveInteger(SpellHash,uniqueId,2,1) //The counter, count how many seconds have passed.
    
    call TimerStart(periodic,1,true,function DamagePeriodic)
    set caster   = null
    set target   = null
    set periodic = null
endfunction

Okay, now we've gotten that down. It is ugly though, I might post later on to explain structs to you. It will make hashes seem a bit more pretty, and it will be more efficient than the normal attachment. It won't be as efficient as normal systems but still pretty fast. [no noticeable difference in many cases]

Yeah, Berbanog explained this pretty well. Just thought I'd might as well add in an example of the usage in a practical spell in JASS, is all. :)
 
Level 9
Joined
Jun 7, 2008
Messages
440
If I wanted to create multiple units of the same type, could I load the handle more than once during a repeating timer? Or do I need to save another one into the hashable??
 
Level 9
Joined
Jun 7, 2008
Messages
440
No problem.

If I were to create a unit i would load the unit from the hashtable correct? Now what if i wanted to create that unit say 2 times? or perhaps 4 times? How do I go about doing this??
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Well you could run a loop that runs for however many units you want created and just use the unit-type of the unit that is stored in the hash-table.

  • Actions
    • Set unit = (Load 1 of 1 in (Last created hashtable))
    • For each (Integer A) from 1 to 2, do (Actions)
      • Loop - Actions
        • Unit - Create 1 (Unit-type of unit) for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
This will create the unit twice (from 1 to 2) for Player 1 (Red) at the center of the map. Feel free to customize those values to your preference.
 
Status
Not open for further replies.
Top