HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Hashtable Not Saving Unit Group

02-26-2010, 10:05 PM#1
sPyRaLz
I've searched around for hours.. I still cant find a solution to this.

Im trying to save a unit group to a hashtable, but it just wont save.
Ive tested it with debug messages to prove this.

Code:
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group g: " + I2S(CountUnitsInGroup(g)))
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group n: " + I2S(CountUnitsInGroup(n)))
    call SaveGroupHandleBJ( n, 0, 1, udg_ht ) 
    set  n1 = LoadGroupHandleBJ( 0, 1, udg_ht ) 
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg n1: " + I2S(CountUnitsInGroup(n1)))

The outputs to the debug messages are
2
2
0

Does anyone have any idea at all how to fix this?
Or if there are any other alternatives i might be able to use? =(
02-26-2010, 11:35 PM#2
PurgeandFire111
I have a feeling you made the same mistake I did. I too looked around for a while when I was dealing with hashtables, then I realized I didn't do:
Collapse JASS:
set udg_ht = InitHashtable()

Hopefully that was the problem. Otherwise it should work, just tested it.
02-27-2010, 04:11 AM#3
sPyRaLz
Quote:
Originally Posted by PurgeandFire111
I have a feeling you made the same mistake I did. I too looked around for a while when I was dealing with hashtables, then I realized I didn't do:
Collapse JASS:
set udg_ht = InitHashtable()

Hopefully that was the problem. Otherwise it should work, just tested it.

Hm.. that would work. From what i understand The prefix "udg_" basically makes the hashtable global right? So it can be shared between functions. Declaring otherwise would only allow it to work within a single function.

I did have it declared by the way, except i used the variable editor. I'm worried that because i did so, my spell wouldn't be multi instancable as it is shared.

After digging around i was fortunate enough to come across this:

http://www.wc3c.net/showthread.php?t=106334

Apparently I misunderstood the function of missionKey and integerKey.

Quote:
// Hashtable value types
constant integer bj_HASHTABLE_BOOLEAN = 0
constant integer bj_HASHTABLE_INTEGER = 1
constant integer bj_HASHTABLE_REAL = 2
constant integer bj_HASHTABLE_STRING = 3
constant integer bj_HASHTABLE_HANDLE = 4

Heres the corrected version of the code:

Quote:
call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group g: " + I2S(CountUnitsInGroup(g)))
call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group n: " + I2S(CountUnitsInGroup(n)))
call SaveGroupHandleBJ( n, 0, 4, udg_ht )
set n1 = LoadGroupHandleBJ( 0, 4, udg_ht )
call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg n1: " + I2S(CountUnitsInGroup(n1)))

That out of the way, any idea how i clean up the hashtable after im done with it?
Wouldnt want any leaks. My guess is:

ht = null
destroy.ht() ?


Edit: Okay.. this is getting really frustrating..
This hashtable group save and load is intended to store a group of units affected by a spell.
For the first cast instance of the spell, it is able to callback the group, after that it keeps returning an empty group..



Edit #2:
Added set udg_ut = InitHashTable()
Group saving now works properly within function #1 with multiple instances.
For some very annoyingly transparent reason, my second function is unable to call the values from a freaking GLOBAL hashtable variable.
Im really stumped here. Any help would be greatly appreciated.

Heres the algorithm..
Quote:
Function #2
load group from udg_ht - Returns 0
end

Function #1
set udg_ht = InitHashtable()
Group size is 3
save group to udg_ht - Okay
load group from udg_ht- Okay, Returns group size 3
call function 2
end
02-27-2010, 06:39 AM#4
saw792
Just putting udg_ in front of a variable declaration doesn't make it global. What matters is where it is declared. Since you're using vJASS you can declare a globals block wherever you like:
Collapse JASS:
globals
  hashtable ht = InitHashtable()
endglobals

You should never have to destroy the hashtable after it's created, they are designed to be permanent. You can use natives to flush hashtables of data though.

Show us your actual, full code in [jass] tags.
02-27-2010, 06:58 AM#5
sPyRaLz
Here it is, Its actually modded off a tutorial by Blade.dk. Unfortunately the tutorial was made before 1.23b where the return bug was used.

Im trying to do a conversion so it works with hashtable. But im having terrible luck.
If it would make it easier, I wouldn't mind sharing the map through PM. Just say the word and ill send it over.

Thanks for responding, your feedback is greatly appreciated.

FYI: Function #2 Is Stomp_Move, Function #1 is Stomp_Action

Forgive me if there are silly mistakes, im not very experienced with vJASS yet.

Collapse JASS:
// Stomp spell by Blade.dk
// Stomp spell by Blade.dk
// Visit [url]http://www.wc3campaigns.net[/url]
//
globals
  hashtable ht = InitHashtable()
endglobals

constant function Stomp_SpellId takes nothing returns integer
    return 'A003'
endfunction

function Stomp_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == Stomp_SpellId()
endfunction

function Stomp_Filter takes nothing returns boolean
    return IsPlayerEnemy(GetOwningPlayer(GetTriggerUnit()), GetOwningPlayer(GetFilterUnit())) and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_FLYING)
