Viable single-timer spell functions?

LurkerAspect

Now officially a Super Lurker
Reaction score
118
Hi there! I'm back! (briefly)

This time I'm concerned about my new coding method I started using since I saw it in an older spell. I can create callback struct spells and functions without the need for struct attachment systems like TimerUtils or ABC.

However, since I've started using this system, my games have been throwing out the weirdest errors and random code meltdowns (everything that could go wrong goes wrong, and things that used to work perfectly suddenly freak out).

If you have some spare time on your hands, could you please let me know if it's this coding method, or if it's something else I'm doing in this code?

JASS:
//ADEFLW_26_111111_BLACKHOLE
scope BlackHole initializer init
 
private keyword BLACK
 
globals
    private constant integer ABILCODE = 'A026'
    private constant integer DUMMY_ID = 'n018'
    private constant real SPEED = 1000
    private constant real SUCK_SPEED = 1000
    private constant real AREA = 400
    private constant real KILL_AREA = 50
    
    private integer I = 0
    private BLACK array DATA
    private timer TIMER = CreateTimer()
endglobals
 
private struct BLACK
    unit caster
    unit dummy
    real tX
    real tY
    static thistype tempDATA
    
    method destroy takes nothing returns nothing
        set .caster = null
        call KillUnit(.dummy)
        set .dummy = null
        set .tX = 0
        set .tY = 0
        call .deallocate()
    endmethod
    
    static method create takes unit caster, real tX, real tY returns BLACK
        local BLACK a = BLACK.allocate()
        
        set a.caster= caster
        set a.dummy = CreateUnit(NEUTRAL_PASSIVE,DUMMY_ID,GetUnitX(caster),GetUnitY(caster),0)
        set a.tX = tX
        set a.tY = tY
        
        return a
    endmethod
    
    static method SuckUnits takes nothing returns boolean
        local thistype this = thistype.tempDATA
        local unit u = GetFilterUnit()
        local real angle 
        local real distance
        if CheckTarget(.caster,u) then
            set angle = Atan2(GetUnitY(.dummy)-GetUnitY(u),GetUnitX(.dummy)-GetUnitX(u))
            set distance = AREA-DistanceUnits(.dummy,u)
            call SetUnitX(u,GetUnitX(u)+(distance/AREA)*SUCK_SPEED*INTERVAL*Cos(angle))
            call SetUnitY(u,GetUnitY(u)+(distance/AREA)*SUCK_SPEED*INTERVAL*Sin(angle))
            if DistanceUnits(.dummy,u) <= KILL_AREA then
                set PLAYER_DEATH_MESSAGE[GetPlayerId(GetOwningPlayer(u))] = "Sucked into a black hole"
                call UnitDamageTarget(.caster,u,10000,false,false,ATTACK_TYPE_CHAOS,DAMAGE_TYPE_UNIVERSAL,null)
            endif
        endif
        set u = null
        return false
    endmethod
    
    method Reposition takes real tX, real tY returns nothing
        set .tX = tX
        set .tY = tY
    endmethod
    
    method Tick takes nothing returns nothing
        local real X = GetUnitX(.dummy)
        local real Y = GetUnitY(.dummy)
        local real angle = Atan2(.tY-Y,.tX-X)
        local real distance = DistancePoints(X,Y,.tX,.tY)
        
        if distance > KILL_AREA then
            call SetUnitPosition(.dummy,X+SPEED*INTERVAL*Cos(angle),Y+SPEED*INTERVAL*Sin(angle))
        endif
        set thistype.tempDATA = this
        call GroupEnumUnitsInRange(GROUP,X,Y,AREA,Filter(function thistype.SuckUnits))
    endmethod
        
endstruct
 
private function callback takes nothing returns nothing
    local BLACK a
    local integer i = 1
    
    loop
        exitwhen i > I
        set a = DATA<i>
        if GetWidgetLife(a.caster) &lt;= 0 or GetUnitAbilityLevel(a.caster,ABILCODE) &lt;= 0 then
            call a.destroy()
            set DATA<i> = DATA<i>
            set I = I-1
            set i=i-1
        else
            call a.Tick()
        endif
        set i = i+1
    endloop
    
    if I == 0 then
        call PauseTimer(TIMER)
    endif
endfunction
 
private function Actions takes nothing returns nothing
    local integer i = 1
    local boolean end = false
    local BLACK a
    
    loop
        exitwhen i &gt; I or end
        set a = DATA<i>
        if GetTriggerUnit() == a.caster then
            call a.Reposition(GetSpellTargetX(),GetSpellTargetY())
            set end = true
        endif
        set i = i+1
    endloop
    
    if end == false then
        set a = BLACK.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
        set I = I+1
        set DATA<i> = a
    endif
    
    if I == 1 then
        call TimerStart(TIMER,INTERVAL,true,function callback)
    endif
endfunction
 
private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == ABILCODE
endfunction
 
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local integer i = 0
    call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CHANNEL)
    call TriggerAddCondition(t,Condition(function Conditions))
    call TriggerAddAction(t,function Actions)
    set t = null
    call DestroyTrigger(t)
