• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Techtree Contest #12 - Evolution

Status
Not open for further replies.
@Lord_Earthfire

Was your entry the Wellings? The author of the Wellings, you have a TriggerSleepAction in your triggers.

@Spellbound

The deadline is on July 31, 2018 GMT 00:00 (day included), right?

Update

I'll post the description of one unit:

Asymptrone
A highly durable scouting unit, capable of siphoning experience points from enemy Heroes and using them as tactical weapons. (Also evolves into different Heroes).

Health: 750 (Reduce to 400)
HP regen: 5/sec
Armor: 2
Armor type: Hero
Move speed: 325

Abilities:
Obtain metadata: Siphon experience points for 45 seconds or until casting sub-spells, dying or being removed. (Working)
Transmit Metadata: Grant experience points to an Allied Hero. (Sub-Spell)
Reload Metadata: Displays the total amount of experience points as a text tag.
Metadata Overload: (WIP) Targets an area, dealing damage to enemy units. Damage is reduced by number and determined by experience points.
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,013
I suggest Read Metadata over Reload, as that's something you usually do with it.

What's a TriggerSleepAction?
It's using Waits (GUI). He just means your code could be improved with timers and proper synchronous delays. In some cases it can cause parts of your trigger to fail or cause huge roadblocks to execution in loops, so it might or might not be a serious issue; I didn't look at your code.
 
The Asymptrone isn't a race, its' a part of a race I'm making (corrected to Artiform). Currently, it cannot reduce Hero levels (couldn't find the native for it yet), but it does drain experience points.

From there, you can choose to give it to Allied Heroes or use it offensively. When it dies, before the experience points expire, they will be returned to the original Hero.

As a consequence, I am making the Heroes weaker.
 