endfunction

function Stomp_CopyGroup takes group g returns group
    set bj_groupAddGroupDest = CreateGroup()
    call ForGroup(g, function GroupAddGroupEnum)
    return bj_groupAddGroupDest
endfunction

function Stomp_Move takes nothing returns nothing  //Moves units

//Initialize local vars, pulls stuff from gamecache
    //local string s = I2S(H2I(GetExpiredTimer()))
    //local gamecache gc = udg_AbilityCache
    //local hashtable ht = ht
    local integer j = Stomp_SpellId()+GetConvertedPlayerId(GetOwningPlayer(GetTriggerUnit()))
    local real x = LoadRealBJ(j+1000*1, 2, ht)
    local real y = LoadRealBJ(j+1000*2, 2, ht)
    local integer i = LoadIntegerBJ(j, 1, ht)
    local group g = LoadGroupHandleBJ(j, 4, ht)
    local real dur = LoadRealBJ(j+1000*4, 2, ht)+0.05 //dur
    local real ux
    local real uy
    local real a
    local unit f
    local real p = LoadRealBJ(j+1000*3, 2, ht)-0.5/(1+0.5*i) //speed
    local real fx = LoadRealBJ(j+1000*5, 2, ht)+0.05
    
    //set ht = InitHashtable()
   
    //call DisplayTimedTextToPlayer(Player(0),0,0,60,"Spell Level: " + I2S(i)) 
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"xpos: " + R2S(x)) 
    //call DisplayTimedTextToPlayer(Player(0),0,0,60,I2S(CountUnitsInGroup(g)))
    //call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg ,j = " + I2S(j))
//Pushback effect
    if dur < 1+0.5*i then
        loop
            set f = FirstOfGroup(g)
            
            exitwhen f == null
            set ux = GetUnitX(f)
            set uy = GetUnitY(f)
            set a = Atan2(uy-y, ux-x)
            
            call SetUnitPosition(f, ux+p*Cos(a), uy+p*Sin(a))
            if fx >= 0.5 then
                call DestroyEffect(AddSpecialEffectTarget(GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 1), f, GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 2)))
            endif
            call GroupRemoveUnit(g, f)
        endloop
        call SaveRealBJ(dur, j+1000*4, 2, ht)  //dur
        call SaveRealBJ(p, j+1000*3, 2, ht) //p, speed
        call SaveRealBJ(fx, j+1000*5, 2, ht) //fx

//Reset fx to 0, put here to prevent resetting during the loop.
        if fx >= 0.5 then
            call SaveRealBJ(fx, j+1000*5, 2, ht)
        endif
    else
        call DestroyGroup(LoadGroupHandleBJ(j, 4, ht))
        //call FlushStoredMission(gc, s)
        call DestroyTimer(GetExpiredTimer())
    endif

//Cleanup
    //set gc = null
    call DestroyGroup(g)
    call FlushParentHashtable(ht)
    set g = null
    set f = null
endfunction

function Stomp_Actions takes nothing returns nothing
//Initialize Variables


    local unit c = GetTriggerUnit()
    local real x = GetUnitX(c)
    local real y = GetUnitY(c)
    local integer i = GetUnitAbilityLevel(c, Stomp_SpellId())
    local integer j = Stomp_SpellId()+GetConvertedPlayerId(GetOwningPlayer(GetTriggerUnit())) //Player Number of Owner of triggering unit
    local boolexpr b = Condition(function Stomp_Filter)
    local group g = CreateGroup()
    local group n
    local group n1
    local unit f
    //local gamecache gc = udg_AbilityCache
    
    local timer t = CreateTimer()
    //local string s = I2S(H2I(t))
    //set ht = InitHashtable()
    
