Journeys in Map Debugging.

overload119

New Member
Reaction score
5
I made this post originally on WC3C, but I'm starting to this this forum is a lot more helpful.

I've been working on this map for a long while, but for the last week I've stopped progress because I've come across a bug that I just can't fix.

In the map there is a hero with a Cleave ability. All it does is knockback enemies. At a certain point in the map (always different) the spell still does damage, but stops knocking back enemies. I've done BJDebugMsg checks to see what the problem is, but it hasn't helped much. At a certain point the PolarProjection functions I'm using just stop working (they move the unit forward, then back, with a net result of 0 displacement, over and over again).

I know the timer is working because part of the spell adds a knockback graphic to the moving unit, and that effect is being displayed and then removed when the knockback is supposed to end.

The spell itself utilizes TimerUtils and structs. I'll post the code as soon as I can.

It's just so frustrating having a spell that works fine until a random time in the map. Any help or tips regarding this would be greatly appreciated.

Spell code:
JASS:

function PolarProjectionX takes real x, real dist, real angle returns real
    return x + dist * Cos(angle)
endfunction

function PolarProjectionY takes real y, real dist, real angle returns real
    return y + dist * Sin(angle)
endfunction

function AngleBetweenPointsXY takes real x1, real y1, real x2, real y2 returns real
    return Atan2(y2 - y1, x2 - x1)
endfunction



JASS:
scope Cleave
private struct data
    unit target          = null
    unit caster          = null
    group knockGroup     = null
    real v               = 0.  
    real angle           = 0.
endstruct

function Trig_Cleave_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A00N'
endfunction

private function Filt takes nothing returns boolean 
    local data d = GetTimerData(GetExpiredTimer())
    local unit t = GetFilterUnit()
    if( IsUnitEnemy(t, tempPlayer) and GetWidgetLife(t)>0.405 and not IsUnitType(t, UNIT_TYPE_MECHANICAL)and not IsUnitInGroup(t, d.knockGroup) and GetUnitMoveSpeed(t)>10 ) then
        call GroupAddUnit(d.knockGroup, t) 
        call UnitAddAbility(t, 'A00O')
    endif    
    return FALSE
endfunction

private function KnockbackGroup takes nothing returns nothing 
    local data d = GetTimerData(GetExpiredTimer())
    local unit u = GetEnumUnit()
    local real dx = 0.
    local real dy = 0.
    
    set dx = PolarProjectionX(GetUnitX(u), d.v, d.angle)
    set dy = PolarProjectionY(GetUnitY(u), d.v, d.angle)     
   
    if IsTerrainWalkable(dx, dy) then
        call SetUnitX(u, dx)
        call SetUnitY(u, dy)
    endif
    
endfunction
 
private function Callback takes nothing returns nothing 
    local timer myTimer = GetExpiredTimer()
    local data d = GetTimerData(myTimer)
    local group g = NewGroup() // Check to see if nearby units can be added to the knockback
    local boolexpr filter = Condition(function Filt)
    local unit target = null
    
    set d.v = d.v*0.925
    
    // Enum and add new units to the knockback group
    call GroupEnumUnitsInRange(g, GetUnitX(d.target), GetUnitY(d.target), 125, filter)   
     // Knockback the entire group
    call ForGroup(d.knockGroup, function KnockbackGroup)
    
    // Cleanup filter and group
    call ReleaseGroup(g)    
    call DestroyBoolExpr(filter)
    
    if d.v < 5 then       
        loop
            set target = FirstOfGroup(d.knockGroup)
            exitwhen target == null 
            call UnitRemoveAbility(target, 'A00O')
            call GroupRemoveUnit(d.knockGroup, target)
        endloop

        call ReleaseGroup(d.knockGroup) 
        call d.destroy()
        call PauseTimer(myTimer)
        call ReleaseTimer(myTimer)
    endif    
endfunction

function Trig_Cleave_Actions takes nothing returns nothing
    local timer myTimer = NewTimer()
    local unit u = GetTriggerUnit()
    local unit t = GetSpellTargetUnit()
    local real amount = 50+GetHeroLevel(u)*10+GetUnitStat(u, ATTACK_POWER)*0.4*BarbarianDamageModifier(u,t)
    local boolexpr filter = Condition(function Filt)    
    local data d = data.create()
    // Healing variables
    local real healSkill = GetPlayerTechCount(GetOwningPlayer(u), 'R00I', true)*0.05
    local real healAmount = 0.
    // tempVariables
    call GroupClear(tempGroup)
    set tempPlayer = GetOwningPlayer(u)
    
    set d.caster = u
    set d.target = t
    set d.knockGroup = NewGroup()
    set d.angle = AngleBetweenPointsXY(GetUnitX(u), GetUnitY(u), GetUnitX(t), GetUnitY(t))
    set d.v = 40.
    
    call GroupAddUnit(d.knockGroup, t)
    call UnitAddAbility(t, 'A00O')
    
    if healSkill > 0 then
        set healSkill = healSkill + 0.1
        set healAmount = amount*healSkill
        call HealUnit(u, u, healAmount)
    endif      
    
    call UnitDamage(u, t, PHYSICAL, amount)
    
    call SetTimerData(myTimer, d)
    call TimerStart(myTimer, 0.03, true, function Callback)