Level 39
Joined
Feb 27, 2007
Messages
5,013
Currently, it cannot reduce Hero levels (couldn't find the native for it yet), but it does drain experience points.
According to Kam's first post in the developer update thread, the xp-setting natives are currently unable to delevel a hero even if it goes below the prior level's xp threshold. Described as buggy behavior in the thread.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
On behalf of my team, we're requesting an extension. We started a month late, perhaps we set our standards too high or something but we're fairly certain we cannot make a polished entry in time.
Merely some rushed monstrosity.

Not sure if anyone else if anyone else needs it, but I am posting a few days ahead of time in hopes that someone does.
 
Level 7
Joined
Feb 10, 2016
Messages
59
On behalf of my team, we're requesting an extension. We started a month late, perhaps we set our standards too high or something but we're fairly certain we cannot make a polished entry in time.
Merely some rushed monstrosity.

It may not be very nice of me to say this, but as someone who got started earlier, it wouldn't feel very fair to me if you got a full-on extension, especially since you're in a team of 3 and I've been having to work on my own. Maybe a limited extension that came with a score penalty would be fair, and I do still want to see what sort of race Team Procrastinators comes up with, but I worked hard to get my race fully polished and debugged on time.

On a related note, I'll be putting up an update to the Shadefrost Nerubians in a minute here. This will probably be the final version for the contest, unless I get some really important feedback between now and the end date.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
Hence why I am requesting an extension, not demanding it.
It happens often however, the cinematic contest I placed second in had an extension of 3 days for one person to finish the entry.
Not saying that means that there should be extensions, just saying that hive is open to the option so we do not need to debate that at least.
 
I think that having more contests improves the quality of the contest, and we should strive not to force people to drop out, especially if they're already working on something. Given the standards you have, what do you see as the time you would need to finish? Should we have contestants vote on whether to extend the deadline? What is the standard operating procedure in this case?
 
Level 27
Joined
May 18, 2018
Messages
397
I have been really short of time, however here are some screenshots:

Wraiths1.PNG

Wraiths2.PNG

Wraiths3.PNG

Wraiths4.PNG

Wraiths5.PNG

It's about 85% ready, so I hope to finish soon.
 
I guess I should also be posting WIPs. Here's one:
Dreamspawn buildings need Figments to progress. Dreamcatchers, as well as the Dreamspawn town halls, will generate Figment automatically. A building in progress will always attempt to pick the nearest Figment in a 4500 range.

besides the floating spider, everything is not-final.
 
Level 7
Joined
Feb 10, 2016
Messages
59
As far as yours is concerned I was waiting until you posted the final draft, considering the post on page 8 was your 'first draft'. I thought you had additional updates coming.
My apologies. I had this post on page 11:
On a related note, I'll be putting up an update to the Shadefrost Nerubians in a minute here. This will probably be the final version for the contest, unless I get some really important feedback between now and the end date.
...which was meant to indicate that my post on page 8 was updated to have the final product. I guess I wasn't clear enough.
 
Level 7
Joined
Feb 10, 2016
Messages
59
the ability doesn't actually cast. It's only here for you to switch targeting parameters. The actual cooldown itself is based on the attack speed of the Soulspinner, which has potential to fluctuate.
If that's the case, wouldn't it make more sense to have that ability be based on Quill Spray? Having that parameter change based on whether or not the ability's on autocast is kind of confusing.
Also, you mentioned it being kind of OP at the moment, and you're right. I had to think about something similar for the Brood Queen in my race. Normally, the best way to nerf an ability like that is to increase the cooldown, possibly also increasing the duration of the summoned units. In your case, there are a couple of other nerfs that might be interesting. You might have a minor nerf where summoning the next pair of units destroys the first pair, or you might do a tactical nerf where, if one of the summoned units is actually killed by an opponent before it expires, the Soulspinner takes some damage too.
 
Level 7
Joined
Feb 10, 2016
Messages
59
Isn't Quill Spray an autocast?
...you're right! I had thought that it wasn't one, because its icons don't have the yellow corners, but when I double-checked in the World Editor, it turned out to be a tweaked copypaste of Orb of Annihilation. Oops. Immolation is definitely a toggle-ability, so that should work. It can be modified so that it doesn't need mana. You could also use a modified Defend, since that's a toggle-ability that doesn't take mana in the first place.
 
Finally, I finished the Aura System needed for some of the abilities I will implement. Here's the code:

JASS:
[hidden="Aura System"]
library AuraSystem /*

    */ requires /*
 
        --------------------------
        */ UnitDex              /*
        --------------------------
            #?  GroupUtils
            #?  WorldBounds
        
            -> TriggerHappy
        
            link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
        
        --------------------------
        */ ListT                /*
        --------------------------
            # Table
            # Alloc
        
            -> Bannar
        
            link: https://www.hiveworkshop.com/threads/containers-list-t.249011/
        
        --------------------------
        */ Eval                 /*
        --------------------------
            # Table
        
            -> MyPad
        
            link: https://www.hiveworkshop.com/threads/evaluate-code.307536/
        
        --------------------------
        */ AllocH               /*
        --------------------------
            # AllocT
        
            -> MyPad
        
        --------------------------
        */ Alloc                /*
        --------------------------
            -> Sevion
    */
 
//! runtextmacro DEFINE_LIST("private", "RealList", "real")

native UnitAlive takes unit id returns boolean

globals
    private unit eventUnit                = null
endglobals

function GetAuraEventUnit takes nothing returns unit
    return eventUnit
endfunction

private function RealToIndex takes real r returns integer
    return R2I(RMaxBJ(r, 0.0001) * 10000.)
endfunction

private keyword AuraM

private struct AuraUnitData extends array
    implement AllocH
 
    private static group currentGrp         = null
    private static unit comparator          = null
    private static real range               = 0.

    private static EvalCode callback        = 0
 
    private static Table lastGroupMap       = 0
 
    private static method onClearGroup takes nothing returns nothing
        local unit lastRemoved  = eventUnit
        local unit enumUnit     = GetEnumUnit()
    
        if not IsUnitInRange(enumUnit, thistype.comparator, thistype.range) then
            call GroupRemoveUnit(thistype.currentGrp, enumUnit)
        
            set eventUnit = enumUnit
            call thistype.callback.run()
            set eventUnit = lastRemoved
        endif
    
        set enumUnit        = null
        set lastRemoved     = null
    endmethod
 
    method destroy takes nothing returns nothing
        if thistype.lastGroupMap.group.has(this) then
            call DestroyGroup(thistype.lastGroupMap.group[this])
            call thistype.lastGroupMap.group.remove(this)
        
            call this.deallocate()
        debug else
            debug call BJDebugMsg("AuraUnitData: Double-free detected!\n" + /*
                              */  I2S(this))
        endif
    endmethod
 
    method operator group takes nothing returns group
        return thistype.lastGroupMap.group[this]
    endmethod
 
    method clearGroup takes unit comparator, real range, EvalCode callback returns nothing
        set thistype.currentGrp   = thistype.lastGroupMap.group[this]
        set thistype.comparator     = comparator
        set thistype.range          = range
        set thistype.callback       = callback
    
        call ForGroup(thistype.currentGrp, function thistype.onClearGroup)
    
        set thistype.currentGrp   = null
        set thistype.comparator     = null
        set thistype.range          = 0.
        set thistype.callback       = 0
    endmethod
 
    static method create takes nothing returns thistype
        local thistype result = thistype.allocate()
    
        set thistype.lastGroupMap.group[result] = CreateGroup()
    
        return result
    endmethod
 
    private static method init takes nothing returns nothing
        set thistype.lastGroupMap = Table.create()
    endmethod
 
    implement AuraM
endstruct

private struct AuraUnit extends array
    private boolean isAlloc
 
    readonly IntegerList auraList
    readonly Table       auraMap
    readonly Table       auraDataMap
 
    method destroy takes nothing returns nothing
        if not this.isAlloc then
            return
        endif
    
        call this.auraDataMap.destroy()
        call this.auraMap.destroy()
        call this.auraList.destroy()
    
        set this.isAlloc        = false
    
        set this.auraList       = 0
        set this.auraMap        = 0
        set this.auraDataMap    = 0
    endmethod
 
    static method operator [] takes unit whichUnit returns AuraUnit
        local AuraUnit result = AuraUnit(GetUnitId(whichUnit))
    
        if not result.isAlloc then
            set result.isAlloc      = true
            set result.auraList     = IntegerList.create()
            set result.auraMap      = Table.create()
            set result.auraDataMap  = Table.create()
        endif
        return result
    endmethod
 
    static method declare takes unit whichUnit, integer auraInstance returns AuraUnit
        local AuraUnit temp = AuraUnit[whichUnit]
    
        if not temp.auraMap.integer.has(auraInstance) then
            set temp.auraMap.integer[auraInstance] = temp.auraList.push(auraInstance).last
            set temp.auraDataMap.integer[auraInstance] = AuraUnitData.create()
        endif
    
        return temp
    endmethod
 
    static method strip takes unit whichUnit, integer auraInstance returns AuraUnit
        local AuraUnit temp         = AuraUnit(GetUnitId(whichUnit))
        local AuraUnitData tempData
    
        if not temp.isAlloc then
            set temp = 0
            return temp
        endif
    
        if temp.auraMap.integer.has(auraInstance) then
            set tempData = AuraUnitData(temp.auraDataMap.integer[auraInstance])
            call tempData.destroy()
        
            call temp.auraList.erase(IntegerListItem(temp.auraMap.integer[auraInstance]))
            call temp.auraMap.integer.remove(auraInstance)
            call temp.auraDataMap.integer.remove(auraInstance)
        endif
    
        return temp
    endmethod
endstruct

private struct AuraTimer extends array
    readonly static Table timerMap      = 0
    readonly static Table timerListMap  = 0
    readonly static Table timerTickMap  = 0
 
    method operator tick takes nothing returns integer
        return thistype.timerTickMap.integer[this]
    endmethod
 
    method operator tick= takes integer newTick returns nothing
        set thistype.timerTickMap.integer[this] = newTick
    endmethod
 
    method operator list takes nothing returns IntegerList
        return IntegerList(thistype.timerListMap.integer[this])
    endmethod
 
    method operator timer takes nothing returns timer
        return thistype.timerMap.timer[this]
    endmethod
 
    static method create takes real enumRate returns thistype
        local thistype result = RealToIndex(enumRate)
    
        if not thistype.timerMap.timer.has(result) then
            set thistype.timerMap.timer[result]         = CreateTimer()
            set thistype.timerListMap.integer[result]   = IntegerList.create()
        endif
        return result
    endmethod
 
    private static method init takes nothing returns nothing
        set thistype.timerMap       = Table.create()
        set thistype.timerListMap   = Table.create()
        set thistype.timerTickMap   = Table.create()
    endmethod
 
    implement AuraM
endstruct

struct Aura extends array
    implement Alloc
 
    private static unit footman         = null
    private static filterfunc onCheck   = null
    private static Table abilMap        = 0
 
    //  Global variables that can be accessed.
    readonly static Aura currentAura    = 0
 
    private IntegerListItem node
    private RealList range
 
    private group unitGroup
    private integer unitGroupCount
    private real enumRate
 
    readonly EvalCode onEnum
    readonly EvalCode onEnumRemove
 
    readonly integer abilId
 
    readonly group handlerGroup
 
    private method populate takes nothing returns nothing
        local integer curLevel
    
        call UnitAddAbility(Aura.footman, this.abilId)
        call this.range.push(0.)
    
        set curLevel = GetUnitAbilityLevel(Aura.footman, this.abilId)
    
        loop
            call SetUnitAbilityLevel(Aura.footman, this.abilId, curLevel + 1)
            exitwhen curLevel == GetUnitAbilityLevel(Aura.footman, this.abilId)
        
            call this.range.push(0.)
            set curLevel = GetUnitAbilityLevel(Aura.footman, this.abilId)
        endloop
    
        call UnitRemoveAbility(Aura.footman, this.abilId)
    endmethod
 
    private static method onCheckGroup takes nothing returns boolean
        call GroupAddUnit(bj_lastCreatedGroup, GetEnumUnit())
        return true
    endmethod
 
    private static method onLoop takes nothing returns nothing
        local AuraTimer auraTimer = AuraTimer.create(TimerGetTimeout(GetExpiredTimer()))
        local AuraUnitData auraData
        local Aura temp
        local IntegerListItem iter
    
        local group grp           = CreateGroup()
        local group lastGrpDest
        local group lastGrpCr
    
        local unit enum
        local unit lastEventUnit
        local integer level

        set iter = auraTimer.list.first
        loop
            exitwhen iter == 0
        
            set temp = Aura(iter.data)
            loop
                set enum = FirstOfGroup(temp.unitGroup)
                exitwhen enum == null
            
                call GroupRemoveUnit(temp.unitGroup, enum)
                call GroupAddUnit(grp, enum)
            
                if UnitAlive(enum) then
                    //  Do a lot of stuff...
                    set level    = GetUnitAbilityLevel(enum, temp.abilId)
                
                    set auraData = AuraUnitData(AuraUnit[enum].auraDataMap.integer[temp])
                    call auraData.clearGroup(enum, temp.range[level].data, temp.onEnumRemove)
                
                    set lastGrpDest          = bj_groupAddGroupDest
                    set lastGrpCr            = bj_lastCreatedGroup
                    set bj_groupAddGroupDest = temp.handlerGroup
                    set bj_lastCreatedGroup  = auraData.group
                
                    call GroupEnumUnitsInRange(temp.handlerGroup, GetUnitX(enum), GetUnitY(enum), temp.range[level].data, Aura.onCheck)

                    set bj_lastCreatedGroup  = lastGrpCr
                    set bj_groupAddGroupDest = lastGrpDest
                    set lastGrpDest          = null
                    set lastGrpCr            = null
                
                    set lastEventUnit        = eventUnit
                    set Aura.currentAura     = temp
                    set eventUnit            = enum
                
                    call temp.onEnum.run()
                
                    set eventUnit            = lastEventUnit
                    set lastEventUnit        = null
                endif
            endloop
        
            call DestroyGroup(temp.unitGroup)
            set temp.unitGroup = grp
            set grp            = CreateGroup()
        
            set iter = iter.next
        endloop
        set grp = null
    endmethod
 
    method removeUnit takes unit whichUnit returns nothing
        local AuraTimer object
    
        if (whichUnit == null) or (not IsUnitInGroup(whichUnit, this.unitGroup)) then
            return
        endif
    
        call GroupRemoveUnit(this.unitGroup, whichUnit)
        set this.unitGroupCount = this.unitGroupCount - 1
    
        call AuraUnit.strip(whichUnit, this)
    
        if this.unitGroupCount == 0 then
            set object = AuraTimer.create(this.enumRate)
            set object.tick = object.tick - 1
        
            if object.tick <= 0 then
                call PauseTimer(object.timer)
            endif
        endif
    endmethod
 
    method addUnit takes unit whichUnit returns nothing
        local AuraTimer object
    
        if (whichUnit == null) or (IsUnitInGroup(whichUnit, this.unitGroup)) then
            return
        endif
    
        call GroupAddUnit(this.unitGroup, whichUnit)
        set this.unitGroupCount = this.unitGroupCount + 1
    
        call AuraUnit.declare(whichUnit, this)
    
        if this.unitGroupCount == 1 then
            set object = AuraTimer.create(this.enumRate)
            if object.tick == 0 then
                //  Define handler function later.
                call TimerStart(object.timer, this.enumRate, true, function thistype.onLoop)
            endif
        
            set object.tick = object.tick + 1
        endif
    endmethod
 
    method setRange takes integer level, real range returns nothing
        if level <= 0 or level > this.range.size() then
            return
        endif
    
        set this.range[level].data = range
    endmethod
 
    static method create takes integer abilId, real enumRate returns Aura
        local Aura result       = Aura.abilMap.integer[abilId]
        local IntegerList list
    
        if result == 0 then
            set result              = Aura.allocate()
        
            set result.abilId       = abilId
            set result.unitGroup    = CreateGroup()
            set result.handlerGroup = CreateGroup()
            set result.enumRate     = enumRate
        
            set result.onEnum       = EvalCode.create()
            set result.onEnumRemove = EvalCode.create()
            set result.range        = RealList.create()
        
            call result.populate()
        
            set Aura.abilMap.integer[abilId] = result
        
            set list                = AuraTimer.create(enumRate).list        
            set result.node         = list.push(result).last
        endif    
        return result
    endmethod
 
    private static method onRemove takes nothing returns nothing
        local unit removed      = GetIndexedUnit()

        local AuraUnit auraUnit = AuraUnit(GetIndexedUnitId())
        local Aura temp
    
        if auraUnit.auraList != 0 then
            loop
                exitwhen auraUnit.auraList.empty()
            
                set temp = Aura(auraUnit.auraList.first.data)
                call temp.removeUnit(removed)
            
                call auraUnit.auraList.erase(auraUnit.auraList.first)
            endloop
        
            call auraUnit.destroy()
        endif
    
        set removed = null
    endmethod
 
    private static method initVar takes nothing returns nothing
        set Aura.abilMap = Table.create()
        set Aura.footman = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hfoo', 0, 0, 0)
        set Aura.onCheck = Filter(function Aura.onCheckGroup)
    
        call ShowUnit(Aura.footman, false)
        call SetUnitInvulnerable(Aura.footman, true)
    endmethod
 
    private static method initListener takes nothing returns nothing
        call RegisterUnitIndexEvent(Condition(function Aura.onRemove), EVENT_UNIT_DEINDEX)
    endmethod
 
    private static method init takes nothing returns nothing
        call Aura.initVar()
        call Aura.initListener()
    endmethod
 
    implement AuraM
endstruct

private module AuraM
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule

endlibrary
[/hidden]

[hidden="AllocH"]
library AllocT /*

    */ requires /*
    
        -------------------
        */ Table         /*
        -------------------
    
    */

//! runtextmacro Alloc("Alloc", "32766")
//! runtextmacro Alloc("AllocH", "2147483647")

//! textmacro Alloc takes NAME, INSTANCES

module $NAME$
    private static Table alloc = 0

    method deallocate takes nothing returns nothing
        debug if not thistype.alloc.has(this) or (thistype.alloc[this] != Table(0)) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "|cffffcc00$NAME$ error:|r Double-free detected.")
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "Faulty instance -> " + I2S(this) + "\n\n")
            debug return
        debug endif
    
        set thistype.alloc[this] = thistype.alloc[0]
        set thistype.alloc[0]    = Table(this)
    endmethod
 
    static method allocate takes nothing returns thistype
        local thistype this = thistype(thistype.alloc[0])
    
        if thistype.alloc[this] == Table(0) then
            debug if integer(this) >= $INSTANCES$ then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "|cffffcc00$NAME$ error:|r Exceeded $INSTANCES$ instances.")
                debug return thistype(0)
            debug endif
        
            set this = this + 1
            set thistype.alloc[0] = Table(this)
        else
            set thistype.alloc[0] = thistype.alloc[this]
        endif
        set thistype.alloc[this] = 0
    
        return this
    endmethod
 
    private static method onInit takes nothing returns nothing
        set thistype.alloc = Table.create()
    endmethod
