• 🏆 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!

[vJASS] HandleId bug

Status
Not open for further replies.
Level 16
Joined
Mar 3, 2006
Messages
1,564
I am really having a very strange bug regarding HandleId. I don't know how to explain it but I will try.

I am trying to make a chain spell so here are the Initial triggers (ignore leaks and any other bugs)

All highlighted lines are part of the bug (or what I think it is)
JASS:
scope ChainFire initializer Init_ChainFire

globals
private constant integer ABILTY_ID = 'A001'
private constant integer CHFIRE_ID = 'c000'
private constant timer   cf_timer  = CreateTimer()
@private constant real CF_TIMER_INTERVAL = 0.01000@
private constant real    STEP_DIST = 16.00
private constant real    CHEK_DIST = 24.00
private constant real    DAMAGE = 55.00
private constant integer JUMP   = 4
private integer index = 0
private integer array temp_dat

private hashtable unit_flag = InitHashtable()

private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL    // Spell Attack Type
private constant damagetype DMGTYPE = DAMAGE_TYPE_FIRE      // Fire Damage Type
endglobals



function GetNearestUnitOfUnitExGroup takes unit source, real radius, integer ht_index returns unit
local group g = CreateGroup()
local group temp_g = CreateGroup()
local real locX = GetUnitX(source)
local real locY = GetUnitY(source)
local real dx
local real dy
local unit ref_u
local unit any_u
local unit temp_u
local real dist_ref_u
local real dist_any_u


call GroupEnumUnitsInRange(temp_g,locX,locY,radius,null)

loop
set temp_u = FirstOfGroup(temp_g)
exitwhen temp_u == null

@if temp_u != LoadUnitHandle(unit_flag,ht_index,GetHandleId(temp_u)) /*and GetWidgetLife(temp_u) >= 0.405 and IsUnitEnemy(temp_u,Player(0))*/ then@
call GroupAddUnit(g,temp_u)
endif

call GroupRemoveUnit(temp_g,temp_u)
endloop
call DestroyGroup(temp_g)

set ref_u = FirstOfGroup(g)
call GroupRemoveUnit(g,ref_u)

loop
set any_u = FirstOfGroup(g)
exitwhen any_u == null
set dx = GetUnitX(ref_u) - locX
set dy = GetUnitY(ref_u) - locY
set dist_ref_u = SquareRoot(dx*dx + dy*dy)
set dx = GetUnitX(any_u) - locX
set dy = GetUnitY(any_u) - locY
set dist_any_u = SquareRoot(dx*dx + dy*dy)

if dist_ref_u > dist_any_u then
set ref_u = any_u
endif

call GroupRemoveUnit(g,any_u)

endloop

call DestroyGroup(g)
return ref_u
endfunction


struct data
unit caster
unit target
unit cfd // Chain Fire Dummy
integer jump
real face

static method MoveCF takes nothing returns nothing
local thistype dat
local integer i = 0
local real x  // Current X of Chain Fire Missile
local real y  // Current Y of Chain Fire Missile
local real tx // Target X
local real ty // Target Y
local real ptx // Previous Target X
local real pty // Previous Target Y
local real dx
local real dy
local real dist

loop
exitwhen i >= index
set dat = temp_dat[i]
set x = GetUnitX(dat.cfd)
set y = GetUnitY(dat.cfd)
set tx = GetUnitX(dat.target)
set ptx = tx
set ty = GetUnitY(dat.target)
set pty = ty
set dx = tx - x
set dy = ty - y
set dist = SquareRoot(dx*dx + dy*dy)