endfunction

//===========================================================================
function InitTrig_Cleave takes nothing returns nothing
    set gg_trg_Cleave = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventNL( gg_trg_Cleave, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Cleave, Condition( function Trig_Cleave_Conditions ) )
    call TriggerAddAction( gg_trg_Cleave, function Trig_Cleave_Actions )
endfunction
endscope


JASS:
function BarbarianDamageModifier takes unit u, unit t returns real
    local player p = GetOwningPlayer(u)
    local real mod = GetPlayerTechCount(GetOwningPlayer(u), 'R00G', true)*0.05
    local real targetLifePercent = GetUnitState(u, UNIT_STATE_LIFE)/GetUnitState(u, UNIT_STATE_MAX_LIFE)*100
    if targetLifePercent <= 30 and mod > 0 then
        set mod = mod+1
    else
        set mod = 1
    endif    
    return mod
endfunction
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
You sure that it's PolarProjection that isn't working? Maybe the angle passed to PolarProjection is wrong.

I'm going to take a wild guess and say you are not properly destroying your structs, and then shit gets cash when you hit the 8191 struct instance limit. It would explain how it only happens after a certain amount of time - but I don't see how it's possible, cause I think you are properly destroying your structs.

Try checking how many struct instances you have when it bugs up. Does vJASS automatically complain about this if you are in debug mode?
 

overload119

New Member
Reaction score
5
Thanks for the reply.

I've tested the PolarProjection angles, they are performing correctly. (All fields are received properly by the function and returned correctly as well).

I am now also thinking that structs issue could be it. But like you said, I thought I'm recycling structs just fine. I tested this theory anyway, by casting Cleave around 90 times (which is far more than the amount of cleaves that can occur in a real-game anyway **since I only do 10 minute tests on BNet which is when the bug occurs in the first place**) So, the structs off the Cleave function should not be the issue unless the struct limit is 8190 for ALL structs, of all types. But I remember this is not the case. Its 8190 structs for each struct type, correct?
 

weaaddar

New Member
Reaction score
6
Its 8190 for structs of the same inheritance. But if you use array members you cut down the number of elements.

Like for instance if your cleave struct had an array 10 of integers, you now can only instance 818 times [because the 0th struct takes array slice spot of 0-10, and each member needs 10 array spots].
 

Rushhour

New Member
Reaction score
46
Yeah its 8190 per struct, not in total^^
JASS:
       call PauseTimer(myTimer)
        call ReleaseTimer(myTimer)

why this? Look at the ReleaseTimer function: it pauses the timer itself^^

And why do you pass the filter for the enumeration as a local boolexpression ? Just inline it GroupEnumUnitsInRange(g,x,y,z,Condition(function MyFilter)) . This won't leak the bool expression, as far as I know the last patch also fixed the null boolexpression leak.

Another thing: Are you sure that only ForGroup() passes the expired timer to the function KnockbackGroup ? Why don't you just use one global dummygroup for those instant enumerations (never really add units to it) and do all your actions from the ForGroup() in the Filter function.

This would get you rid of the GroupUtilis. I guess Timer or GroupUtils run out of timers/groups, maybe you forgot ReleaseTimer() in another trigger?
 

overload119

New Member
Reaction score
5
Yeah its 8190 per struct, not in total^^
JASS:
       call PauseTimer(myTimer)
        call ReleaseTimer(myTimer)

why this? Look at the ReleaseTimer function: it pauses the timer itself^^

And why do you pass the filter for the enumeration as a local boolexpression ? Just inline it GroupEnumUnitsInRange(g,x,y,z,Condition(function MyFilter)) . This won't leak the bool expression, as far as I know the last patch also fixed the null boolexpression leak.

Another thing: Are you sure that only ForGroup() passes the expired timer to the function KnockbackGroup ? Why don't you just use one global dummygroup for those instant enumerations (never really add units to it) and do all your actions from the ForGroup() in the Filter function.

This would get you rid of the GroupUtilis. I guess Timer or GroupUtils run out of timers/groups, maybe you forgot ReleaseTimer() in another trigger?

I keep the boolexpr local, so I can destroy it later.
call DestroyBoolExpr(filter)
The latest patch fixed boolexpr that are set to null after being destroyed making unrelated code fail.

I updated the group code, so it does use a global group now.

EDIT: I read somewhere (since I'm using the fastest version of timerUtils) that you have to pause timers before releasing them... But you are right that it doesn't make sense.
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
1) Use tick instead of reals.

JASS:
    local boolexpr filter = Condition(function Filt)

2) Remove all of these and replace them with a globals.

JASS:
    call GroupClear(tempGroup)

3) It will be cleaned when an enum is called.

4) Your locals are leaking. Use struct to store them or null them in end of the function.

JASS:
    local group g = NewGroup()

5) A global group is always faster.

JASS:
    if IsTerrainWalkable(dx, dy) then

6) Remove it.

JASS:
    local data d = GetTimerData(GetExpiredTimer())

7) Use a globals to pass data to it.

I read somewhere (since I'm using the fastest version of timerUtils) that you have to pause timers before releasing them... But you are right that it doesn't make sense.
8) TimerUtils will do the works for you.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top