endfunction
 
endscope</i></i></i></i></i>

So that's my new coding system. To save your time, the suspected culprits are:
- method destroy (I've never known the proper syntax for destroying structs)
- .allocate() & .deallocate() (same reasoning as above)
- using an empty global GROUP & static method SuckUnits (tip I received ages ago for leakless groups)

Understand that almost every spell designed for multiple simultaneous instances has been coded this way, including a periodic AI system and over 60 spells. It's at a point where I spend a month coding and then the game I've made is unplayable.

For comparison, here's an older code from an older game that always works perfectly (using Cohadar's ABC timer system):
JASS:
//==========================================================
//Ball Lightning - &#039;A00T&#039;
//==========================================================
scope EnergyWave initializer init
 
globals
    private group EW_damagedgroup
    private unit EW_tempunit
endglobals
 
private struct EWstruct
    unit caster
    real vertexes
    real distance
    real maxdist
    method onDestroy takes nothing returns nothing
        set .caster = null
        set .vertexes = 0
        set .distance = 0
        set .maxdist = 0
    endmethod
endstruct
 
private function EWGC takes nothing returns boolean
    if IsUnitInGroup(GetFilterUnit(), EW_damagedgroup) == true then
        return false
    elseif IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(EW_tempunit)) == false then
        return false
    elseif IsUnitAliveBJ(GetFilterUnit()) == false then
        return false
    elseif IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) then
        return false
    elseif IsUnitOwnedByPlayer(GetFilterUnit(), Player(PLAYER_NEUTRAL_PASSIVE)) == true or IsUnitOwnedByPlayer(GetFilterUnit(), Player(PLAYER_NEUTRAL_AGGRESSIVE)) then
        return true
    endif
    return true
endfunction
 
private function Damage takes unit caster, unit target, real dmg returns nothing
    call UnitDamageTarget(caster, target, dmg, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, WEAPON_TYPE_WHOKNOWS)
    call DestroyEffect(AddSpecialEffect(&quot;Abilities\\Weapons\\ChimaeraLightningMissile\\ChimaeraLightningMissile.mdl&quot;, GetUnitX(target), GetUnitY(target)))
endfunction
    
private function callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local EWstruct a = GetTimerStructA(t)
    local real uX = GetUnitX(a.caster)
    local real uY = GetUnitY(a.caster)
    local real offsetX = 0
    local real offsetY = 0
    local real anglediff = 0
    local real angle = 0
    local integer loopval = 0
    local group damagegroup
    local unit u
    if a.distance &lt; a.maxdist and IsUnitAliveBJ(a.caster) == true then
        set anglediff = 360/a.vertexes
        loop
            exitwhen loopval &gt; a.vertexes
            set damagegroup = CreateGroup()
            set angle = loopval*anglediff
            set offsetX = uX+a.distance*Cos(angle*bj_DEGTORAD)
            set offsetY = uY+a.distance*Sin(angle*bj_DEGTORAD)
            set EW_tempunit = a.caster
            call GroupEnumUnitsInRange(damagegroup, offsetX, offsetY, 100, Filter(function EWGC))
            set EW_tempunit = null
            loop
                set u = FirstOfGroup(damagegroup)
                exitwhen u == null
                call Damage(a.caster, u, 50*GetUnitAbilityLevel(a.caster, &#039;A00T&#039;))
                call GroupAddUnit(EW_damagedgroup, u)
                call GroupRemoveUnit(damagegroup, u)
                set u = null
            endloop
            call DestroyGroup(damagegroup)
            call DestroyEffect(AddSpecialEffect(&quot;Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl&quot;, offsetX, offsetY))
            set loopval = loopval+1
        endloop
        set loopval = 0
        set a.vertexes = a.vertexes+3
        set a.distance = a.distance+75.00
    else
        call PauseTimer(t)
        call DestroyTimer(t)
        call a.destroy()
        call ClearTimerStructA(t)
        call DestroyGroup(EW_damagedgroup)
    endif
    set u = null
endfunction
            
private function EWA takes nothing returns nothing
    local timer t = CreateTimer()
    local EWstruct a = EWstruct.create()
    set EW_damagedgroup = CreateGroup()
    set a.caster = GetTriggerUnit()
    set a.vertexes = 3
    set a.distance = 50
    set a.maxdist = 300+100*GetUnitAbilityLevel(a.caster, &#039;A00T&#039;)
    call SetTimerStructA(t, a)
    call TimerStart(t, 0.05, true, function callback)    
    set t = null
endfunction
 
private function EWC takes nothing returns boolean
    return GetSpellAbilityId() == &#039;A00T&#039;
endfunction
 
private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Filter(function EWC))
    call TriggerAddAction(t, function EWA)
endfunction
 
endscope

Any input is greatly appreciated!

PS: Just in case: if you're just checking out this post and like the spells I've posted, help yourself to the code credit-free, I don't really care about who uses my stuff! :D
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top