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!
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?
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:
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:
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.
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?
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
<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.
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??
I really don't know what you mean by "load the handle more than once during a repeating timer". Please try to explain your problem exactly so we can better help you.
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??
This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
By continuing to use this site, you are consenting to our use of cookies.