HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Steps To Debug A Map

01-02-2010, 02:54 PM#1
thehellman
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.

EDIT: I'll post the spell code when I get home.
01-02-2010, 03:00 PM#2
DioD
there is no need to debug function that working.
if function worked one time its OK.

if function fail at random time, you shoud debug its params only.
01-02-2010, 03:02 PM#3
weaaddar
DioD that statement maybe true of pure stateless functions, but Jass has mutable state, so it can be that the act of calling a function simply means you can't call it again.
01-02-2010, 03:12 PM#4
DioD
jass execute one function at time, one instance cannot affect other (expect sleeps and events)
01-02-2010, 03:30 PM#5
weaaddar
Collapse Zinc:
library
{
      static boolean called = false;
      function DoSomething()
      {
            if(called) return;
            called = true;
            /*something here */
      }

A function can be affected by mutable state. Bad code can break something that worked once, on the second or third or nth call. Reading over the comment it sounds like your not recycling your vectors or whatever so you are running out and so all these functions just stop working.
01-02-2010, 06:35 PM#6
thehellman
So here is the code.

Functions used by the scope:
Collapse 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

The actual spell code:
Collapse JASS:
scope Cleave

private struct data
    unit target = null
    unit caster = null
    group g     = null
    real v      = 0.  
    real angle  = 0.
endstruct

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

private function AddEffect takes unit u returns nothing
    call UnitAddAbility(u, 'A00O')
endfunction

private function AddEffectEx takes nothing returns nothing
    local data d = GetTimerData(GetExpiredTimer())    
    call UnitAddAbility(GetEnumUnit(), 'A00O')
    call GroupAddUnit(d.g, GetEnumUnit())
endfunction

private function RemoveEffectEx takes nothing returns nothing
    local data d = GetTimerData(GetExpiredTimer())    
    call UnitRemoveAbility(GetEnumUnit(), 'A00O')
endfunction

private function Filt takes nothing returns boolean 
    local data d = GetTimerData(GetExpiredTimer())
    return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(d.caster)) and GetWidgetLife(GetFilterUnit())>0.405 and (IsUnitInGroup(GetFilterUnit(), d.g)==FALSE) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)==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()
    local boolexpr filter = Condition(function Filt)
    
    set d.v = d.v*0.925
    
    call GroupEnumUnitsInRange(g, GetUnitX(d.target), GetUnitY(d.target), 125, filter)   
    call ForGroup(g, function AddEffectEx) // Add new units  
    call ForGroup(d.g, function KnockbackGroup) // Knockback the entire group

    call ReleaseGroup(g)    
    call DestroyBoolExpr(filter)
    
    if d.v < 5 then       
        call ForGroup(d.g, function RemoveEffectEx)
        call ReleaseGroup(d.g) 
        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.
    
    set d.caster = u
    set d.target = t
    set d.g = NewGroup()
    set d.angle = AngleBetweenPointsXY(GetUnitX(u), GetUnitY(u), GetUnitX(t), GetUnitY(t))
    set d.v = 40.
    
    call GroupAddUnit(d.g, t)

    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 AddEffect(t)
    
    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

This line is the same as the BJ except it doesn't leak the null.
Collapse JASS:
call TriggerRegisterAnyUnitEventNL( gg_trg_Cleave, EVENT_PLAYER_UNIT_SPELL_EFFECT )

EDIT: Some other functions I forgot. (These should have nothing to do with the spell not working)
UnitDamage - This function is just a custom damage thingie for my damage system
HealUnit - Custom function for my healing system
BarbarianDamageModifier might be useful

Collapse 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