endmodule

//! endtextmacro

endlibrary

library_once Alloc uses AllocT
endlibrary

library_once AllocH uses AllocT
endlibrary
[/hidden]

And here's the fruit of my labor (of course, the stats are temporary, and the effect will not be that of constant damage output per second but that of augmentation). (Using Bandicam)


Edit:

I felt that the system wasn't good enough, so I went all out with it (Reworked to purely use a TriggerRegisterUnitInRange and a first of group loop). The cost is that I cannot feasibly finish 4 heroes on time (so only 2).


JASS:
library AuraSystem /*

    */ requires /*
   
        --------------------------
        */ UnitDex              /*
        --------------------------
            #?  GroupUtils
            #?  WorldBounds
           
            -> TriggerHappy
           
            link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
           
        --------------------------
        */ ListT                /*
        --------------------------
            # Table
            # Alloc
           
            -> Bannar
           
            link: https://www.hiveworkshop.com/threads/containers-list-t.249011/
           
        --------------------------
        */ Eval                 /*
        --------------------------
            # Table
           
            -> MyPad
           
            link: https://www.hiveworkshop.com/threads/evaluate-code.307536/
           
        --------------------------
        */ AllocH               /*
        --------------------------
            # AllocT
           
            -> MyPad
           
        --------------------------
        */ Alloc                /*
        --------------------------
            -> Sevion
    */
   
