Viable single-timer spell functions?

LurkerAspect

Now officially a Super Lurker
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 - 'A00T'
//==========================================================
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, 'A00T'))
                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, 'A00T')
    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() == 'A00T'
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.
  • C Cherry.Grove:
    So what's the whole reforged situation? Do we have to run the classic version bootleg now?
  • C Cherry.Grove:
    My boyfriend is super into Bethesda modding so I'm probably going to join him in that for a bit, then move on to making mobile/browser games.
  • C Cherry.Grove:
    I mostly just want to look at my old projects because I know I posted uploads on here somewhere.
  • C Cherry.Grove:
    My one game was basically Among Us but you sabotaged an entire medieval city simulated drastically inefficiently with dynamic NPCs :S
  • C Cherry.Grove:
    of course I never finished it x D
  • jonas jonas:
    xD
  • jonas jonas:
    I think you can still run the original game, just there's a chance your map won't work with the newer patches
  • jonas jonas:
    what development framework have you been looking at for mobile development?
  • jonas jonas:
    I'm currently creating (as a hobby) a space invaders like game for mobile using Xamarin, after starting a few larger projects that I also may not have finished :rolleyes::p
  • The Helper The Helper:
    Awesome! you should look at the Atari VCS platform it does not have a huge user base but the one it has is super active at around 11k and they have no games in there store
  • The Helper The Helper:
    basically it is linux
  • The Helper The Helper:
    OMG it is BanLord as Blackveiled on the forum
  • The Helper The Helper:
    what is up buddy
  • jonas jonas:
    thanks for the tip, that sounds cool. Never heard about the VCS before but seems like it should be a good match
  • tom_mai78101 tom_mai78101:
    The winter vacation left a toll on me. I missed staying up late and sleeping in late.
  • C Cherry.Grove:
    i haven't looked into it yet
  • C Cherry.Grove:
    If I could use something Python compatible it would save me some learning
  • C Cherry.Grove:
    But Unity might be easiest
  • C Cherry.Grove:
    looking at old posts here makes me happy. Makes me be all like "wow I was pretty smart in 2009"
  • C Cherry.Grove:
    mostly failing at life through adulthood really demotivated me for a while
  • C Cherry.Grove:
    ....what I don't get is why the fuck they would force us to permanently convert to a version of the game everyone fucking hated : \
  • C Cherry.Grove:
    Raid Shadow Legends texture pack smh
  • jonas jonas:
    Most people fail at life throughout their adulthood. Sometimes we get to learn from it. Usually it just feels like crap.
  • The Helper The Helper:
    What does not kill you makes you stronger is what they say

    Staff online

    Members online

    Affiliates

    Hive Workshop NUON Dome
    Top