Struct arrays and knockback problems

Jonnycakes

New Member
Reaction score
6
Hi, I am working on (what I think is) a fairly complex spell. It runs, but there are still a couple of things that aren't working quite right and I am not sure why. I will list the problems in a list:

1. Every time multiple instances of the spell are cast, I get the debug message, "Double free of type: deluge__data". I don't know why-I thought I structured the code that gets rid of unused slots in the struct array for data (d).

2. The same thing happens when multiple units are hit with the spell. "Double free of type: deluge__units." I think this is related to number 1, as I structured it the same way.

3. The knockback for units isn't working how I want it to. It seems like units hit by the spell aren't being knocked back every time the timer callback function is called (every .04 seconds). Also, units get stunned when they shouldn't-I think this is either because they aren't getting paused when they should, so they move while being knocked back screwing up the condition that checks if they have actually been moved, or because said condition is triggering when it shouldn't.

Here is my code-please point out any errors you see, related or not:
JASS:
scope deluge initializer init


private struct data //struct for spell instance

    unit caster
    real x
    real y
    real angle
    real r
    integer level
    player p
    group gr
    
endstruct

private struct units //struct for unit hit by the spell

    unit u
    real r
    real x
    real y
    real angle
    
endstruct


globals

    private timer time=CreateTimer()
    private integer ins=0 //instances of the spell
    private integer inu=0 //instances of units being knocked back by the spell
    private data array d
    private units array e
    
endglobals


private function conditions takes nothing returns boolean

    return GetSpellAbilityId()=='A00R'
    
endfunction


private function filter takes nothing returns boolean

    return true
    
endfunction


private function trees takes nothing returns nothing
    
    call KillDestructable(GetEnumDestructable())
    
endfunction