//! runtextmacro DEFINE_LIST("private", "RealList", "real")

native UnitAlive takes unit id returns boolean

globals
    private unit eventUnit                  = null
    private unit auraUnit                   = null
    private integer addCounter              = 0
endglobals

function GetAuraEventUnit takes nothing returns unit
    return eventUnit
endfunction

function GetAuraUnit takes nothing returns unit
    return auraUnit
endfunction

function PreventAura takes nothing returns nothing
    set addCounter = addCounter - 1
endfunction

function AllowAura takes nothing returns nothing
    set addCounter = addCounter + 1
endfunction

private function RealToIndex takes real r returns integer
    return R2I(RMaxBJ(r, 0.0001) * 10000.)
endfunction

private function Execute takes code func returns nothing
    call ForForce(bj_FORCE_PLAYER[0], func)
endfunction

private keyword AuraM

private struct AuraUnitData extends array
    implement AllocH

    private static EvalCode callback        = 0
   
    private static Table lastGroupMap       = 0
    private static Table rangeMap           = 0
    private static Table triggerMap         = 0
    private static Table triggerIdMap       = 0
    private static Table unitMap            = 0
    private static Table auraInstMap        = 0
   
    readonly static Table unitListMap       = 0
    readonly static trigger onEntranceTrig  = null
    readonly static thistype currentInst    = 0
   
    method pushAura takes unit whichUnit returns nothing
        local integer unitId    = GetUnitId(whichUnit)
        local Table table
        local IntegerList list
       
        if whichUnit == null then
            return
        endif
       
        if not thistype.unitListMap.integer.has(unitId) then
            set table = Table.create()
            set list = IntegerList.create()
           
            set table.integer[-1] = list
            set thistype.unitListMap.integer[unitId] = table
        else
            set table = Table(thistype.unitListMap.integer[unitId])
            set list = IntegerList(table.integer[-1])
        endif
       
        if not table.integer.has(this) then
            set table.integer[this] = list.push(this).last
            call GroupAddUnit(thistype.lastGroupMap.group[this], whichUnit)
        endif
    endmethod

    method popAura takes unit whichUnit returns nothing
        local integer unitId    = GetUnitId(whichUnit)
        local Table table
        local IntegerList list
       
        if whichUnit == null then
            return
        endif
       
        if not thistype.unitListMap.integer.has(unitId) then
            //  no need to pop aura.
            return
        endif
       
        set table   = Table(thistype.unitListMap.integer[unitId])
        set list    = IntegerList(table.integer[-1])
       
        if table.integer.has(this) then
            call list.erase(table.integer[this])
            call table.integer.remove(this)
            call GroupRemoveUnit(thistype.lastGroupMap.group[this], whichUnit)
        endif
    endmethod

    private static method onEntrance takes nothing returns nothing
        local thistype this = thistype.triggerIdMap.integer[GetHandleId(GetTriggeringTrigger())]
        local thistype lastInst  = thistype.currentInst
       
        local unit lastEventUnit = eventUnit
        local unit lastAuraUnit  = auraUnit
       
        set eventUnit            = thistype.unitMap.unit[this]
        set auraUnit             = GetTriggerUnit()
        set thistype.currentInst = this
       
        set addCounter = 0
        call TriggerEvaluate(thistype.onEntranceTrig)
       
        set eventUnit            = lastEventUnit
        set auraUnit             = lastAuraUnit
        set thistype.currentInst = lastInst
       
        set lastEventUnit   = null
        set lastAuraUnit    = null

        if addCounter >= 0 then
            call this.pushAura(GetTriggerUnit())
        endif
    endmethod
   
    method destroy takes nothing returns nothing
        if thistype.lastGroupMap.group.has(this) then
            call DestroyGroup(thistype.lastGroupMap.group[this])
            call thistype.lastGroupMap.group.remove(this)
        else
            debug call BJDebugMsg("|cffffcc00AuraUnitData:|r Double-free detected!\n->" + I2S(this))
            return
        endif
       
        if thistype.triggerMap.trigger.has(this) then
            call thistype.triggerIdMap.integer.remove(GetHandleId(thistype.triggerMap.trigger[this]))
            call DestroyTrigger(thistype.triggerMap.trigger[this])
           
            call thistype.triggerMap.trigger.remove(this)
        endif
       
        call thistype.unitMap.unit.remove(this)
        call thistype.rangeMap.unit.remove(this)
        call thistype.auraInstMap.integer.remove(this)
       
        call this.deallocate()
    endmethod
   
    method operator group= takes group newGroup returns nothing
        set thistype.lastGroupMap.group[this] = newGroup
    endmethod
   
    method operator group takes nothing returns group
        return thistype.lastGroupMap.group[this]
    endmethod
   
    method operator range= takes real newRange returns nothing
        local real lastRange = thistype.rangeMap.real[this]
        local integer trigId
        local trigger trig
       
        if not (newRange != range) then
            return
        endif
       
        set thistype.rangeMap.real[this] = newRange
       
        if thistype.triggerMap.trigger.has(this) then
            set trigId = GetHandleId(thistype.triggerMap.trigger[this])
           
            call DestroyTrigger(thistype.triggerMap.trigger[this])
            call thistype.triggerIdMap.integer.remove(trigId)
        endif
       
        set trig  = CreateTrigger()
       
        set thistype.triggerMap.trigger[this] = trig
        set trigId = GetHandleId(trig)
       
        set thistype.triggerIdMap.integer[trigId] = this
       
        call TriggerRegisterUnitInRange(trig, thistype.unitMap.unit[this], newRange, null)
        call TriggerAddCondition(trig, Condition(function thistype.onEntrance))
    endmethod   
   
    method operator range takes nothing  returns real
        return thistype.rangeMap.real[this]
    endmethod
   
    method operator aura= takes integer newAura returns nothing
        set thistype.auraInstMap.integer[this] = newAura
    endmethod
   
    method operator aura takes nothing returns integer
        return thistype.auraInstMap.integer[this]
    endmethod
   
    static method create takes unit whichUnit returns thistype
        local IntegerList list
        local Table table
       
        local thistype result = thistype.allocate()
        local integer  unitId = GetUnitId(whichUnit)
       
        set thistype.lastGroupMap.group[result] = CreateGroup()
        set thistype.rangeMap.real[result]      = 0.
        set thistype.unitMap.unit[result]       = whichUnit
       
        call result.pushAura(whichUnit)
       
        return result
    endmethod

    private static method onRemove takes nothing returns nothing
        local integer unitId        = GetIndexedUnitId()
        local unit    u             = GetUnitById(unitId)
       
        local Table table
        local IntegerList list
        local thistype temp
       
        if thistype.unitListMap.integer.has(unitId) then
            set table = Table(thistype.unitListMap.integer[unitId])
            set list  = IntegerList(table.integer[-1])
           
            loop
                exitwhen list.empty()
               
                set temp = thistype(list.first.data)
                call temp.popAura(u)
            endloop
        endif
       
        set u = null
    endmethod
   
    private static method initVar takes nothing returns nothing
        set thistype.lastGroupMap = Table.create()
        set thistype.rangeMap     = Table.create()
        set thistype.triggerMap   = Table.create()
        set thistype.triggerIdMap = Table.create()
        set thistype.unitMap      = Table.create()
        set thistype.auraInstMap  = Table.create()
       
        set thistype.unitListMap  = Table.create()
       
        set thistype.onEntranceTrig = CreateTrigger()
    endmethod
   
    private static method initListener takes nothing returns nothing
        call RegisterUnitIndexEvent(Condition(function thistype.onRemove), EVENT_UNIT_DEINDEX)
    endmethod
   
    private static method init takes nothing returns nothing
        call thistype.initVar()
        call thistype.initListener()
    endmethod
   
    implement AuraM