if dist < CHEK_DIST then

    call UnitDamageTarget(dat.caster, dat.target,DAMAGE, false, false, ATKTYPE, DMGTYPE, null)
    @call SaveUnitHandle(unit_flag,dat,GetHandleId(dat.target),dat.target)@
    set dat.jump = dat.jump - 1
    if dat.jump <= 0 then // end the spell
            set index = index - 1
            set temp_dat[i] = temp_dat[index]
            set i = i - 1
            call KillUnit(dat.cfd)
            set dat.caster = null
            set dat.target = null
            call FlushChildHashtable( unit_flag , dat )

        if index == 0 then
            call PauseTimer(cf_timer)
        endif

        call dat.deallocate()

    else
        // seek the next target
        @set dat.target = GetNearestUnitOfUnitExGroup(dat.target,500.0,dat)@
        @call BJDebugMsg("target["+I2S(dat)+"] = "+I2S(GetHandleId(dat.target)))@
        // if there is no unit then end the spell, also
        if dat.target == null then
        call BJDebugMsg("Spell Ended no new target")
            set dat.jump = 0
            set index = index - 1
            set temp_dat[i] = temp_dat[index]
            set i = i - 1
            call KillUnit(dat.cfd)
            set dat.caster = null
            set dat.target = null
            call FlushChildHashtable( unit_flag , dat )

        if index == 0 then
            call PauseTimer(cf_timer)
        endif

        call dat.deallocate()

        else
        
            // set the facing and locations again as done in StartCF method
            // since tx and ty have not been set to another value then use it again
            set dx = GetUnitX(dat.target) - ptx
            set dy = GetUnitY(dat.target) - pty
            set dat.face = Atan2(dy,dx)
            call SetUnitFacing(dat.cfd,dat.face * bj_RADTODEG)
        
        endif

    endif


else

    call SetUnitX(dat.cfd,x + STEP_DIST * Cos(dat.face))
    call SetUnitY(dat.cfd,y + STEP_DIST * Sin(dat.face))

endif





set i = i + 1
endloop

endmethod

static method StartCF takes unit caster, unit target returns nothing
local thistype dat = thistype.allocate()
local real cx // Caster X
local real cy // Caster Y
local real tx // Target X
local real ty // Target Y
local real dx
local real dy

set temp_dat[index] = dat
set dat.caster = caster
set dat.target = target
set dat.jump   = JUMP
set cx = GetUnitX(caster)
set cy = GetUnitY(caster)
set tx = GetUnitX(target)
set ty = GetUnitY(target)
set dx = tx - cx
set dy = ty - cy
set dat.face = Atan2(dy,dx)
set dat.cfd = CreateUnit(GetOwningPlayer(caster),CHFIRE_ID,cx,cy,dat.face*bj_RADTODEG)

if index ==0 then
call TimerStart(cf_timer,CF_TIMER_INTERVAL,true,function thistype.MoveCF)
endif

set index = index + 1
endmethod

endstruct

function CF_Actions takes nothing returns nothing

    if GetSpellAbilityId() == ABILTY_ID then
        call data.StartCF(GetTriggerUnit(),GetSpellTargetUnit())
    endif

endfunction

function Init_ChainFire takes nothing returns nothing
local trigger t = CreateTrigger()

    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t,function CF_Actions)

endfunction

endscope
Now, to the
bug

When the spell is cast rapidly (i.e. hold Shift + Many Clicks on the target) the last unit in the chain takes the damage from the first cast only and ignores the rest although the missile dummy goes towards it.
I have tried many things at it gave me strange results:

- When the spell is cast once and waited for it to end the last unit takes normal damage. No problems for this one.

- When I hold Shift and click twice on a target, every unit takes twice damage except the last one in the chain that took the damage one time.