private function looper takes nothing returns nothing
    local integer i=1
    local group g=CreateGroup()
    local unit u
    local rect rct
    local real x
    local real y
    
    loop
        exitwhen i>ins 
        if d<i>.r&lt;0 then //if the spell is over, set instances minus one and restructure the array-here is one problem
            call d<i>.destroy()
            if ins&gt;1 and i!=ins then
                set d<i>=d[ins]
                call d[ins].destroy()
                set i=i-1
            endif
            set ins=ins-1
        else
            set d<i>.x=d<i>.x+50.*Cos(d<i>.angle)  //spell actions-moving spell, getting units hit by it, killing trees
            set d<i>.y=d<i>.y+50.*Sin(d<i>.angle) 
            call DestroyEffect(AddSpecialEffect(&quot;Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl&quot;, d<i>.x, d<i>.y))
            set d<i>.r=d<i>.r-.04
            call GroupEnumUnitsInRange(g, d<i>.x, d<i>.y, 100., Filter(function filter))
            loop
                set u=FirstOfGroup(g)
                exitwhen u==null
                call GroupRemoveUnit(g, u)
                if not IsUnitAlly(u, d<i>.p) and not IsUnitInGroup(u, d<i>.gr) then //this sets up a struct for a unit affected by the spell
                    set inu=inu+1
                    set e[inu]=units.create()
                    set e[inu].u=u
                    set e[inu].x=GetUnitX(u)
                    set e[inu].y=GetUnitY(u)
                    set e[inu].r=1.
                    set e[inu].angle=Atan2(e[inu].y-d<i>.y, e[inu].x-d<i>.x)
                    call UnitDamageTarget(d<i>.caster, u, I2R(d<i>.level)*50.+100, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
                    call DestroyEffect(AddSpecialEffect(&quot;&quot;, d<i>.x, d<i>.y))
                    call GroupAddUnit(d<i>.gr, u)
                endif
            endloop
            set rct=Rect(d<i>.x-100., d<i>.y-100., d<i>.x+100., d<i>.y+100.) //kills trees
            call EnumDestructablesInRect(rct, Filter(function filter), function trees) 
            call RemoveRect(rct)
        endif
        set i=i+1
    endloop
    
    set i=1
    loop
        exitwhen i&gt;inu
        if e<i>.r&lt;0 then //this is for getting rid of unused instances of the units struct
            call PauseUnit(e<i>.u, false)
            call e<i>.destroy()
            if inu&gt;1 and i!=inu then
                set e<i>=e[inu]
                call e[inu].destroy()
                set i=i-1
            endif
            set inu=inu-1
        else
            set x=e<i>.x //actions for units-knockback and pause
            set y=e<i>.y
            set e<i>.x=e<i>.x+40.*Cos(e<i>.angle)
            set e<i>.y=e<i>.y+40.*Sin(e<i>.angle) 
            call DestroyEffect(AddSpecialEffect(&quot;Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdl&quot;, e<i>.x, e<i>.y))
            call SetUnitPosition(e<i>.u, e<i>.x, e<i>.y)
            set x=GetUnitX(e<i>.u)-e<i>.x
            set y=GetUnitY(e<i>.u)-e<i>.y
            if SquareRoot(x*x+y*y)&lt;10. then //stuns, ends knockback, and unpauses if a unit hits something
                set e<i>.r=-1.
                set u=CreateUnit(Player(8), &#039;h009&#039;, e<i>.x, e<i>.y, 0.)
                call UnitApplyTimedLife(u, &#039;Bhwd&#039;, 2.)
                call IssueTargetOrder(u, &quot;impale&quot;, e<i>.u)
            else
                call PauseUnit(e<i>.u, true)
                set e<i>.r=e<i>.r-.04
            endif
        endif
        set i=i+1
    endloop
    
    if ins+inu==0 then
        call PauseTimer(time)
    endif
    
    set g=null
    set u=null
    set rct=null

endfunction


private function actions takes nothing returns nothing

    local data this=data.create()
    local location l=GetSpellTargetLoc()
    local real x=GetLocationX(l)
    local real y=GetLocationY(l)
    call RemoveLocation(l)
    set l=null
    set this.caster=GetTriggerUnit()
    set this.x=GetUnitX(this.caster)
    set this.y=GetUnitY(this.caster)
    set this.angle=Atan2(y-this.y, x-this.x)
    set this.p=GetOwningPlayer(this.caster)
    set this.level=GetUnitAbilityLevel(this.caster, &#039;A00R&#039;)
    set this.r=I2R(this.level)*0.+1.8
    set this.gr=CreateGroup()
    set ins=ins+1
    set d[ins]=this
    if ins==1 and inu==0 then
        call TimerStart(time, .04, true, function looper)
    endif
    
endfunction


//===========================================================================
private function init takes nothing returns nothing

    local trigger t=CreateTrigger()
    local integer i=0
    loop
        call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        set i=i+1
        exitwhen i==8
    endloop
    call TriggerAddCondition(t, Condition(function conditions))
    call TriggerAddAction(t, function actions)
    
endfunction


endscope</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>
 

ertaboy356b

Old School Gamer
Reaction score
86
Hi, you should learn how to use Systems such as KeyTimers and TimerUtils.. This is pretty easy actually.. I think I made something a while back.. I have the map I am working on now. I will upload it so you can look..

Here's the trigger I made:

JASS:

scope DKKnock initializer Trig

    globals
        private trigger Trigger = CreateTrigger()
        private constant integer ABID = &#039;A00N&#039;
        private constant real Chance = 15
        private constant real BaseDamage = 25
        private constant real Increment = 20
        private constant real Range = 250
        public integer DS = 0
        public integer array Off
        public unit array DS_Target
    endglobals
    
    private struct data
        unit u
        unit target
        integer ticks
        effect e
        real angle
    endstruct
    
    private function Move takes nothing returns boolean
        local data d = data(KT_GetData())
        local real x = GetUnitX(d.target) + 10 * Cos(d.angle * bj_DEGTORAD)
        local real y = GetUnitY(d.target) + 10 * Sin(d.angle * bj_DEGTORAD)
        call SetUnitX(d.target,x)
        call SetUnitY(d.target,y)
        set d.e = AddSpecialEffect(&quot;Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl&quot;,x,y)
        call DestroyEffect(d.e)
        set d.ticks = d.ticks - 1
        if d.ticks == 0 or GetWidgetLife(d.target) &lt; 1 then
            call PauseUnit(d.target,false)
            set d.u = null
            set d.target = null
            set d.e = null
            call d.destroy()
            return true
        endif
        return false
    endfunction
    
    private function Act takes nothing returns nothing
        local data d = data.create()
        set d.u = GetAttacker()
        set d.target = GetTriggerUnit()
        set d.ticks = R2I(Range / 10)
        set d.angle = AngleBetweenXY(GetUnitX(d.u),GetUnitY(d.u),GetUnitX(d.target),GetUnitY(d.target))
        call PauseUnit(d.target,true)
        call UnitDamageTarget(d.u,d.target,BaseDamage + (GetUnitAbilityLevel(d.u,ABID)* Increment),true,false,ATTACK_TYPE_HERO,DAMAGE_TYPE_MAGIC,WEAPON_TYPE_WHOKNOWS)
        call KT_Add(function Move,d,0.03)
        if DS == 1 then
            set DS = 0
        endif
        if DS_Target[GetUnitIndex(d.u)] != null then
            set DS_Target[GetUnitIndex(d.u)] = null
        endif
    endfunction
    
    private function Cond takes nothing returns boolean
        return Off[GetUnitIndex(GetAttacker())] != 1 and GetUnitAbilityLevel(GetAttacker(),ABID) &gt; 0 and (GetRandomReal(0,100) &lt;= Chance or (DS == 1 and DS_Target[GetUnitIndex(GetAttacker())] == GetTriggerUnit()))
    endfunction

    private function Trig takes nothing returns nothing
        call TriggerRegisterAnyUnitEventBJ(Trigger,EVENT_PLAYER_UNIT_ATTACKED)
        call TriggerAddAction(Trigger,function Act)
        call TriggerAddCondition(Trigger, Condition(function Cond))
    endfunction

endscope


This trigger is using KeyTimers and PUI.. The triggers are in the map..
 

Attachments

  • map.w3x
    417.8 KB · Views: 136

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
This trigger is using KeyTimers and PUI.. The triggers are in the map..
You are not using PUI properly.
 

Jonnycakes

New Member
Reaction score
6
Hi, you should learn how to use Systems such as KeyTimers and TimerUtils.. This is pretty easy actually.. I think I made something a while back.. I have the map I am working on now. I will upload it so you can look..

That's all fine and good, and I may eventually choose to use those systems, but what is wrong with my trigger? Those systems would solve the double clear for the struct arrays, but I don't think those systems will help with the odd knockback from my spell. And I still want to know what is wrong regardless. Is it wrong not to use those systems? What if I want to get this trigger right before I look into using them (I do)?
 

ertaboy356b

Old School Gamer
Reaction score
86
This trigger is using KeyTimers and PUI.. The triggers are in the map..
You are not using PUI properly.

How should I use PUI then?

@ TOPIC

for 1 and 2, I think using a global struct is a bad idea.. How about using a local struct instead, then destroying them after the spell is done.. Since variables are getting reused a lot, some variables might have been overridden by the other when multiple instances of the spell is executed..

So for example

if we have these:

JASS:
struct data
   unit u
endstruct

globals
   data MyData
endglobals

function Action takes nothing returns nothing
   set MyData.u = GetTriggerUnit()
endfunction


This is a global struct.. So everytime the function "Action" is called, MyData.u gets overridden.. So for multiple instances of the spell that happens alot, it would get overriden alot making glitches in the process.
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
How should I use PUI then?
JASS:
struct data
//! runtextmacro PUI()
endstruct

JASS:
set data[whichUnit] = &lt;vars&gt; //Attach struct.
data[whichUnit]//return attached struct.
&lt;vars&gt;.release//Remove attached struct.
 

Jonnycakes

New Member
Reaction score
6
How should I use PUI then?

@ TOPIC

for 1 and 2, I think using a global struct is a bad idea.. How about using a local struct instead, then destroying them after the spell is done.. Since variables are getting reused a lot, some variables might have been overridden by the other when multiple instances of the spell is executed..

So for example

if we have these:

JASS:
struct data
   unit u
endstruct

globals
   data MyData
endglobals

function Action takes nothing returns nothing
   set MyData.u = GetTriggerUnit()
endfunction


This is a global struct.. So everytime the function "Action" is called, MyData.u gets overridden.. So for multiple instances of the spell that happens alot, it would get overriden alot making glitches in the process.

Ah, but if you pay attention the global structs that I am using are arrays, and should be properly indexed so as to not be overwritten. Each instance of the spell is supposed to create a new index in the array, each of which gets looped through for each instance of the spell going on. Like I said, it works fine for single instances of the spell (and I think it even works with multiple instances), but I get a "double clear" error message.

EDIT: Also, how would I use local structs to pass data between functions? With a timer callback, don't the structs NEED to be global?
 

ertaboy356b

Old School Gamer
Reaction score
86
If you use KeyTimers system, you can do this:

JASS:

struct data
   unit u
endstruct

function TimerFunc takes nothing returns boolean
   local data d = data(KT_GetData()) // this will get the data from the previous function
   call KillUnit(d.u)
   call d.destroy() // Don&#039;t forget to destroy the local struct when the timer is about to end.
   return true // If you return true, the timer will end, if false, the timer will recall itself after the timeout
endfunction

function Act takes nothing returns nothing
    local data d = data.create()
    set d.u = GetTriggerUnit()
    call KT_Add(function TimerFunc, d, 0.03) // This is a KT Function, arguments are &quot;function, struct, timeout&quot;
endfunction
 

Jonnycakes

New Member
Reaction score
6
Understood, but what about without KeyTimers? Thanks for trying to help, but we both just keep saying the same things over and over. The solution will probably come to me some time in the future.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top