endstruct

private struct AuraUnit extends array
    private boolean isAlloc
   
    readonly IntegerList auraList
    readonly Table       auraMap
    readonly Table       auraDataMap
   
    method destroy takes nothing returns nothing
        if not this.isAlloc then
            return
        endif
       
        call this.auraDataMap.destroy()
        call this.auraMap.destroy()
        call this.auraList.destroy()
       
        set this.isAlloc        = false
       
        set this.auraList       = 0
        set this.auraMap        = 0
        set this.auraDataMap    = 0
    endmethod
   
    static method operator [] takes unit whichUnit returns AuraUnit
        local AuraUnit result = AuraUnit(GetUnitId(whichUnit))
       
        if not result.isAlloc then
            set result.isAlloc      = true
            set result.auraList     = IntegerList.create()
            set result.auraMap      = Table.create()
            set result.auraDataMap  = Table.create()
        endif
        return result
    endmethod
   
    static method declare takes unit whichUnit, integer auraInstance, real auraRange returns AuraUnit
        local AuraUnit      temp        = AuraUnit[whichUnit]
        local AuraUnitData  tempData
       
        if not temp.auraMap.integer.has(auraInstance) then
            set tempData        = AuraUnitData.create(whichUnit)
            set tempData.aura   = auraInstance
           
            set temp.auraMap.integer[auraInstance] = temp.auraList.push(auraInstance).last
            set temp.auraDataMap.integer[auraInstance] = tempData           
        else
            set tempData = AuraUnitData(temp.auraDataMap.integer[auraInstance])
        endif
        set tempData.range = auraRange
       
        return temp
    endmethod
   
    static method strip takes unit whichUnit, integer auraInstance returns AuraUnit
        local AuraUnit temp         = AuraUnit(GetUnitId(whichUnit))
        local AuraUnitData tempData
       
        if not temp.isAlloc then
            set temp = 0
            return temp
        endif
       
        if temp.auraMap.integer.has(auraInstance) then
            set tempData = AuraUnitData(temp.auraDataMap.integer[auraInstance])
            call tempData.destroy()
           
            call temp.auraList.erase(IntegerListItem(temp.auraMap.integer[auraInstance]))
            call temp.auraMap.integer.remove(auraInstance)
            call temp.auraDataMap.integer.remove(auraInstance)
        endif
       
        return temp
    endmethod