- At first I solved this problem by changing the filter in the GetNearest function I added GetWidgetLife and it worked normally when cast many times. (don't know why)

- Next time I removed GetWidgetLife and changed the timer time to 0.02 and ,strangely, the spell worked normally.

- I reset the timer to 0.01 and decided to track the unit handle Id so I added a line to view the Id and here is the results I get:
(Note: The result will varry from each run)

- for single cast:
target[1] = 1048731
target[1] = 1048730
target[1] = 1048729

- for 3 casts:
target[1] = 1048731
target[1] = 1048730
target[2] = 1048731
target[1] = 1048729
target[2] = 1048730
target[3] = 1048731
target[2] = 1048750
target[3] = 1049101


I don't understand how the id changed, I hope someone explain this to me. And sorry for all this long post.
 
Last edited:
Well, if the handle ID is changing, then that means that the unit that the variable is pointing to has changed.

It probably is a problem with the grouping. Try to make sure you are grouping from the correct area, and also make sure that the units from the previous casts have all been removed from the older groups or w/e and aren't saved in the hashtable. (unless they are supposed to be)

Sorry, you already probably know most of this. I recommend you post the test map so someone can help a bit more. Or maybe try to run separate tests on your group nearest unit function to see if it is working properly.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
Bribe is just saying, your indentation of the script is conflicting with peoples' ability to help you because it's such an eye sore to read. No offense of course, but the indenting should be fixed so that we can more clearly find the problems.

The problem is that I can't find the wrong with the script it should run without glitches, you see the script works fine, very fine, when I add GetWidgetLife(u) to the filter which checks what unit will be removed from the group that the GetNearest function use to determine which unit is the closest, but when I add this temp_u != LoadUnitHandle(unit_flag,dat,GetHandleId(temp_u)) alone I found that the handleId for the last unit change. How is it possible that the handle change ?

Expect edit soon.

<<< EDIT >>>
@ Bribe: sorry, I didn't understand what do you mean by indentation; you mean this /* */ don't you ?
anyway I removed them from the script above.
 
Here, I indented it for you:

JASS:
scope ChainFire initializer Init_ChainFire

    globals
        private constant integer ABILTY_ID = 'A001'
        private constant integer CHFIRE_ID = 'c000'
        private constant timer cf_timer = CreateTimer( )
        private constant real CF_TIMER_INTERVAL = 0.01000
        private constant real STEP_DIST = 16.00
        private constant real CHEK_DIST = 24.00
        private constant real DAMAGE = 55.00
        private constant integer JUMP = 4
        private integer index = 0
        private integer array temp_dat

        private hashtable unit_flag = InitHashtable( )

        private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL // Spell Attack Type
        private constant damagetype DMGTYPE = DAMAGE_TYPE_FIRE // Fire Damage Type
    endglobals



    function GetNearestUnitOfUnitExGroup takes unit source, real radius, integer ht_index returns unit
        local group g = CreateGroup( )
        local group temp_g = CreateGroup( )
        local real locX = GetUnitX( source )
        local real locY = GetUnitY( source )
        local real dx
        local real dy
        local unit ref_u
        local unit any_u
        local unit temp_u
        local real dist_ref_u
        local real dist_any_u


        call GroupEnumUnitsInRange( temp_g, locX, locY, radius, null )

        loop
            set temp_u = FirstOfGroup( temp_g )
            exitwhen temp_u == null

            if temp_u != LoadUnitHandle( unit_flag, ht_index, GetHandleId( temp_u ) ) / * and GetWidgetLife( temp_u ) >= 0.405 and IsUnitEnemy( temp_u, Player( 0 ) ) * / then
                call GroupAddUnit( g, temp_u )
            endif

            call GroupRemoveUnit( temp_g, temp_u )
        endloop
        call DestroyGroup( temp_g )

        set ref_u = FirstOfGroup( g )
        call GroupRemoveUnit( g, ref_u )

        loop
            set any_u = FirstOfGroup( g )
            exitwhen any_u == null
            set dx = GetUnitX( ref_u ) - locX
            set dy = GetUnitY( ref_u ) - locY
            set dist_ref_u = SquareRoot( dx * dx + dy * dy )
            set dx = GetUnitX( any_u ) - locX
            set dy = GetUnitY( any_u ) - locY
            set dist_any_u = SquareRoot( dx * dx + dy * dy )

            if dist_ref_u > dist_any_u then
                set ref_u = any_u
            endif

            call GroupRemoveUnit( g, any_u )

        endloop

        call DestroyGroup( g )
        return ref_u
    endfunction


    struct data
        unit caster
        unit target
        unit cfd // Chain Fire Dummy
        integer jump
        real face

        static method MoveCF takes nothing returns nothing
            local thistype dat
            local integer i = 0
            local real x // Current X of Chain Fire Missile
            local real y // Current Y of Chain Fire Missile
            local real tx // Target X
            local real ty // Target Y
            local real ptx // Previous Target X
            local real pty // Previous Target Y
            local real dx
            local real dy
            local real dist

            loop
                exitwhen i >= index
                set dat = temp_dat[i]
                set x = GetUnitX( dat.cfd )
                set y = GetUnitY( dat.cfd )
                set tx = GetUnitX( dat.target )
                set ptx = tx
                set ty = GetUnitY( dat.target )
                set pty = ty
                set dx = tx - x
                set dy = ty - y
                set dist = SquareRoot( dx * dx + dy * dy )

                if dist < CHEK_DIST then

                    call UnitDamageTarget( dat.caster, dat.target, DAMAGE, false, false, ATKTYPE, DMGTYPE, null )
                    call SaveUnitHandle( unit_flag, dat, GetHandleId( dat.target ), dat.target )
                    set dat.jump = dat.jump - 1
                    if dat.jump <= 0 then // end the spell
                        set index = index - 1
                        set temp_dat[i] = temp_dat[index]
                        set i = i - 1
                        call KillUnit( dat.cfd )
                        set dat.caster = null
                        set dat.target = null
                        call FlushChildHashtable( unit_flag , dat )

                        if index == 0 then
                            call PauseTimer( cf_timer )
                        endif

                        call dat.deallocate( )

                    else
                        // seek the next target
                        set dat.target = GetNearestUnitOfUnitExGroup( dat.target, 500.0, dat )
                        call BJDebugMsg( "target[" + I2S( dat ) + "] = " + I2S( GetHandleId( dat.target ) ) )
                        // if there is no unit then end the spell, also
                        if dat.target == null then
                            call BJDebugMsg( "Spell Ended no new target" )
                            set dat.jump = 0
                            set index = index - 1
                            set temp_dat[i] = temp_dat[index]
                            set i = i - 1
                            call KillUnit( dat.cfd )
                            set dat.caster = null
                            set dat.target = null
                            call FlushChildHashtable( unit_flag , dat )

                            if index == 0 then
                                call PauseTimer( cf_timer )
                            endif

                            call dat.deallocate( )

                        else
        
                            // set the facing and locations again as done in StartCF method
                            // since tx and ty have not been set to another value then use it again
                            set dx = GetUnitX( dat.target ) - ptx
                            set dy = GetUnitY( dat.target ) - pty
                            set dat.face = Atan2( dy, dx )
                            call SetUnitFacing( dat.cfd, dat.face * bj_RADTODEG )
        
                        endif

                    endif


                else

                    call SetUnitX( dat.cfd, x + STEP_DIST * Cos( dat.face ) )
                    call SetUnitY( dat.cfd, y + STEP_DIST * Sin( dat.face ) )

                endif





                set i = i + 1
            endloop

        endmethod

        static method StartCF takes unit caster, unit target returns nothing
            local thistype dat = thistype.allocate( )
            local real cx // Caster X
            local real cy // Caster Y
            local real tx // Target X
            local real ty // Target Y
            local real dx
            local real dy

            set temp_dat[index] = dat
            set dat.caster = caster
            set dat.target = target
            set dat.jump = JUMP
            set cx = GetUnitX( caster )
            set cy = GetUnitY( caster )
            set tx = GetUnitX( target )
            set ty = GetUnitY( target )
            set dx = tx - cx
            set dy = ty - cy
            set dat.face = Atan2( dy, dx )
            set dat.cfd = CreateUnit( GetOwningPlayer( caster ), CHFIRE_ID, cx, cy, dat.face * bj_RADTODEG )

            if index ==0 then
                call TimerStart( cf_timer, CF_TIMER_INTERVAL, true, function thistype.MoveCF )
            endif

            set index = index + 1
        endmethod

    endstruct

    function CF_Actions takes nothing returns nothing

        if GetSpellAbilityId( ) == ABILTY_ID then
            call data.StartCF( GetTriggerUnit( ), GetSpellTargetUnit( ) )
        endif

    endfunction

    function Init_ChainFire takes nothing returns nothing
        local trigger t = CreateTrigger( )

        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction( t, function CF_Actions )

    endfunction

endscope
 
I was too lazy to do the work myself, so I use The_Witcher's Script Language Aligner.

To indent code is to put 1 tab (or four spaces) right before each line, and the number of spaces would increase by four when you hit a block starter like function, struct, scope, etc.. And it would decrease by four whenever you hit a block ended like end function, endstruct, endscope, etc..

It makes your code look better and more readable.
 
Level 2
Joined
Feb 24, 2012
Messages
8
It's a wild guess, but maybe your GetNearestUnitOfUnitExGroup is returning one of the chain fire dummy units that you're creating.

If that's not it, then try killing the target[x] units, and see what units die when you got the anomalies.
Either way, I'm pretty sure the handle ID of a unit does not change, ever.

An unrelated note: you could make clever use of the GroupEnumUnitsInRange(temp_g,locX,locY,radius,null) by having a filter function, i.e.
GroupEnumUnitsInRange(temp_g,locX,locY,radius,myFilterFunc) where myFilterFunc only adds a unit to the group when it's the nearest to the point, as well as checking if it's the LoadUnitHandle(). That way you don't need to loop over the temp_g twice.
 
Level 16
Joined
Mar 3, 2006
Messages
1,564
An unrelated note: you could make clever use of the GroupEnumUnitsInRange(temp_g,locX,locY,radius,null) by having a filter function, i.e.
GroupEnumUnitsInRange(temp_g,locX,locY,radius,myFilterFunc) where myFilterFunc only adds a unit to the group when it's the nearest to the point, as well as checking if it's the LoadUnitHandle(). That way you don't need to loop over the temp_g twice.

I am trying not to use a filter function to avoid making thread and so making the spell more faster and lighter.

<<< EDIT >>>

And it did the same problem.

JASS:
function Exclude takes nothing returns boolean
return GetFilterUnit() != (LoadUnitHandle(unit_flag,temp_i,GetHandleId(GetFilterUnit())))
endfunction

function GetNearestUnit takes unit source, real radius, integer dat returns unit
local group g = CreateGroup()
local real locX = GetUnitX(source)
local real locY = GetUnitY(source)
local real dx
local real dy
local unit ref_u
local unit any_u
local real dist_ref_u
local real dist_any_u

  set temp_i = dat
  call GroupEnumUnitsInRange(g,locX,locY,radius,function Exclude)

  set ref_u = FirstOfGroup(g)
  call GroupRemoveUnit(g,ref_u)

  loop
   set any_u = FirstOfGroup(g)
   exitwhen any_u == null
   set dx = GetUnitX(ref_u) - locX
   set dy = GetUnitY(ref_u) - locY
   set dist_ref_u = SquareRoot(dx*dx + dy*dy)
   set dx = GetUnitX(any_u) - locX
   set dy = GetUnitY(any_u) - locY
   set dist_any_u = SquareRoot(dx*dx + dy*dy)

   if dist_ref_u > dist_any_u then
     set ref_u = any_u
   endif

   call GroupRemoveUnit(g,any_u)

  endloop

  call DestroyGroup(g)
return ref_u
endfunction
 
Last edited:
Status
Not open for further replies.
Top