Lastly I want to add, that this isn't the only spell that uses knockback, there is one other spell that also knockbacks. Once this stops working, the knockback on the other spell will also stop working.
01-02-2010, 10:32 PM#7
thehellman
Quote:
Originally Posted by weaaddar
Collapse Zinc:
library
{
      static boolean called = false;
      function DoSomething()
      {
            if(called) return;
            called = true;
            /*something here */
      }

A function can be affected by mutable state. Bad code can break something that worked once, on the second or third or nth call. Reading over the comment it sounds like your not recycling your vectors or whatever so you are running out and so all these functions just stop working.

Can you please go into more detail about this? I'm confused on what a mutable state is.
01-03-2010, 01:05 AM#8
akolyt0r
TriggerRegisterSomething(... null) does NOT leak ...
and you should use a global variable for your filter

and using GetTimerData in ForGroup calls and Filter is quite ugly ...

too tired to look deeper into your code.
01-03-2010, 03:46 AM#9
thehellman
Quote:
Originally Posted by akolyt0r
TriggerRegisterSomething(... null) does NOT leak ...
and you should use a global variable for your filter

and using GetTimerData in ForGroup calls and Filter is quite ugly ...

too tired to look deeper into your code.

Thanks for the post.

TriggerRegisterSomething does leak, it leaks a null, so it has to be replaced with a global BOOLEXPR = TRUE (this is one of the reason BoolexprUtils was made by Vex)

What are some alternatives to using GetTimerData in ForGroup calls and Filters? I'm aware of the looping through a group, but that would make my code messier. But I'll try it since I have to try something.
01-03-2010, 05:39 AM#10
DioD
terrable code...

you create one struct per every call of any function and never destroy it.

33 structs per second * 8 function * number of units.


after 20th call you ran out of structs.
01-03-2010, 12:32 PM#11
thehellman
Quote:
Originally Posted by DioD
terrable code...

you create one struct per every call of any function and never destroy it.

33 structs per second * 8 function * number of units.


after 20th call you ran out of structs.

Thank you for looking over my code. But I'm still confused.
The struct is only created in the Actions functions, meaning once each time the spell is cast. It is then destroyed when the spell ends, in the timer callback. Where is your math coming from and how would I fix this?
01-03-2010, 01:04 PM#12
DioD
private function AddEffectEx takes nothing returns nothing
local data d = GetTimerData(GetExpiredTimer())
call UnitAddAbility(GetEnumUnit(), 'A00O')
call GroupAddUnit(d.g, GetEnumUnit())
endfunction
01-03-2010, 02:08 PM#13
thehellman
Quote:
Originally Posted by DioD
private function AddEffectEx takes nothing returns nothing
local data d = GetTimerData(GetExpiredTimer())
call UnitAddAbility(GetEnumUnit(), 'A00O')
call GroupAddUnit(d.g, GetEnumUnit())
endfunction

Yes, I'm getting it's handle.. not creating a new one. It's just assigning data to the struct that was created earlier on...?
01-03-2010, 02:57 PM#14
Themerion
Since you are already using vJASS, you should encapsulate the spell in scopes. Will make it easier to read ( => increases the chances that someone finds your bug).

And, also, you do THREE group loops every iteration? You need only do one...

Collapse JASS:
// Three
    local group g = NewGroup()
    local boolexpr filter = Condition(function Filt)
    
    call GroupEnumUnitsInRange(g, GetUnitX(d.target), GetUnitY(d.target), 125, filter)   
    call ForGroup(g, function AddEffectEx) // Add new units  
    call ForGroup(d.g, function KnockbackGroup) // Knockback the entire group

    call ReleaseGroup(g)    

Using a scope will allow you to declare this:
Collapse JASS:
globals
    private group tempGroup
endglobals

// And in the Init-function:
set tempGroup = CreateGroup()

Now you can do all the iterations in ONE loop by putting everything into the filter-function:
Collapse JASS:
private function Filt takes nothing returns boolean 
    local data d = GetTimerData(GetExpiredTimer())
    if IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(d.caster)) and GetWidgetLife(GetFilterUnit())>0.405 and (IsUnitInGroup(GetFilterUnit(), d.g)==FALSE) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL)==FALSE) then

   // Do the knockback and the effects...

    endif

    return false
endfunction

call GroupEnumUnitsBlah(tempGroup, filterFunction)
01-03-2010, 03:00 PM#15
thehellman
EDIT: removed quote, it's too long.

Ehh the spell code was always in a scope. But you did teach me some interesting ways of going about things, such as exceuting code in the filter. I'll try that, it will help efficiency, but I don't think it'll fix the strange error I'm getting. I'll let you know after I do it.