endstruct

private struct AuraTimer extends array
    readonly static Table timerMap      = 0
    readonly static Table timerListMap  = 0
    readonly static Table timerTickMap  = 0
   
    method operator tick takes nothing returns integer
        return thistype.timerTickMap.integer[this]
    endmethod
   
    method operator tick= takes integer newTick returns nothing
        set thistype.timerTickMap.integer[this] = newTick
    endmethod
   
    method operator list takes nothing returns IntegerList
        return IntegerList(thistype.timerListMap.integer[this])
    endmethod
   
    method operator timer takes nothing returns timer
        return thistype.timerMap.timer[this]
    endmethod
   
    static method create takes real enumRate returns thistype
        local thistype result = RealToIndex(enumRate)
       
        if not thistype.timerMap.timer.has(result) then
            set thistype.timerMap.timer[result]         = CreateTimer()
            set thistype.timerListMap.integer[result]   = IntegerList.create()
        endif
        return result
    endmethod
   
    private static method init takes nothing returns nothing
        set thistype.timerMap       = Table.create()
        set thistype.timerListMap   = Table.create()
        set thistype.timerTickMap   = Table.create()
    endmethod
   
    implement AuraM
endstruct

struct Aura extends array
    implement Alloc
   
    private static unit footman         = null
    private static Table abilMap        = 0
   
    //  Global variables that can be accessed.
    readonly static Aura currentAura    = 0
   
    private IntegerListItem node
    private RealList range
   
    private group unitGroup
    private integer unitGroupCount
    private real enumRate
   
    readonly EvalCode onEnum
    readonly EvalCode onEnumRemove
    readonly EvalCode onEnter
   
    readonly integer abilId
   
    readonly group handlerGroup
   
    boolean removeOnDeath
   
    private method populate takes nothing returns nothing
        local integer curLevel
       
        call UnitAddAbility(Aura.footman, this.abilId)
        call this.range.push(0.)
       
        set curLevel = GetUnitAbilityLevel(Aura.footman, this.abilId)
       
        loop
            call SetUnitAbilityLevel(Aura.footman, this.abilId, curLevel + 1)
            exitwhen curLevel == GetUnitAbilityLevel(Aura.footman, this.abilId)
           
            call this.range.push(0.)
            set curLevel = GetUnitAbilityLevel(Aura.footman, this.abilId)
        endloop
       
        call UnitRemoveAbility(Aura.footman, this.abilId)
    endmethod
   
    private static constant integer OP_COUNT        = 701
   
    private static AuraTimer lastAuraTimer          = 0
    private static IntegerListItem  lastIterable    = 0
    private static integer   ticks                  = 0
    private static integer   offset                 = 0
   
    private static group     execOuterGroup         = null
    private static group     execInnerGroup         = null
   
    private static unit      lastOuterUnit          = null
    private static unit      lastInnerUnit          = null
   
    private static boolean   hasLoopStarted         = false
    private static boolean   hasCreatedOuterGroup   = false
    private static boolean   hasCreatedInnerGroup   = false
    private static boolean   hasOuterLoopStarted    = false
    private static boolean   hasInnerLoopStarted    = false
   
    //  Ensure that the loop isn't intensive.
    private static method onLoopExec takes nothing returns nothing
        local AuraUnitData auraData
        local Aura temp
        local IntegerListItem iter
       
        local group grp           
        local group grp2
        local group auraDataGrp
       
        local unit enum
        local unit lastEventUnit
        local unit lastUnit
       
        local integer level
       
        local boolean isAlive
        local boolean startedOuterLoop  =   Aura.hasOuterLoopStarted
        local boolean startedInnerLoop  =   Aura.hasInnerLoopStarted
               
        local real range
       
        if not Aura.hasLoopStarted then
            set Aura.hasLoopStarted = true
            set iter = lastAuraTimer.list.first
        else
            set iter = Aura.lastIterable
        endif
       
        loop
            exitwhen iter == 0 or (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0)
           
            set temp = Aura(iter.data)
           
            //  If we created any outer group, refer to it.
            if Aura.hasCreatedOuterGroup then
                set grp  = Aura.execOuterGroup
                set Aura.hasCreatedOuterGroup = false
            else
                set grp  = CreateGroup()
            endif
           
            set Aura.ticks = Aura.ticks + 1
           
            set Aura.hasOuterLoopStarted = true
            if startedOuterLoop then
                set enum = Aura.lastOuterUnit
            else
                set enum = FirstOfGroup(temp.unitGroup)
            endif
           
            loop
                exitwhen (enum == null) or (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0)
               
                set Aura.ticks = Aura.ticks + 1
               
                set lastEventUnit = eventUnit
                set eventUnit     = enum

                call GroupRemoveUnit(temp.unitGroup, enum)
                call GroupAddUnit(grp, enum)

                set auraData = AuraUnitData(AuraUnit[enum].auraDataMap.integer[temp])
                if Aura.hasCreatedInnerGroup then
                    set grp2  = Aura.execInnerGroup
                    set Aura.hasCreatedInnerGroup = false
                else
                    set grp2  = CreateGroup()
                endif

                set isAlive  = UnitAlive(enum)
                set level    = GetUnitAbilityLevel(enum, temp.abilId)
                set range    = temp.range[level].data
               
                set Aura.hasInnerLoopStarted = true           
                set auraDataGrp = auraData.group
                set lastUnit = auraUnit
               
                if startedInnerLoop then
                    set auraUnit = Aura.lastInnerUnit
                else
                    set auraUnit = FirstOfGroup(auraDataGrp)
                endif
               
                loop
                    exitwhen (auraUnit == null) or (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0)
                   
                    set Aura.ticks = Aura.ticks + 1
                   
                    if IsUnitInRange(auraUnit, enum, range) and isAlive then
                        call temp.onEnum.run()
                       
                        if not UnitAlive(auraUnit) and temp.removeOnDeath then
                            call temp.onEnumRemove.run()
                            call auraData.popAura(auraUnit)
                        else
                            call GroupAddUnit(grp2, auraUnit)                       
                            call GroupRemoveUnit(auraDataGrp, auraUnit)
                        endif
                    else
                        if auraUnit != eventUnit then
                            call temp.onEnumRemove.run()
                            call auraData.popAura(auraUnit)
                        else
                            call GroupAddUnit(grp2, auraUnit)
                            call GroupRemoveUnit(auraDataGrp, auraUnit)
                        endif
                    endif
                   
                    set auraUnit = lastUnit

                    set lastUnit = auraUnit
                    set auraUnit = FirstOfGroup(auraData.group)
                endloop
                               
                set eventUnit = lastEventUnit
                if (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0)then
                    set Aura.lastOuterUnit = enum
                    set Aura.lastInnerUnit = auraUnit
                    set Aura.hasCreatedInnerGroup = true
                    set Aura.execInnerGroup       = grp2
                   
                    set auraUnit           = lastUnit
                    exitwhen true
                endif
               
                set Aura.hasInnerLoopStarted = false
           
                call DestroyGroup(auraDataGrp)
                set auraData.group = grp2

                set enum = FirstOfGroup(temp.unitGroup)
            endloop
           
            if (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0) then
                set Aura.hasCreatedOuterGroup = true
                set Aura.execOuterGroup       = grp
                set Aura.lastIterable         = iter
               
                exitwhen true
            endif
           
            set Aura.hasOuterLoopStarted = false
                 
            call DestroyGroup(temp.unitGroup)
            set temp.unitGroup = grp
           
            set iter = iter.next
        endloop

        set grp2        = null
        set grp         = null
        set auraDataGrp = null
        set enum        = null
        set lastEventUnit   = null
        set lastUnit        = null
       
        if (ModuloInteger(Aura.ticks + 1, Aura.OP_COUNT) == 0) then
            set Aura.ticks = Aura.ticks + 1
            set Aura.offset = Aura.offset + 1
            call Execute(function Aura.onLoopExec)
        else
            set Aura.hasLoopStarted = false
            set Aura.hasCreatedOuterGroup= false
            set Aura.hasCreatedInnerGroup= false
            set Aura.hasOuterLoopStarted = false
            set Aura.hasInnerLoopStarted = false
           
            set Aura.execOuterGroup      = null
            set Aura.execInnerGroup      = null
            set Aura.lastOuterUnit       = null
            set Aura.lastInnerUnit       = null
           
            set Aura.lastIterable        = 0
            set Aura.lastAuraTimer       = 0
           
            set Aura.offset               = 0
        endif
    endmethod
   
    private static method onLoop takes nothing returns nothing
        set lastAuraTimer = AuraTimer.create(TimerGetTimeout(GetExpiredTimer()))
       
        set Aura.ticks    = 0
        call Execute(function Aura.onLoopExec)
    endmethod
   
    private method filter takes unit whichUnit, real newRange returns nothing
        local unit lastEventUnit
        local unit enumUnit
       
        local group newGroup
        local group iterGroup
       
        local boolean isInGroup = IsUnitInGroup(whichUnit, this.unitGroup)
       
        local AuraUnitData auraUnitData = AuraUnitData(AuraUnit[whichUnit].auraDataMap.integer[this])
       
        if isInGroup then
            set newGroup = CreateGroup()
        endif
       
        set lastEventUnit= eventUnit
        set eventUnit    = whichUnit
       
        set iterGroup    = auraUnitData.group
       
        loop
            set enumUnit = auraUnit
            set auraUnit = FirstOfGroup(iterGroup)
           
            exitwhen auraUnit == null
           
            if not IsUnitInRange(auraUnit, eventUnit, newRange) or ((auraUnit == eventUnit) and not isInGroup) then
                call this.onEnumRemove.run()
            endif
           
            if isInGroup then
                call GroupAddUnit(newGroup, auraUnit)
            else
                call auraUnitData.popAura(auraUnit)
            endif
            set auraUnit = enumUnit
        endloop
       
        if isInGroup then
            call DestroyGroup(auraUnitData.group)
            set auraUnitData.group = newGroup
            set newGroup           = null
        endif
       
        set eventUnit    = lastEventUnit
        set lastEventUnit= null
    endmethod
   
    private static Aura filterInstance  = 0
    private static unit filterParameter = null
   
    private static method filterExec takes nothing returns nothing
        call Aura.filterInstance.filter(Aura.filterParameter, 0.)
    endmethod
   
    private method filterEx takes unit whichUnit returns nothing
        local Aura lastInst      = Aura.filterInstance
        local unit lastParam     = Aura.filterParameter
       
        set Aura.filterParameter = whichUnit
        set Aura.filterInstance  = this
       
        call Execute(function Aura.filterExec)
       
        set Aura.filterParameter = lastParam
        set Aura.filterInstance  = lastInst
       
        set lastParam = null
    endmethod
   
    method removeUnit takes unit whichUnit returns nothing
        local AuraTimer object
       
        if (whichUnit == null) or (not IsUnitInGroup(whichUnit, this.unitGroup)) then
            return
        endif
       
        call GroupRemoveUnit(this.unitGroup, whichUnit)
        set this.unitGroupCount = this.unitGroupCount - 1
       
        //  Let onEnumRemove run here...
        call this.filterEx(whichUnit)
        call AuraUnit.strip(whichUnit, this)
       
        if this.unitGroupCount == 0 then
            set object = AuraTimer.create(this.enumRate)
            set object.tick = object.tick - 1
           
            if object.tick <= 0 then
                call PauseTimer(object.timer)
            endif
        endif
    endmethod
   
    method updateUnitRange takes unit whichUnit returns nothing
        local integer level = GetUnitAbilityLevel(whichUnit, this.abilId)
       
        call AuraUnit.declare(whichUnit, this, this.range[level].data)
        //  This gets updated on next tick anyway
        // call this.filter(whichUnit, this.range[level].data)
    endmethod
   
    method addUnit takes unit whichUnit returns nothing
        local AuraTimer object
        local integer level
       
        if (whichUnit == null) then
            return
           
        elseif (IsUnitInGroup(whichUnit, this.unitGroup)) then
            call this.updateUnitRange(whichUnit)
            return
        endif
       
        set level = GetUnitAbilityLevel(whichUnit, this.abilId)
       
        call GroupAddUnit(this.unitGroup, whichUnit)
        set this.unitGroupCount = this.unitGroupCount + 1
       
        call AuraUnit.declare(whichUnit, this, this.range[level].data)
           
        if this.unitGroupCount == 1 then
            set object = AuraTimer.create(this.enumRate)
            if object.tick == 0 then
                //  Define handler function later.
                call TimerStart(object.timer, this.enumRate, true, function Aura.onLoop)
            endif
           
            set object.tick = object.tick + 1
        endif
    endmethod
   
    method setRange takes integer level, real range returns nothing
        local unit enumUnit
        local group newGroup
       
        if level <= 0 or level > this.range.size() then
            return
        endif
       
        set this.range[level].data = range
        set newGroup = CreateGroup()
       
        loop
            set enumUnit = FirstOfGroup(this.unitGroup)
            exitwhen enumUnit == null
           
            set level = GetUnitAbilityLevel(enumUnit, this.abilId)
            call AuraUnit.declare(enumUnit, this, this.range[level].data)
           
            if this.range[level].data < range then
                call this.filter(enumUnit, this.range[level].data)
            endif
           
            call GroupRemoveUnit(this.unitGroup, enumUnit)
            call GroupAddUnit(newGroup, enumUnit)
        endloop
       
        call DestroyGroup(this.unitGroup)
        set this.unitGroup = newGroup
        set newGroup = null
    endmethod
   
    static method create takes integer abilId, real enumRate returns Aura
        local Aura result       = Aura.abilMap.integer[abilId]
        local IntegerList list
       
        if result == 0 then
            set result              = Aura.allocate()
           
            set result.abilId       = abilId
            set result.unitGroup    = CreateGroup()
            set result.handlerGroup = CreateGroup()
            set result.enumRate     = enumRate
           
            set result.onEnum       = EvalCode.create()
            set result.onEnumRemove = EvalCode.create()
            set result.onEnter      = EvalCode.create()
            set result.range        = RealList.create()
           
            set result.removeOnDeath    = true
            call result.populate()
           
            set Aura.abilMap.integer[abilId] = result
           
            set list                = AuraTimer.create(enumRate).list           
            set result.node         = list.push(result).last
        endif       
        return result
    endmethod
   
    private static method onRemove takes nothing returns nothing
        local unit removed      = GetIndexedUnit()

        local AuraUnit auraUnit = AuraUnit(GetIndexedUnitId())
        local Aura temp
       
        if auraUnit.auraList != 0 then
            loop
                exitwhen auraUnit.auraList.empty()
               
                set temp = Aura(auraUnit.auraList.first.data)
                call temp.removeUnit(removed)
            endloop
           
            call auraUnit.destroy()
        endif
       
        set removed = null
    endmethod
   
    private static method onEntrance takes nothing returns nothing
        local Aura curAura = Aura(AuraUnitData.currentInst.aura)
       
        call curAura.onEnter.run()
    endmethod
   
    private static method initVar takes nothing returns nothing
        set Aura.abilMap = Table.create()
        set Aura.footman = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hfoo', 0, 0, 0)
       
        call ShowUnit(Aura.footman, false)
        call SetUnitInvulnerable(Aura.footman, true)
    endmethod
   
    private static method initListener takes nothing returns nothing
        call RegisterUnitIndexEvent(Condition(function Aura.onRemove), EVENT_UNIT_DEINDEX)
        call TriggerAddCondition(AuraUnitData.onEntranceTrig, Condition(function Aura.onEntrance))
    endmethod
   
    private static method init takes nothing returns nothing
        call Aura.initVar()
        call Aura.initListener()
    endmethod
   
    implement AuraM
endstruct

private module AuraM
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule

endlibrary

Now, the ability showcased before affects all allied melee ground units within 600 range of the Hero, giving them a boost when they are far and allowing them to stun-lock a target enemy unit on contact with the propulsion cooldown of 16/13/10 (in retrospect, this is too short, so I'm nerfing it to 40/32/24). (Heroes are particularly vulnerable to this.) In addition, if not within the vicinity of another Hero with said aura, the ability is lost.
 
Last edited:
I think so, though @KILLCIDE is here to clarify that. (The second spell was going to be and still is a headache to aesthetically implement based on what I had in mind.)

Some things noteworthy that might be already known:


Some dummy spells do require the caster to have it before being cast (at least a 0-second timer, I guess). Otherwise, no effect is observed upon cast. (To circumvent this, have a global dummy cast that ability)

E.g.: Purge, Ensnare, etc..
 
Status
Not open for further replies.
Top