//Functions
    //call DisplayTextToForce( GetPlayersAll(), GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 0) ) //Debug
    //call DisplayTextToForce( GetPlayersAll(), GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 1) ) //Debug
    call DestroyEffect(AddSpecialEffect(GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 0), x, y))
    call GroupEnumUnitsInRange(g, x, y, 200+50*i, b)
    set n = Stomp_CopyGroup(g)
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group g: " + I2S(CountUnitsInGroup(g)))
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg, Group n: " + I2S(CountUnitsInGroup(n)))
    call SaveGroupHandleBJ( g, j, 4, ht )   
    set  n1 = LoadGroupHandleBJ( j, 4, ht ) 
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg n1: " + I2S(CountUnitsInGroup(n1)))
    
    loop
        set f = FirstOfGroup(n)
        call DisplayTimedTextToPlayer(Player(0),0,0,60,GetUnitName(f))
        exitwhen f == null
        call UnitDamageTarget(c, f, 50*i, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
        call GroupRemoveUnit(n, f)
    endloop

//Store stuff into gamecache

//    call StoreInteger(gc, s, "level", i)
//    call StoreInteger(gc, s, "group", H2I(g))

//    call StoreReal(gc, s, "x", x)
//    call StoreReal(gc, s, "y", y)
//    call StoreReal(gc, s, "speed", 10)
    
//Store stuff into Hashtable    
    call SaveIntegerBJ( i, j+1000*1, 1, ht  )
    //call SaveIntegerBJ( j, Stomp_SpellId()+j+1000*2, 1, ht  )
    //call SaveGroupHandleBJ( g, 0 , 4, ht )  
    set  n1 = LoadGroupHandleBJ( j, 4, ht ) 
    call DisplayTimedTextToPlayer(Player(0),0,0,60,"Debug Msg n1: " + I2S(CountUnitsInGroup(n1)) + " ,j = " + I2S(j))
    call SaveRealBJ( x, j+1000*1, 2, ht )
    call SaveRealBJ( y, j+1000*2, 2, ht )
    call SaveRealBJ( 10, j+1000*3, 2, ht  )

//Expiring timer that runs Stomp_Move
    call TimerStart(t, 1, true, function Stomp_Move)

//Destroy variables after use.
    set c = null
    call DestroyBoolExpr(b)
    set b = null
    set g = null
    call DestroyGroup(n)
    call DestroyGroup(n1)
    set n = null
    set f = null
//    set gc = null
    call FlushParentHashtable(ht)
    set ht = null //This may cause a bug
    
    set t = null
endfunction


//===========================================================================
function InitTrig_Stomp takes nothing returns nothing
    set gg_trg_Stomp = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Stomp, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Stomp, Condition( function Stomp_Conditions ) )
    call TriggerAddAction( gg_trg_Stomp, function Stomp_Actions )
    call Preload(GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 0))
    call Preload(GetAbilityEffectById(Stomp_SpellId(), EFFECT_TYPE_MISSILE, 1))
endfunction



Edit:
Problem solved, Caused by careless destruction of unit group before its intended use finished.
Also caused by premature flushing of the parent hashtable.
02-28-2010, 03:53 PM#6
Master_chan
Did you create group for n1 variable?
03-02-2010, 04:36 AM#7
sPyRaLz
Quote:
Originally Posted by Master_chan
Did you create group for n1 variable?

Yea i did. But it was unused in the end. Please note the block of code up above is riddled with bugs =/ If you'd like a copy of the fixed code i could post it here once i'm able.

Its been heavily modified since then. Im now using structs attached to a timer to pass my variables instead of a global hashtable. Eliminates the risk of accidentaly overwriting an important value stored in hashtable as my spells are intended to be multiinstancable.

That actually brings up the question.. if i create a global hashtable, is it shared between all players in multiplayer?
03-02-2010, 06:15 AM#8
Ammorth
Hashtables are global, so yes.

Usually when writing spells, we "attach" the spell data to the timer controlling it. You can do this by using one of the keys for the spcific timer ID (or handle ID). You can get a Handle ID by calling GetHandleId() and it will return a unique integer for any handle. Therefore, you could then store the spell struct in a hashtable based on this ID.

If you use TimerUtils (which I recommend) then this will do it all automatically for you, with SetTimerData(timer, data); where data is the struct (or integer) you wish to attach. You can then call GetTimerData(timer, data) in each function that is executed from the timer to get your struct back.

But it sounds like you already know this.
03-02-2010, 11:58 AM#9
sPyRaLz
Hmm.. That sounds useful. I was actually using Vexorians CSData library, which i'm not sure whether or not has the limitation of passing structs between function calls.

I searched for GetHandleID() couldnt find it in my VJass function list though. =/
03-02-2010, 03:02 PM#10
Ammorth
native GetHandleId takes handle h returns integer

I think you may need a more recent version of the trigger GUI thing (name is slipping my mind) to see newer natives.
03-02-2010, 04:08 PM#11
sPyRaLz
Quote:
Originally Posted by Ammorth
native GetHandleId takes handle h returns integer

I think you may need a more recent version of the trigger GUI thing (name is slipping my mind) to see newer natives.

I havnt actually had a chance to try yet, but assuming my version of Jasshelper recognizes the function, does GetHandleId work with timers? eg. GetHandleId(timer)
03-02-2010, 05:45 PM#12
Ammorth
yes, anything that extends a handle (unit, item, timers, triggers, etc) You can actually get a full list of all the type and which extend handles by looking at the common.j file found either within patch.mpq or within your jasshelper folder.

Watch our for multiple extends (aka agent and widget).