• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a faction for Warcraft 3 and enter Hive's 19th Techtree Contest: Co-Op Commanders! Click here to enter!
  • Create a void inspired texture for Warcraft 3 and enter Hive's 34th Texturing Contest: Void! Click here to enter!
  • The Hive's 21st Texturing Contest: Upgrade is now concluded, time to vote for your favourite set of icons! Click here to vote!

jass]

JASS:
scope Devourer initializer Init /*
*************************************************************************************
*
*       Zephyr Contest # 14 : Unique Summoning
*
*************************************************************************************
*
*   Summon Devourer
*
*   ---------------------------------------------------------------------------
*
*       Target point, if there are 5/4/3 trees within 256 range from the target
*        point, it conjures a portal. After 5/4/3 seconds, the Devourer is summoned.
*
*       Whenever a unit loses health within 1000 radius, the Devourer consumes the 
*       health, healing by 30/40/50% of the lost health. Whenever the Devourer has
*       consumed, 400 Health it increases the Devourer's timed life by 1/2/3 seconds.
*
*       Whenever a unit loses mana within 1000 radius, the Devourer stores them inside 
*       his vessel. If the amount of mana stored reaches 400/350/300 mana, it releases a 
*       shockwave that expands for 1 seconds 400 units outwards from the Devourer,
*       dealing 100/200/300 damage to enemy units.
*
*       When the devourer attacks a unit, it releases a swarm that travels 768
*       range and deals 75/150/225 damage to units within 128 radius.
*       Has a cooldown of 10/8/6 seconds.
*
*       The Devourer has 750/1000/1250 max health but doesn't regenerate HP, has 
*       a movement speed of 300/315/330, attack cooldown of 1.5/1.4/1.3 seconds,
*       attack damage of 25/35/45 and 1200/1200 sight range.
*
*       Lasts 20/30/40 seconds.
*
*************************************************************************************
*
*   Requires:
*
*   Table
*       - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*   DummyRecycler
*       - hiveworkshop.com/forums/spells-569/dummy-recycler-v1-13-a-277659/?prev=r%3D20%26page%3D2
*   GetUnitCollision
*       - github.com/nestharus/JASS/blob/master/jass/Systems/GetUnitCollision/script.j
*   MapBounds
*       - hiveworkshop.com/forums/jass-resources-412/snippet-mapbounds-222870/
*   SpellEffectEvent
*       - hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
*   RegisterPlayerUnitEvent
*       - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*   IsDestructableTree
*       - hiveworkshop.com/forums/jass-resources-412/snippet-isdestructabletree-248054/
*   Projection
*       - hiveworkshop.com/forums/2038279-post655.html
*   (optional) ZLibrary
*       - hiveworkshop.com/forums/jass-resources-412/snippet-zlibrary-237821/
*
*************************************************************************************
*
*   How to Import:
*
*       1) Import first the dummy.mdx into your map(if you don't have it)
*       2) Copy the Missile Dummy unit from the Object Editor
*       3) Copy the 3 Devourer units, then configure the ids at the end of
*          the constants.
*       4) Copy the libraries (ZLibrary is optional)
*       5) Copy the spell code
*
*************************************************************************************
*
*   Credits:
*
*       Bribe 
*       Adiktuz
*       Nestharus
*       Magtheridon96
*       D.O.G.
*       BPower
*       Deaod
*       Maker
*       Flux
*       D4RK_G4ND4LF
*
*************************************************************************************/
    globals
        /*
        *   Timeout for all timers running in this spell
        */
        private constant real TIMEOUT = 0.03125
        /*
        *   The ability id of the spell
        */
        private constant integer ABIL_ID = 'Asdv'
        /*
        *   rawcode id for the creeps (sfx units)
        */
        private constant integer CREEP_ID = 'nspg'
        /*
        *   rawcode id of dummies
        */
        private constant integer DUMMY_ID = 'dumi'
        //------------------------------------------------------
        /*
        *   Spell on cast
        */
        //------------------------------------------------------
        /*
        *   Required trees for summoning
        */
        private constant integer REQUIRED_TREES = 6
        private constant integer REQUIRED_TREES_PER_LVL = -1
        /*
        *   Radius for checking the required trees
        */
        private constant real TREE_RADIUS_CHECK = 256
        private constant real TREE_RADIUS_CHECK_PER_LVL = 0
        /*
        *   Spawn delay of the devourer
        */
        private constant real SPAWN_DELAY = 6
        private constant real SPAWN_DELAY_PER_LVL = -1
        /*
        *   How long the the devourer last
        */
        private constant real TIMED_LIFE = 10
        private constant real TIMED_LIFE_PER_LVL = 10
        //------------------------------------------------------
        /*
        *   Souls configurables
        */
        //------------------------------------------------------
        /*
        *   Consumes all health lost within the given range
        */
        private constant real HEALTH_FEED_RADIUS = 1000
        private constant real HEALTH_FEED_RADIUS_PER_LVL = 0
        /*
        *   How much of the consume health are healed to the
        *   devourer
        */
        private constant real HEALTH_RATE = 0.2
        private constant real HEALTH_RATE_PER_LVL = 0.1
        /*
        *   How much health are required to increase the timed
        *   life
        */
        private constant real TIMED_LIFE_REQ = 400
        private constant real TIMED_LIFE_REQ_PER_LVL = 0
        /*
        *   How much timed life is added?
        */
        private constant real TIMED_LIFE_BONUS = 0
        private constant real TIMED_LIFE_BONUS_PER_LVL = 1
        //------------------------------------------------------
        /*
        *   Mana Release/Shockwave Configurables
        */
        //------------------------------------------------------
        /*
        *   Consumes all mana lost within the given range
        */
        private constant real MANA_FEED_RADIUS = 1000
        private constant real MANA_FEED_RADIUS_PER_LVL = 0
        /*
        *   Required mana to release the shockwave  
        */        
        private constant real MANA_RELEASE_REQ = 450
        private constant real MANA_RELEASE_REQ_PER_LVL = -50
        /*
        *   How large the shockwave is
        */
        private constant real MANA_RELEASE_RADIUS = 450
        private constant real MANA_RELEASE_RADIUS_PER_LVL = 0
        /*
        *   How long does the shockwave grow
        *   from it's release point to it's max radius
        */
        private constant real MANA_RELEASE_GROW_TIME = 1
        /*
        *   Damage dealt to the affected units 
        */
        private constant real MANA_RELEASE_DMG = 0
        private constant real MANA_RELEASE_DMG_PER_LVL = 100
        private constant attacktype MANA_RELEASE_ATKTYPE = ATTACK_TYPE_CHAOS
        private constant damagetype MANA_RELEASE_DMGTYPE = DAMAGE_TYPE_UNIVERSAL
        /*
        *   Is the shockwave locked to the devourer?
        *   locked = follows the devourer's position instead
        *            of the position of release.
        */
        private constant boolean MANA_RELEASED_LOCKED = true
        /*
        *   Cooldown of the shockwave
        */
        private constant real MANA_RELEASE_CD = 0
        private constant real MANA_RELEASE_CD_PER_LVL = 0
        /*
        *   Does the devourer collect mana even
        *   if the shockwave is on cooldown?
        */
        private constant boolean COLLECT_MANA_ON_CD = false
        //------------------------------------------------------
        /*
        *   Swarm configurables
        */
        //------------------------------------------------------
        /*
        *   Starting polar offset of the swarms
        */
        private constant real SWARM_SPAWN_OFFSET = 128
        private constant real SWARM_SPAWN_OFFSET_PER_LVL = 0
        /*
        *   How far doest the swarm travel
        *   (from the offset)
        */
        private constant real SWARM_TRAVEL_DIST = 768
        private constant real SWARM_TRAVEL_DIST_PER_LVL = 0
        /*
        *   Radius of the swarm
        */
        private constant real SWARM_RADIUS = 128
        private constant real SWARM_RADIUS_PER_LVL = 0
        /*
        *   Damage dealt by the swarm
        */
        private constant real SWARM_DAMAGE = 0
        private constant real SWARM_DAMAGE_PER_LVL = 75
        private constant attacktype SWARM_DAMAGE_ATKTYPE = ATTACK_TYPE_CHAOS
        private constant damagetype SWARM_DAMAGE_DMGTYPE = DAMAGE_TYPE_UNIVERSAL
        /*
        *   Swarm cooldown
        */
        private constant real SWARM_CD = 12
        private constant real SWARM_CD_PER_LVL = -2
        /*
        *   Swarm speed
        */
        private constant real SWARM_SPEED = 512
        //------------------------------------------------------
        /*
        *   On Cast Effects
        */
        //------------------------------------------------------
        private constant real DUMMY_COLLISION_SIZE = 16
        /*
        *   Portal Fields
        */
        /*
        *   Model of the portal
        */
        private constant string PORTAL_MODEL = "Abilities\\Spells\\Other\\Parasite\\ParasiteMissile.mdl"
        /*
        *   Height of the portal
        */
        private constant real PORTAL_Z = 128
        /*
        *   Starting appearance of the portal
        */
        private constant real PORTAL_START_SCALE = 1
        private constant integer PORTAL_START_ALPHA = 255
        private constant integer PORTAL_START_RED = 255
        private constant integer PORTAL_START_GREEN = 255
        private constant integer PORTAL_START_BLUE = 255
        /*
        *   End appearance of the portal
        */
        private constant real PORTAL_END_SCALE = 5
        private constant integer PORTAL_END_ALPHA = 255
        private constant integer PORTAL_END_RED = 255
        private constant integer PORTAL_END_GREEN = 255
        private constant integer PORTAL_END_BLUE = 255
        /*
        *   Portal shard fields
        */
        /*
        *   Show the portal shards?
        */
        private constant boolean SHOW_SHARDS = true
        /*
        *   Model of the shards
        */
        private constant string SHARD_MODEL = "Abilities\\Spells\\Other\\Parasite\\ParasiteMissile.mdl"
        /*
        *   Start appearance of the shards
        */
        private constant real SHARD_START_SCALE = 1
        private constant integer SHARD_START_ALPHA = 255
        private constant integer SHARD_START_RED = 255
        private constant integer SHARD_START_GREEN = 255
        private constant integer SHARD_START_BLUE = 255
        /*
        *   End appearance of the shards
        */
        private constant real SHARD_END_SCALE = 0
        private constant integer SHARD_END_ALPHA = 255
        private constant integer SHARD_END_RED = 255
        private constant integer SHARD_END_GREEN = 255
        private constant integer SHARD_END_BLUE = 255
        /*
        *   Spawn distance of the shards
        */
        private constant real SHARD_DISTANCE = 300
        private constant real SHARD_DISTANCE_VAR = 100
        /*
        *   Spawn angular variation of the shards
        */
        private constant real SHARD_ANGLE = 0
        private constant real SHARD_ANGLE_VAR = bj_PI
        private constant real SHARD_Z_ANGLE = bj_PI/2
        private constant real SHARD_Z_ANGLE_VAR = SHARD_Z_ANGLE*2
        /*
        *   How often shards spawn
        */
        private constant real SHARD_PERIOD = 0.0625
        /*
        *   How many shards spawn per period
        */
        private constant integer SHARD_COUNT = 2
        /*
        *   Speed of the shards
        */
        private constant real SHARD_SPEED = 200
        private constant real SHARD_SPEED_VAR = 50
        //------------------------------------------------------
        /*
        *   On Spawn effects 
        */
        private constant string ON_SPAWN_SFX = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
        /*
        *   Show creep nova
        */
        private constant boolean SHOW_CREEP_NOVA = true
        /*
        *   Start appearance of the nova creeps
        */
        private constant real NCREEP_START_SCALE = 0.3
        private constant integer NCREEP_START_ALPHA = 200
        private constant integer NCREEP_START_RED = 64
        private constant integer NCREEP_START_GREEN = 64
        private constant integer NCREEP_START_BLUE = 64
        /*
        *   End appearance of the nova creeps
        */
        private constant real NCREEP_END_SCALE = 0.3
        private constant integer NCREEP_END_ALPHA = 0
        private constant integer NCREEP_END_RED = 64
        private constant integer NCREEP_END_GREEN = 64
        private constant integer NCREEP_END_BLUE = 64
        /*
        *   Number of creeps released
        */
        private constant integer NCREEP_COUNT = 32
        /*
        *   Speed of the creeps
        */
        private constant real NCREEP_SPEED = 100
        private constant real NCREEP_SPEED_VAR = 50
        /*
        *   Creep life time 
        */
        private constant real NCREEP_TIME = 3.0
        /*
        *   Animation played by the creep
        */
        private constant integer NCREEP_ANIM = 1
        /*
        *   Nova creep height
        */
        private constant real NCREEP_Z = 0
        //------------------------------------------------------
        /*
        *   Devourer appearances
        */
        private constant real DEVOURER_SCALE = 0.75
        private constant real DEVOURER_SCALE_PER_LVL = 0.25
        
        private constant integer DEVOURER_ALPHA = 200
        private constant integer DEVOURER_RED = 100
        private constant integer DEVOURER_GREEN = 100
        private constant integer DEVOURER_BLUE = 100
        
        private constant real DEVOURER_Z = 0
        
        private constant string DEVOURER_DEATH_SFX = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
        //------------------------------------------------------
        /*
        *   Shadow Trail fields
        */
        private constant boolean SHOW_TRAIL = true
        /*
        *   Trail period
        */
        private constant real TRAIL_PERIOD = 0.15625
        /*
        *   Trail duration
        */
        private constant real TRAIL_DURATION = 1.5
        /*
        *   Trail animation
        */
        private constant integer TRAIL_ANIM = 1
        //------------------------------------------------------
        /*
        *   Aura fields
        */
        private constant boolean SHOW_AURA = true
        /*
        *   Aura creep spawn period
        */
        private constant real AURA_SPAWN_PERIOD = 0.125
        /*
        *   Number of creep spawn
        */
        private constant integer ACREEP_COUNT = 2
        /*
        *   Start appearance of the aura creeps
        */
        private constant real ACREEP_START_SCALE = 0.3
        private constant integer ACREEP_START_ALPHA = 200
        private constant integer ACREEP_START_RED = 64
        private constant integer ACREEP_START_GREEN = 64
        private constant integer ACREEP_START_BLUE = 64
        /*
        *   End appearance of the aura creeps
        */
        private constant real ACREEP_END_SCALE = 0.3
        private constant integer ACREEP_END_ALPHA = 0
        private constant integer ACREEP_END_RED = 64
        private constant integer ACREEP_END_GREEN = 64
        private constant integer ACREEP_END_BLUE = 64
        /*
        *   Aura creep speed
        */
        private constant real ACREEP_SPEED = 200
        private constant real ACREEP_SPEED_VAR = 50
        /*
        *   Aura creep duration
        */
        private constant real ACREEP_TIME = 1.25
        /*
        *   Aura creep animation
        */
        private constant integer ACREEP_ANIM = 1
        /*
        *   Aura creep height
        */
        private constant real ACREEP_Z = 0
        //------------------------------------------------------
        /*
        *   Shockwave fields
        */
        private constant string MANA_RELEASE_SFX = "Abilities\\Spells\\Other\\HowlOfTerror\\HowlCaster.mdl"
        /*
        *   Number of shockwave segments
        */
        private constant integer WAVE_COUNT = 18
        /*
        *   Shockwave appearance
        */
        private constant string WAVE_MODEL = "Abilities\\Spells\\Undead\\OrbOfDeath\\AnnihilationMissile.mdl"
        
        private constant real WAVE_SCALE = 1.25
        private constant integer WAVE_ALPHA = 255
        private constant integer WAVE_RED = 255
        private constant integer WAVE_GREEN = 255
        private constant integer WAVE_BLUE = 255
        
        private constant real WAVE_Z = 32
        //------------------------------------------------------
        /*
        *   Swarm fields
        */
        private constant string SWARM_RELEASE_SFX = ""
        private constant string SWARM_MODEL = "Abilities\\Weapons\\CryptFiendMissile\\CryptFiendMissile.mdl"
        /*
        *   Swarm appearance
        */
        private constant real SWARM_SCALE = 1
        private constant integer SWARM_RED = 255
        private constant integer SWARM_GREEN = 255
        private constant integer SWARM_BLUE = 255
        private constant integer SWARM_ALPHA = 255
        /*
        *   Number of swarm segment
        */
        private constant integer SWARM_COUNT = 5
        /*
        *   How curved the Swarm is 
        *   (the arc is 2D and is perpendicular to the point)
        */
        private constant real SWARM_ARC = 1
        /*
        *   Swarm height
        */
        private constant real SWARM_Z = 32
    endglobals
    /*
    *   Setup Devourer IDs
    */
    globals
        private integer array DEVOURER_ID
    endglobals
    
    private function SetupDevourerId takes nothing returns nothing
        set DEVOURER_ID[1] = 'dev1'
        set DEVOURER_ID[2] = 'dev2'
        set DEVOURER_ID[3] = 'dev3'
    endfunction
    /*
    *   For checking alive units 
    */
    native UnitAlive takes unit u returns boolean
    
    private function ManaFeedValid takes unit u, unit t, player p returns boolean
        return GetUnitState(t, UNIT_STATE_MAX_MANA) > 0 and UnitAlive(t) and t != u // and IsUnitEnemy(t, p)
    endfunction
    
    private function HealthFeedValid takes unit u, unit t, player p returns boolean
        return UnitAlive(t) and t != u // and IsUnitEnemy(t, p)
    endfunction
    
    private function ManaReleaseValid takes unit u, unit t, player p returns boolean
        return u != t and /*
            */ IsUnitEnemy(t, p) and /*
            */ UnitAlive(t) and /*
            */ not IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ not IsUnitType(t, UNIT_TYPE_STRUCTURE)
    endfunction
    
    private function SwarmValid takes unit u, unit t, player p returns boolean
        return u != t and /*
            */ IsUnitEnemy(t, p) and /*
            */ UnitAlive(t) and /*
            */ not IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ not IsUnitType(t, UNIT_TYPE_STRUCTURE)
    endfunction
    /*
    *   For calculating Values with respect to levels
    */
    private function GetLevelValueR takes real base, real increment, integer level returns real
        return base + increment*level
    endfunction
    private function GetLevelValueI takes integer base, integer increment, integer level returns integer
        return base + increment*level
    endfunction
    /*
    *   For calculating value variance
    */
    private function GetVarianceR takes real base, real variance returns real
        if variance == 0 then
            return base
        endif
        return GetRandomReal(base - variance, base + variance)
    endfunction
    private function GetVarianceI takes integer base, integer variance returns integer
        if variance == 0 then
            return base
        endif
        return GetRandomInt(base - variance, base + variance)
    endfunction
    /*
    *   For calculating value over time 
    */
    private function LinearR takes real a, real b, real t returns real
        return a + (b - a)*t
    endfunction
    private function LinearI takes integer a, integer b, real t returns integer
        return R2I(LinearR(I2R(a), I2R(b), t))
    endfunction
    
    /*
    *   Parabola function
    */
    function Parabola takes real x, real d, real h returns real
        return 4*h*x*(d - x)/(d*d)
    endfunction
    /**************************************
    *
    *   For getting the location surface z
    *
    **************************************/
    static if (not LIBRARY_ZLibrary) then
        globals
            private constant location p = Location(0.0, 0.0)
        endglobals
        function GetSurfaceZ takes real x, real y returns real
            call MoveLocation(p, x, y)
            return GetLocationZ(p)
        endfunction
    endif
    /*
    *   Making the unit fly
    */
    private function UnitFly takes unit u returns boolean
        return UnitAddAbility(u, 'Amrf') and UnitRemoveAbility(u, 'Amrf')
    endfunction
    /*
    *   Create a unit without shadows
    */
    private function CreateUnitWithoutShadow takes player owner, integer uid, real x, real y, real facing returns unit
        local image i = CreateImage("Textures\\white.blp", 1, 1, 0, 0, 0, 0, 1, 1, 0, 3)
        call DestroyImage(i)
        set bj_lastCreatedUnit = CreateUnit(owner, uid, x, y, facing)
        call DestroyImage(i)
        call CreateImage("Textures\\white.blp", 1, 1, 0, 0, 0, 0, 1, 1, 0, 3)
        call SetImageRenderAlways(i, false)
        call SetImageColor(i, 0, 0, 0, 0)
        return bj_lastCreatedUnit
    endfunction
    /*
    *   For handling lists and periods
    */
    //! textmacro DEVOURER_LIST_TIMER
        /*
        *   list variables
        */
        private static thistype array next
        private static thistype array prev
        /*
        *   the timer
        */
        private static constant timer t = CreateTimer()
        /*
        *   method that allocates and inserts the allocated
        *   instance to the linked list
        */
        private static method insert takes code c returns thistype
            // allocate new instance
            local thistype this = allocate()
            // insert it to the list
            set next[this] = 0
            set prev[this] = prev[0]
            set next[prev[0]] = this
            set prev[0] = this
            // check if list contains only a single instance
            if prev[this] == 0 then
                // if true, start timer
                call TimerStart(t, TIMEOUT, true, c)
            endif 
            // return instance
            return this
        endmethod
        /*
        *   method that deallocates and removes the allocated
        *   instance from the linked list
        */
        private method remove takes nothing returns nothing
            // dealloc instance
            call deallocate()
            // remove isntance from list
            set next[prev[this]] = next[this]
            set prev[next[this]] = prev[this]
        endmethod
    //! endtextmacro
    /*
    *   Structs for holding appearance values and position values
    */
    private struct Appearance
        integer red
        integer green
        integer blue
        integer alpha
        real scale
    endstruct
    /*
    *   Struct for timed appearances (fade, color change, etc.)
    */
    private struct TimedAppearance
        //! runtextmacro DEVOURER_LIST_TIMER()
        private unit u
        // start and end appearances
        private Appearance start
        private Appearance end
        // timer 
        private real current
        private real max
        
        method destroy takes nothing returns nothing
            call remove()
            
            set u = null
            call start.destroy()
            call end.destroy()
            
            set current = 0
            set max = 0
        endmethod
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            local real pct
            local integer a
            local integer r
            local integer g
            local integer b
            local real s
            loop
                exitwhen 0 == this
                /*
                *   Check if the timer has reached the max time.
                */
                if current < max then
                    /*
                    *   if not, increment by TIMEOUT
                    */
                    set current = current + TIMEOUT
                    /*
                    *   Calculate the progress of the timer
                    */
                    set pct = current/max
                    /*
                    *   apply linear calculation from start values to end values
                    */
                    set a = LinearI(start.alpha, end.alpha, pct)
                    set r = LinearI(start.red, end.red, pct)
                    set g = LinearI(start.green, end.green, pct)
                    set b = LinearI(start.blue, end.blue, pct)
                    set s = LinearR(start.scale, end.scale, pct)
                    /*
                    *   apply the calculated appearance   
                    */
                    call SetUnitVertexColor(u, r, g, b, a)
                    call SetUnitScale(u, s, 0, 0)
                else
                    call destroy()
                endif
                set this = next[this]
            endloop
        endmethod
        
        static method add takes unit temp, Appearance startColor, Appearance endColor, real maxTime returns nothing
            /*
            *   Get the new node
            */
            local thistype this = insert(function thistype.period)
            /*
            *   setup the variables values
            */
            set start = startColor
            set end = endColor
            set current = 0
            set max = maxTime
            set u = temp
            /*
            *   Modify the unit's appearance using the start values
            */
            call SetUnitVertexColor(u, start.red, start.green, start.blue, start.alpha)
            call SetUnitScale(u, start.scale, 0, 0)
        endmethod
    endstruct
    /*
    *   Struct for effect dummies
    */
    private struct Particle
        readonly unit u
        readonly real x
        readonly real y
        readonly real z
        private effect mdl
        static method create takes string sfx, real tx, real ty, real tz, real face returns thistype
            local thistype this = allocate()
            /*
            *   Get the dummy
            */
            set u = GetRecycledUnit(tx, ty, false, face)
            /*
            *   create the vector
            */
            set x = tx
            set y = ty
            set z = GetSurfaceZ(tx, ty) + tz // calculate the true z
            call SetUnitFlyHeight(u, tz, 0)
            /*
            *   attach model
            */
            set mdl = AddSpecialEffectTarget(sfx, u, "origin")
            return this
        endmethod
        method destroy takes nothing returns nothing
            /*
            *   recycle the dummy
            */
            call AddRecycleTimer(u, 2.0)
            /*
            *   destroy the model
            */
            call DestroyEffect(mdl)
            set u = null
            set mdl = null
            
            set x = 0
            set y = 0
            set z = 0
        endmethod
    endstruct
    /*
    *   Projectile motion
    */
    //! textmacro DEV_PROJECTILE
        /*
        *   Polar offsets
        */
        private real ox
        private real oy
        private real oz
        /*
        *   the target data
        */
        private unit target
        private real size
        /*
        *   Speed of the missile (used for homing)
        */
        private real speed
        /*
        *   How long does the missile last (used for missiles that are not homing)
        */
        private real missileTime
        /*
        *   For checking 
        */
        private boolean started
        /*
        *   Clear instance data then execute onImpact method
        */
        private method destroyMissile takes nothing returns nothing
            if started then
                call onImpact(target)
                set target = null
                set speed = 0
                set missileTime = 0
                set started = false
            endif
        endmethod
        /*
        *   Update the movement of the projectile
        */
        private method move takes nothing returns nothing
            local real ux 
            local real uy 
            local real uz 
            local real tx
            local real ty
            local real tz
            local real dx
            local real dy
            local real dz
            local real a2
            local real a3
            /*
            *   Instance is valid?
            */
            if started then
                /*
                *   Get coordinates of the projectile
                */
                set ux = GetUnitX(u)
                set uy = GetUnitY(u)
                set uz = GetSurfaceZ(ux, uy) + GetUnitFlyHeight(u)
                /*
                *   Check if target is alive (also for checking if it is homing
                */
                if UnitAlive(target) then
                    /*
                    *   Get the target's coordinates
                    */
                    set tx = GetUnitX(target)
                    set ty = GetUnitY(target)
                    set tz = GetSurfaceZ(tx, ty) + GetUnitFlyHeight(target)
                    /*
                    *   Get coordinate differences
                    */
                    set dx = tx - ux
                    set dy = ty - uy
                    set dz = tz - uz
                    /*
                    *   Check if missile has already hit the target
                    */
                    if GetMagnitude3D(dx, dy, dz) <= size then
                        /*
                        *   Destroy if true
                        */ 
                        call destroyMissile()
                    endif
                    /*
                    *   Calculate 2D angle and 3D pitch
                    */
                    set a2 = Atan2(dy, dx)
                    set a3 = Atan2(dz, GetMagnitude2D(dx, dy))
                    /*
                    *   Update coordinates
                    */
                    set ux = ux + speed*Cos(a2)*Cos(a3) 
                    set uy = uy + speed*Sin(a2)*Cos(a3)
                    set uz = uz + speed*Sin(a3)
                    /*
                    *   For safe coordinates
                    */
                    set ux = GetBoundedX(ux)
                    set uy = GetBoundedY(uy)
                    set uz = uz - GetSurfaceZ(ux, uy)
                    /*
                    *   Coordinates is safe, move the unit
                    */
                    call SetUnitX(u, ux)
                    call SetUnitY(u, uy)
                    call SetUnitFlyHeight(u, uz, 0)
                    /*
                    *   Set the facing towards the target
                    */
                    call SetUnitFacing(u, a2*bj_RADTODEG)
                    /*
                    *   If the unit is a dummy, update pitch
                    */
                    if GetUnitTypeId(u) == DUMMY_ID then
                        call SetUnitAnimationByIndex(u, R2I(a3*bj_RADTODEG + 90.5))
                    endif
                elseif missileTime > 0 then
                    /*
                    *   Update missile time 
                    */
                    set missileTime = missileTime - TIMEOUT
                    /*
                    *   Update coordinates
                    */
                    set ux = GetBoundedX(ux + ox)
                    set uy = GetBoundedY(uy + oy)
                    call SetUnitX(u, ux)
                    call SetUnitY(u, uy)
                    call SetUnitFlyHeight(u, (uz + oz) - GetSurfaceZ(ux, uy), 0) 
                else
                    /*
                    *   Unit has no target and missileTime, destroy 
                    */
                    call destroyMissile()
                endif
            endif
        endmethod
        
        /*
        *   To set the target
        */
        private method setTarget takes unit temp returns nothing
            set target = temp
            set size = GetUnitCollision(temp)
            if GetUnitTypeId(temp) == DUMMY_ID then
                /*
                *   If the unit is a dummy, give it a larger collision size
                */
                set size = size + DUMMY_COLLISION_SIZE
            endif
        endmethod
        /*
        *   To set the time 
        */
        private method setTime takes real time returns nothing
            set missileTime = time
        endmethod
        /*
        *   To launch the missile
        */
        private method start takes real vel, real angle2d, real angle3d returns nothing
            set speed = vel*TIMEOUT
            set ox = speed*Cos(angle2d)*Cos(angle3d)
            set oy = speed*Sin(angle2d)*Cos(angle3d)
            set oz = speed*Sin(angle3d)
            
            set started = true
        endmethod
    //! endtextmacro
    /*
    *   Struct for creeps
    */
    struct Creep
        /*
        *   Implement list
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The creep
        */
        private unit u
        /*
        *   Missile required method
        */
        method onImpact takes unit temp returns nothing
            call remove()
            call RemoveUnit(u)
            set u = null
        endmethod
        /*
        *   Implement missile
        */
        //! runtextmacro DEV_PROJECTILE()
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            loop
                exitwhen 0 == this
                call move()
                set this = next[this]
            endloop
        endmethod
        
        static method spawn takes player p, real x, real y, boolean aura returns nothing
            /*
            *   Get the new instance
            */
            local thistype this = insert(function thistype.period)
            local Appearance start = Appearance.create()
            local Appearance end = Appearance.create()
            /*
            *   Generate random angle
            */
            local real angle2d = GetRandomReal(-bj_PI, bj_PI)
            local real velocity
            local real time 
            local real z
            /*
            *   There are two types of creeps(see constants) : Aura and Nova
            */
            if aura then
                set start.red = ACREEP_START_RED
                set start.green = ACREEP_START_GREEN
                set start.blue = ACREEP_START_BLUE
                set start.alpha = ACREEP_START_ALPHA
                set start.scale = ACREEP_START_SCALE
                
                set end.red = ACREEP_END_RED
                set end.green = ACREEP_END_GREEN
                set end.blue = ACREEP_END_BLUE
                set end.alpha = ACREEP_END_ALPHA
                set end.scale = ACREEP_END_SCALE
                
                set velocity = GetVarianceR(ACREEP_SPEED, ACREEP_SPEED_VAR)
                
                set time = ACREEP_TIME
                
                set z = ACREEP_Z
            else
                set start.red = NCREEP_START_RED
                set start.green = NCREEP_START_GREEN
                set start.blue = NCREEP_START_BLUE
                set start.alpha = NCREEP_START_ALPHA
                set start.scale = NCREEP_START_SCALE
                
                set end.red = NCREEP_END_RED
                set end.green = NCREEP_END_GREEN
                set end.blue = NCREEP_END_BLUE
                set end.alpha = NCREEP_END_ALPHA
                set end.scale = NCREEP_END_SCALE
                
                set velocity = GetVarianceR(NCREEP_SPEED, NCREEP_SPEED_VAR)
                
                set time = NCREEP_TIME
                
                set z = NCREEP_Z
            endif
            /*
            *   Create the unit without shadow
            */
            set u = CreateUnitWithoutShadow(p, CREEP_ID, x, y, angle2d*bj_RADTODEG)
            /*
            *   To prevent the unit from displacing
            */
            call SetUnitPathing(u, false)
            call SetUnitX(u, x)
            call SetUnitY(u, y)
            /*
            *   Make the unit a "dummy"
            */
            call PauseUnit(u, true)
            call UnitAddAbility(u, 'Aloc')
            call UnitFly(u)
            call SetUnitFlyHeight(u, z, 0)
            call SetUnitUseFood(u, false)
            call SetUnitTurnSpeed(u, 1000)
            /*
            *   Play the animation
            */
            if aura then
                call SetUnitAnimationByIndex(u, ACREEP_ANIM)
            else
                call SetUnitAnimationByIndex(u, NCREEP_ANIM)
            endif
            /*
            *   Launch the unit as a missile
            */
            call setTime(time)
            call start(velocity, angle2d, 0)
            /*
            *   Apply timed appearance
            */
            call TimedAppearance.add(u, start, end, time)
        endmethod
    endstruct
    /*
    *   Struct for portal shards
    */
    private struct Shard
        /*
        *   Implement the list timer 
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The shard.
        *   Note: "u" is required by the Missile textmacro
        */
        private Particle pt
        private unit u
        /*
        *   Required missile method
        */
        method onImpact takes unit temp returns nothing
            call remove()
            call pt.destroy()
            set u = null
        endmethod
        /*
        *   Implement missile
        */
        //! runtextmacro DEV_PROJECTILE()
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            loop
                exitwhen 0 == this
                call move()
                set this = next[this]
            endloop
        endmethod
       
        static method spawn takes Particle p returns nothing
            /*
            *   Get new instance
            */
            local thistype this = insert(function thistype.period)
            /*
            *   Get the spawn variance
            */
            local real a2 = GetVarianceR(SHARD_ANGLE, SHARD_ANGLE_VAR)
            local real a3 = GetVarianceR(SHARD_Z_ANGLE, SHARD_Z_ANGLE_VAR)
            local real vel = GetVarianceR(SHARD_SPEED, SHARD_SPEED_VAR)
            local real dist = GetVarianceR(SHARD_DISTANCE, SHARD_DISTANCE_VAR)
            /*
            *   Calculate the spawn coordinates
            */
            local real x = p.x + dist*Cos(a2)*Cos(a3)
            local real y = p.y + dist*Sin(a2)*Cos(a3)
            local real z = p.z + dist*Sin(a3)
            /*
            *   Calculate the time it takes to travel towards the target
            */
            local real time = dist/vel
            local Appearance start
            local Appearance end 
            /*
            *   Create shard
            */
            set pt = Particle.create(SHARD_MODEL, x, y, z, a2*bj_RADTODEG + 180)
            set u = pt.u
            /*
            *   Launch missile
            */
            call setTarget(p.u)
            call start(vel, 0, 0)
            /*
            *   Create timed appearance
            */
            set start = Appearance.create()
            set end = Appearance.create()
            
            set start.red = SHARD_START_RED
            set start.green = SHARD_START_GREEN
            set start.blue = SHARD_START_BLUE
            set start.alpha = SHARD_START_ALPHA
            set start.scale = SHARD_START_SCALE
            
            set end.red = SHARD_END_RED
            set end.green = SHARD_END_GREEN
            set end.blue = SHARD_END_BLUE
            set end.alpha = SHARD_END_ALPHA
            set end.scale = SHARD_END_SCALE
            call TimedAppearance.add(u, start, end, time)
        endmethod
    endstruct
    /*
    *   Struct for swarm
    */
    private struct Swarm
        /*
        *   Implement list
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The particles that travel
        */
        private Table particles
        /*
        *   the source of damage
        */
        private unit own
        private player owner
        
        private real damage
        private real radius 
        /*
        *   Position of swarm
        */
        private real x
        private real y
        /*
        *   Polar offset
        */
        private real ox
        private real oy
        /*
        *   Duration of swarm
        */
        private real time
        /*
        *   For saving units who are already damaged
        */
        private Table damagedUnits
        
        private static group g = CreateGroup()
        
        method destroy takes nothing returns nothing
            local integer i = 0
            /*
            *   Remove instance
            */
            call remove()
            /*
            *   If the swarm has particles, destroy them
            */
            if SWARM_COUNT > 0 then
                loop
                    set i = i + 1
                    call Particle(particles.integer[i]).destroy()
                    exitwhen i >= SWARM_COUNT
                endloop
                call particles.destroy()
            endif
            /*
            *   Clear data
            */
            call damagedUnits.destroy()
            set own = null
            set owner = null
            set x = 0
            set y = 0
            set ox = 0
            set oy = 0
            set radius = 0
            set damage = 0
            set time = 0
        endmethod
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            local integer i
            local unit u
            loop
                exitwhen 0 == this
                /*
                *   Check if swarm can still travel
                */
                if time > 0 then
                    /*
                    *   Update time 
                    */
                    set time = time - TIMEOUT
                    /*
                    *   Move the swarm safely
                    */
                    set x = GetBoundedX(x + ox)
                    set y = GetBoundedY(y + oy)
                    /*
                    *   Enum units 
                    */
                    call GroupEnumUnitsInRange(g, x, y, radius, null)
                    loop
                        set u = FirstOfGroup(g)
                        exitwhen u == null
                        set i = GetHandleId(u)
                        /*
                        *   Check if unit is valid through filter and if it has not yet damaged
                        */
                        if SwarmValid(own, u, owner) and not damagedUnits.boolean[i] then
                            /*
                            *   Add the unit to damagedUnits then deal damage to it
                            */
                            set damagedUnits.boolean[i] = true
                            call UnitDamageTarget(own, u, damage, true, false, SWARM_DAMAGE_ATKTYPE, SWARM_DAMAGE_DMGTYPE, null)
                        endif
                        /*
                        *   Remove unit from group
                        */
                        call GroupRemoveUnit(g, u)
                    endloop
                    /*
                    *   If swarm has particles, update their positions parallel to the swarm
                    */
                    if SWARM_COUNT > 0 then
                        set i = 0
                        loop
                            set i = i + 1
                            set u = particles.unit[SWARM_COUNT + i]
                            call SetUnitX(u, GetBoundedX(GetUnitX(u) + ox))
                            call SetUnitY(u, GetBoundedY(GetUnitY(u) + oy))
                            exitwhen i >= SWARM_COUNT
                        endloop
                    endif
                else
                    call destroy()
                endif
                
                set this = next[this]
            endloop
        endmethod
        
        static method spawn takes unit source, unit target, integer level returns nothing
            local thistype this = insert(function thistype.period)
            local real tx = GetUnitX(target)
            local real ty = GetUnitY(target)
            local real a2
            local real facing
            local real offset = GetLevelValueR(SWARM_SPAWN_OFFSET, SWARM_SPAWN_OFFSET_PER_LVL, level)
            local integer i
            
            local real sx
            local real sy
            local real ex
            local real ey
            local real cx
            local real cy
            local real arc
            local real ri
            local real max
            local Particle p
            /*
            *   Get the source coordinates
            */
            set x = GetUnitX(source)
            set y = GetUnitY(source)
            /*
            *   Get the angle towards the target
            */
            set a2 = Atan2(ty - y, tx - x)
            /*
            *   Move the swarm position by offset
            */
            set ox = SWARM_SPEED*TIMEOUT*Cos(a2)
            set oy = SWARM_SPEED*TIMEOUT*Sin(a2)
            
            set x = x + offset*Cos(a2)
            set y = y + offset*Sin(a2)
            /*
            *   Setup data
            */
            set damagedUnits = Table.create()
            set damage = GetLevelValueR(SWARM_DAMAGE, SWARM_DAMAGE_PER_LVL, level)
            set radius = GetLevelValueR(SWARM_RADIUS, SWARM_RADIUS_PER_LVL, level)
            
            set own = source
            set owner = GetOwningPlayer(own)
            /*
            *   If the constant has a swarm count, create the particles
            */
            set i = 0
            if SWARM_COUNT > 0 then
                set particles = Table.create()
                set facing = a2*bj_RADTODEG
                /*
                *   Calculate the perpendicular position
                */
                set ex = x + radius*Cos(a2 + bj_PI/2)
                set ey = y + radius*Sin(a2 + bj_PI/2)
                
                set sx = x + radius*Cos(a2 - bj_PI/2)
                set sy = y + radius*Sin(a2 - bj_PI/2)
                set max = I2R(SWARM_COUNT) + 1
                loop
                    set i = i + 1
                    set ri = I2R(i)
                    /*
                    *   Get the position
                    */
                    set cx = LinearR(sx, ex, ri/max)
                    set cy = LinearR(sy, ey, ri/max)
                    /*
                    *   Calculate parabola offset 
                    */
                    set arc = Parabola(ri, max, SWARM_ARC*radius)
                    /*
                    *   Update position
                    */
                    set cx = cx + arc*Cos(a2)
                    set cy = cy + arc*Sin(a2)
                    /*
                    *   Create particle
                    */
                    set p = Particle.create(SWARM_MODEL, cx, cy, SWARM_Z, facing)
                    set particles.integer[i] = p
                    set particles.unit[SWARM_COUNT + i] = p.u
                    /*
                    *   Apply appearance changes
                    */
                    call SetUnitScale(p.u, SWARM_SCALE, 0, 0)
                    call SetUnitVertexColor(p.u, SWARM_RED, SWARM_GREEN, SWARM_BLUE, SWARM_ALPHA)
                    exitwhen i >= SWARM_COUNT
                endloop
            endif
            /*
            *   Calculate the time needed
            */
            set time = GetLevelValueR(SWARM_TRAVEL_DIST, SWARM_TRAVEL_DIST_PER_LVL, level)/SWARM_SPEED
            /*
            *   Create release sfx
            */
            if SWARM_RELEASE_SFX != "" then
                call DestroyEffect(AddSpecialEffect(SWARM_RELEASE_SFX, x, y))
            endif
        endmethod
    endstruct 
    /*
    *   Struct for the shockwave (mana release)
    */
    private struct Shockwave
        /*
        *   Implement list
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The particles
        */
        private Table particles
        /*
        *   The damage source
        */
        private player pOwner
        private unit owner 
        /*
        *   The center of shockwave
        *   This is needed if the shockwave is not locked
        *   to it's owner
        */
        private real x
        private real y
        
        private real damage
        /*
        *   The time it takes for the shockwave to expand
        */
        private real expandTime 
        /*
        *   The max shockwave radius
        */
        private real maxRadius
        /*
        *   Same concept as the swarm
        */
        private Table damagedUnits
        
        private static group g = CreateGroup()
        
        method destroy takes nothing returns nothing
            local integer i = 0
            /*
            *   Remove instance
            */
            call remove()
            /*
            *   If the shockwave as particles, destroy each
            */
            if WAVE_COUNT > 0 then
                loop
                    set i = i + 1
                    call Particle(particles[i]).destroy()
                    exitwhen i >= WAVE_COUNT
                endloop
                call particles.destroy()
            endif
            call damagedUnits.destroy()
            /*
            *   Clear data
            */
            set pOwner = null
            set owner = null
            set x = 0
            set y = 0
            
            set damage = 0
            set expandTime = 0
            set maxRadius = 0
        endmethod
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            
            local integer i
            
            local unit t
            local integer id
            
            local real radius
            local real pct
            
            local real angle
            loop
                exitwhen 0 == this
                /*
                *   If the shockwave is locked and the source
                *   is alive, move the shockwave
                */
                if MANA_RELEASED_LOCKED and UnitAlive(owner) then
                    set x = GetUnitX(owner)
                    set y = GetUnitY(owner)
                endif
                /*
                *   Check if the shockwave is still growing
                */
                if expandTime < MANA_RELEASE_GROW_TIME  then
                    /*
                    *   update time
                    */
                    set expandTime = expandTime + TIMEOUT
                    /*
                    *   Calculate the progress of the growth
                    */
                    set pct = expandTime/MANA_RELEASE_GROW_TIME
                    /*
                    *   Calculate the radius of the shockwave
                    */
                    set radius = maxRadius*pct
                    /*
                    *   Enum units
                    */
                    call GroupEnumUnitsInRange(g, x, y, radius, null)
                    loop
                        set t = FirstOfGroup(g)
                        exitwhen t == null
                        set id = GetHandleId(t)
                        /*
                        *   Same mechanics as to the swarm
                        */
                        if ManaReleaseValid(owner, t, pOwner) and not damagedUnits.boolean[id]  then
                            call UnitDamageTarget(owner, t, damage, true, false, MANA_RELEASE_ATKTYPE, MANA_RELEASE_DMGTYPE, null)
                            set damagedUnits.boolean[id] = true
                        endif
                        call GroupRemoveUnit(g, t)
                    endloop
                    /*
                    *   If the shockwave has particles, update their position
                    */
                    if WAVE_COUNT > 0 then
                        set i = 0
                        set angle = (bj_PI*2)/I2R(WAVE_COUNT)
                        loop
                            set i = i + 1
                            call SetUnitX(particles.unit[WAVE_COUNT + i], GetBoundedX(x + radius*Cos(angle*I2R(i))))
                            call SetUnitY(particles.unit[WAVE_COUNT + i], GetBoundedY(y + radius*Sin(angle*I2R(i))))
                            exitwhen i >= WAVE_COUNT
                        endloop
                    endif
                else 
                    call destroy()
                endif
                set this = next[this]
            endloop
        endmethod
        
        static method start takes unit o, player own, integer level returns nothing
            local thistype this = insert(function thistype.period)
            local integer i
            local real angle
            local Particle p
            
            set owner = o
            set pOwner = own
            set x = GetUnitX(o)
            set y = GetUnitY(o)
            
            set expandTime = 0
            
            set damagedUnits = Table.create()
            set maxRadius = GetLevelValueR(MANA_RELEASE_RADIUS, MANA_RELEASE_RADIUS_PER_LVL, level)
            set damage = GetLevelValueR(MANA_RELEASE_DMG, MANA_RELEASE_DMG_PER_LVL, level)
            
            set i = 0
            /*
            *   If the shockwave can create particles, create them
            */
            if WAVE_COUNT > 0 then
                set particles = Table.create()
                /*
                *   Calculate angle distribution
                */
                set angle = 360/I2R(WAVE_COUNT)
                loop
                    set i = i + 1
                    /*
                    *   Create particles
                    */
                    set p = Particle.create(WAVE_MODEL, x, y, WAVE_Z, angle*I2R(i))
                    set particles[i] = p
                    set particles.unit[WAVE_COUNT + i] = p.u
                    /*
                    *   Update their appearances
                    */
                    call SetUnitVertexColor(p.u, WAVE_RED, WAVE_GREEN, WAVE_BLUE, WAVE_ALPHA)
                    call SetUnitScale(p.u, WAVE_SCALE, 0, 0)
                    exitwhen i >= WAVE_COUNT
                endloop
            endif
            /*
            *   If the shockwave has a release effect, create it
            */
            if MANA_RELEASE_SFX != "" then
                call DestroyEffect(AddSpecialEffect(MANA_RELEASE_SFX, x, y))
            endif
        endmethod
    endstruct
    /*
    *   Shadow trail
    */
    private struct DevourerTrail
        /*
        *   Implement list
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The trail
        */
        private unit u
        /*
        *   Trail duration
        */
        private real time 
        method destroy takes nothing returns nothing
            call remove()
            call RemoveUnit(u)
            set u = null
            set time = 0
        endmethod
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            loop
                exitwhen 0 == this
                if time > 0 then
                    set time = time - TIMEOUT
                else 
                    call destroy()
                endif
                set this = next[this]
            endloop
        endmethod
        static method create takes unit own, player p, integer lvl returns thistype
            local thistype this = insert(function thistype.period)
            local Appearance start = Appearance.create()
            local Appearance end = Appearance.create()
            local real x = GetUnitX(own)
            local real y = GetUnitY(own)
            /*
            *   Create the unit without shadow (same concept as to Creep struct)
            */  
            set u = CreateUnitWithoutShadow(p, DEVOURER_ID[lvl], x, y, GetUnitFacing(own))
            /*
            *   In case the unit is displaced
            */
            call SetUnitPathing(u, false)
            call SetUnitX(u, x)
            call SetUnitY(u, y)
            /*
            *   Make the unit a "dummy"
            */
            call PauseUnit(u, true)
            call UnitAddAbility(u, 'Aloc')
            call UnitFly(u)
            call SetUnitFlyHeight(u, DEVOURER_Z, 0)
            call SetUnitUseFood(u, false)
            call SetUnitTurnSpeed(u, 1000)
            /*
            *   Play the animation
            */
            call SetUnitAnimationByIndex(u, TRAIL_ANIM)
            
            set time = TRAIL_DURATION
            /*
            *   Apply timed appearance
            */
            set start.red = DEVOURER_RED
            set start.green = DEVOURER_GREEN
            set start.blue = DEVOURER_BLUE
            set start.alpha = DEVOURER_ALPHA
            set start.scale = GetLevelValueR(DEVOURER_SCALE, DEVOURER_SCALE_PER_LVL, lvl)
            
            set end.red = start.red 
            set end.green = start.green
            set end.blue = start.blue
            set end.alpha = 0
            set end.scale = start.scale
            
            call TimedAppearance.add(u, start, end, time)
            return this
        endmethod
    endstruct
    
    private struct Devourer
        //! runtextmacro DEVOURER_LIST_TIMER()
        private static Table id
        private unit u
        private integer level
        
        private real time
        private real timeBonus
        
        private Table healthTable
        private real healthMax
        private real healthStorage
        private real healthRadius
        private real healthRate
        
        private Table manaTable
        private real manaMax
        private real manaStorage
        private real manaRadius
        private real manaCD
        private real manaCDMax
        
        private real swarmMax
        private real swarmCD
            
        private player owner
        
        private real trailPeriod
        private real prevX
        private real prevY
        
        private real auraPeriod
        
        private static group g = CreateGroup()
        
        method destroy takes nothing returns nothing
            call remove()
            /*
            *   If the devourer as death sfx
            */
            if DEVOURER_DEATH_SFX != "" then
                call DestroyEffect(AddSpecialEffect(DEVOURER_DEATH_SFX, GetUnitX(u), GetUnitY(u)))
            endif
            /*
            *   Destroy health and mana table
            */
            call healthTable.destroy()
            call manaTable.destroy()
            call id.remove(GetHandleId(u))
            call RemoveUnit(u)
            /*
            *   Clear data
            */
            set u = null
            set level = 0
            
            set healthMax = 0
            set healthStorage = 0
            set healthRadius = 0
            set healthRate = 0
            set manaMax = 0
            set manaStorage = 0
            set manaRadius = 0
            set manaCD = 0
            set manaCDMax = 0
            
            set swarmMax = 0
            set swarmCD = 0
            
            set owner = null
            
            set trailPeriod = 0
            set prevX = 0
            set prevY = 0
            
            set auraPeriod = 0
        endmethod
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            
            local unit t
            local integer id
            
            local real prevLife
            local real newLife
            local real lifeDif
            
            local real prevMana
            local real newMana
            local real manaDif
            
            local integer i
            local real x
            local real y
            loop
                exitwhen 0 == this
                /*
                *   Check if the devourer's timed life has ended or if it still alive
                */
                if time > 0 and UnitAlive(u) then
                    /*
                    *    update time 
                    */
                    set time = time - TIMEOUT
                    /*
                    *   Get devourer position   
                    */
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    /*
                    *   If the spell is allowed to create trails
                    */
                    if SHOW_TRAIL then
                        /*
                        *   Check if trailPeriod has ended
                        */
                        if trailPeriod > 0 then
                            set trailPeriod = trailPeriod - TIMEOUT
                        /*
                        *   Check if the devourer has changed position
                        */
                        elseif prevX != x and prevY != y then
                            /*
                            *   If true, turn the trail cooldown on
                            */
                            set trailPeriod = TRAIL_PERIOD
                            /*
                            *   Create the trail
                            */
                            call DevourerTrail.create(u, owner, level)
                            /*
                            *   Save the devourer's position
                            */
                            set prevX = x
                            set prevY = y
                        endif
                    endif
                    /*
                    *   If the spell is allowed to show the aura
                    */
                    if SHOW_AURA then
                        if auraPeriod > 0 then
                            set auraPeriod = auraPeriod - TIMEOUT
                        else
                            set auraPeriod = AURA_SPAWN_PERIOD
                            set i = ACREEP_COUNT
                            loop
                                exitwhen i == 0
                                call Creep.spawn(owner, x, y, true)
                                set i = i - 1
                            endloop
                        endif
                    endif
                    /*
                    *   Check if the devourer can monitor the health
                    */
                    if 0 < healthRadius then
                        /*
                        *   Enum units
                        */
                        call GroupEnumUnitsInRange(g, x, y, healthRadius, null)
                        loop
                            set t = FirstOfGroup(g)
                            exitwhen t == null
                            /*
                            *   Check for unit validity
                            */
                            if HealthFeedValid(u, t, owner) then
                                /*
                                *   Get the handle id of the unit
                                */
                                set id = GetHandleId(t)
                                /*
                                *   Get the recorded life of the unit
                                */
                                set prevLife = healthTable.real[id]
                                /*
                                *   Get the current life of the unit 
                                */
                                set newLife = GetWidgetLife(t)
                                /*
                                *   Save the current life
                                */
                                set healthTable.real[id] = newLife
                                /*
                                *   Check if the unit has lost some health
                                */
                                set lifeDif = prevLife - newLife
                                /*
                                *   If true, store the lost health
                                */
                                if lifeDif > 0 then
                                    set healthStorage = healthStorage + lifeDif
                                    /*
                                    *   Heal the devourer
                                    */
                                    call SetWidgetLife(u, GetWidgetLife(u) + lifeDif*healthRate)
                                    /*
                                    *   If the health storage has reached it's max value
                                    *   increment the timed life
                                    */
                                    if healthStorage >= healthMax then
                                        set healthStorage = 0
                                        set time = time + timeBonus
                                    endif
                                endif
                            endif
                            
                            call GroupRemoveUnit(g, t)
                        endloop
                    endif
                    /*
                    *   Check if the devourer can monitor the mana
                    */
                    if 0 < manaRadius then
                        /*
                        *   Update shockwave cooldown
                        */
                        if manaCD > 0 then
                            set manaCD = manaCD - TIMEOUT
                        endif
                        /*
                        *   Enum units
                        */
                        call GroupEnumUnitsInRange(g, x, y, manaRadius, null)
                        loop
                            set t = FirstOfGroup(g)
                            exitwhen t == null
                            /*
                            *   Validate unit
                            */
                            if ManaFeedValid(u, t, owner) then
                                /*
                                *   Get the handle id of the unit 
                                */
                                set id = GetHandleId(t)
                                /*
                                *   Get the previous mana of the unit
                                */
                                set prevMana = manaTable.real[id]
                                /*
                                *   Get the current mana
                                */
                                set newMana = GetUnitState(t, UNIT_STATE_MANA)
                                /*
                                *   Save the new mana
                                */
                                set manaTable.real[id] = newMana
                                /*
                                *   Check if the unit can collect mana while on cooldown
                                */
                                if COLLECT_MANA_ON_CD or manaCD <= 0 then
                                    /*
                                    *   get mana difference
                                    */
                                    set manaDif = prevMana - newMana
                                    /*
                                    *   if the unit has lost mana, add it to the storage
                                    */
                                    if manaDif > 0 then
                                        set manaStorage = manaStorage + manaDif
                                        /*
                                        *   Check if the mana storage has reached it's max value
                                        */
                                        if manaStorage >= manaMax and manaCD <= 0 then
                                            /*
                                            *   Empty the storage, start the cooldown then release the shockwave
                                            */
                                            set manaStorage = 0
                                            set manaCD = manaCDMax
                                            call Shockwave.start(u, owner, level)
                                        endif
                                    endif
                                endif
                            endif
                            
                            call GroupRemoveUnit(g, t)
                        endloop
                    endif
                    /*
                    *   Update the swarm release cooldown
                    */
                    if swarmCD > 0 then
                        set swarmCD = swarmCD - TIMEOUT
                    endif
                else
                    call destroy()
                endif
                
                set this = next[this]
            endloop
        endmethod
        
        static method summon takes player p, real x, real y, integer lvl returns nothing
            local thistype this = insert(function thistype.period)
            local integer i
            /*
            *   Create the devourer
            */
            set u = CreateUnit(p, DEVOURER_ID[lvl], x, y, 270)
            /*
            *   Apply appearance changes
            */
            call SetUnitVertexColor(u, DEVOURER_RED, DEVOURER_GREEN, DEVOURER_BLUE, DEVOURER_ALPHA)
            call SetUnitScale(u, GetLevelValueR(DEVOURER_SCALE, DEVOURER_SCALE_PER_LVL, lvl), 0, 0)
            call UnitFly(u)
            call SetUnitFlyHeight(u, DEVOURER_Z, 0)
            /*
            *   Setup all data
            */
            set owner = p
            set level = lvl

            set time = GetLevelValueR(TIMED_LIFE, TIMED_LIFE_PER_LVL, lvl)
            set timeBonus = GetLevelValueR(TIMED_LIFE_BONUS, TIMED_LIFE_BONUS_PER_LVL, lvl)
            
            set manaTable = Table.create()
            set manaMax = GetLevelValueR(MANA_RELEASE_REQ, MANA_RELEASE_REQ_PER_LVL, lvl)
            set manaStorage = 0
            set manaCD = 0
            set manaCDMax = GetLevelValueR(MANA_RELEASE_CD, MANA_RELEASE_CD_PER_LVL, lvl)
            set manaRadius = GetLevelValueR(MANA_FEED_RADIUS, MANA_FEED_RADIUS_PER_LVL, lvl)
            
            set healthTable = Table.create()
            set healthMax = GetLevelValueR(TIMED_LIFE_REQ, TIMED_LIFE_REQ_PER_LVL, lvl)
            set healthStorage = 0
            set healthRate = GetLevelValueR(HEALTH_RATE, HEALTH_RATE_PER_LVL, lvl)
            set healthRadius = GetLevelValueR(HEALTH_FEED_RADIUS, HEALTH_FEED_RADIUS_PER_LVL, lvl)
            
            set swarmMax = GetLevelValueR(SWARM_CD, SWARM_CD_PER_LVL, lvl)
            
            /*
            *   If the devourer is displaced 
            */
            set x = GetUnitX(u)
            set y = GetUnitY(u)
            /*
            *   If the spell is allowed to create trail, save the position
            */
            if SHOW_TRAIL then
                set prevX = x
                set prevY = y
            endif
            /*
            *   Create the creep nova if it is allowed
            */
            if SHOW_CREEP_NOVA then
                set i = NCREEP_COUNT
                loop
                    exitwhen i == 0
                    call Creep.spawn(p, x, y, false)
                    set i = i - 1
                endloop
            endif
            /*
            *   Create spawn sfx
            */
            if ON_SPAWN_SFX != "" then
                call DestroyEffect(AddSpecialEffect(ON_SPAWN_SFX, x, y))
            endif
            /*
            *   Save the instance (For swarm proc)
            */
            set id[GetHandleId(u)] = this
        endmethod
        
        static method onAttack takes nothing returns boolean
            local unit source = GetAttacker()
            local unit victim = GetTriggerUnit()
            local thistype this = id[GetHandleId(source)]
            /*
            *   If unit is a valid devourer
            */
            if this != 0 then
                /*
                *   Check if target is valid and swarm is not in cooldown
                */
                if swarmCD <= 0 and SwarmValid(source, victim, owner) then
                    set swarmCD = swarmMax
                    call Swarm.spawn(source, victim, level)
                endif
            endif
            set source = null
            set victim = null
            return false
        endmethod
        
        static method init takes nothing returns nothing
            set id = Table.create()
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ATTACKED, function thistype.onAttack)
        endmethod
    endstruct
    
    private struct SummonDevourer
        /*
        *   Implement list
        */
        //! runtextmacro DEVOURER_LIST_TIMER()
        /*
        *   The owner of the portal
        */
        private player owner
        
        private Particle portal
        private real time
        /*
        *   For spawning shards
        */
        private real spawnTime
        
        private integer level
        
        method destroy takes nothing returns nothing
            call remove()
            call portal.destroy()
            
            set owner = null
            set time = 0
            set spawnTime = 0
            set level = 0
        endmethod
        
        private static method period takes nothing returns nothing
            local thistype this = next[0]
            local integer i
            loop
                exitwhen 0 == this
                /*
                *   Check if portal is not dying
                */
                if time > 0 then
                    /*
                    *   Update time 
                    */
                    set time = time - TIMEOUT
                    /*
                    *   Create shards if allowed
                    */
                    if SHOW_SHARDS then
                        if spawnTime > 0 then
                            set spawnTime = spawnTime - TIMEOUT
                        else
                            set spawnTime = SHARD_PERIOD
                            set i = SHARD_COUNT
                            loop
                                exitwhen i == 0
                                call Shard.spawn(portal)
                                set i = i - 1
                            endloop
                        endif
                    endif
                else
                    /*
                    *   Create the devourer at current position
                    */
                    call Devourer.summon(owner, portal.x, portal.y, level) 
                    call destroy()
                endif
                set this = next[this]
            endloop
        endmethod
        
        private static real checkY
        private static real checkX
        private static real checkRadius
        private static integer treeCount
        private static Table trees
        
        private static method treeFilter takes nothing returns boolean
            local real dx
            local real dy
            local destructable dest = GetFilterDestructable()
            /*
            *   Get the coordinate differences
            */
            set dx = GetDestructableX(dest) - checkX 
            set dy = GetDestructableY(dest) - checkY
            /*
            *   Check if 
            *       - The destructable is within range
            *       - The destructable is a tree and alive
            */
            if dx*dx + dy*dy <= checkRadius and IsDestructableTree(dest) and IsDestructableAlive(dest) then
                /*
                *   Increment tree count
                */
                set treeCount = treeCount + 1
                set trees.destructable[treeCount] = dest
            endif
            set dest = null
            return false
        endmethod
        
        static method onCast takes nothing returns boolean
            local thistype this
            local unit caster = GetTriggerUnit()
            local real x = GetSpellTargetX()
            local real y = GetSpellTargetY()
            local Appearance start
            local Appearance end
            local integer lvl = GetUnitAbilityLevel(caster, ABIL_ID)
            local real radius = GetLevelValueR(TREE_RADIUS_CHECK, TREE_RADIUS_CHECK_PER_LVL, lvl)
            /*
            *   Create the rect for tree check 
            */
            local rect r = Rect(x - radius, y - radius, x + radius, y + radius)
            /*
            *   Prepare tree checking 
            */
            call trees.flush()
            set treeCount = 0
            set checkX = x
            set checkY = y
            set checkRadius = radius*radius
            call EnumDestructablesInRect(r, Filter(function thistype.treeFilter), null)
            /*
            *   If the number of trees pass the requirement
            */
            if treeCount >= GetLevelValueI(REQUIRED_TREES, REQUIRED_TREES_PER_LVL, lvl) then
                /*
                *   Destroy all trees
                */
                loop
                    exitwhen treeCount == 0
                    call KillTree(trees.destructable[treeCount])
                    set treeCount = treeCount - 1
                endloop
                /*
                *   Create portal
                */
                set this = insert(function thistype.period)
                set level = lvl
                set owner = GetTriggerPlayer()
                set portal = Particle.create(PORTAL_MODEL, x, y, PORTAL_Z, 270)
                set time = GetLevelValueR(SPAWN_DELAY, SPAWN_DELAY_PER_LVL, level)
                /*
                *   Apply timed appearance
                */
                set start = Appearance.create()
                set end = Appearance.create()
                
                set start.red = PORTAL_START_RED
                set start.green = PORTAL_START_GREEN
                set start.blue = PORTAL_START_BLUE
                set start.alpha = PORTAL_START_ALPHA
                set start.scale = PORTAL_START_SCALE
                
                set end.red = PORTAL_END_RED
                set end.green = PORTAL_END_GREEN
                set end.blue = PORTAL_END_BLUE
                set end.alpha = PORTAL_END_ALPHA
                set end.scale = PORTAL_END_SCALE
                
                call TimedAppearance.add(portal.u, start, end, time)
                
                if SHOW_SHARDS then
                    set spawnTime = SHARD_PERIOD
                endif
            else
                /*
                *   The number is invalid, stop casting
                */
                call IssueImmediateOrderById(caster, 851972)
            endif
            set treeCount = 0
            call trees.flush()
            call RemoveRect(r)
            set r = null
            set caster = null
            return false
        endmethod
        
        static method init takes nothing returns nothing
            set trees = Table.create()
            call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
        endmethod
    endstruct
    
    private function Init takes nothing returns nothing
        call SetupDevourerId()
        
        call Devourer.init()
        call SummonDevourer.init()
    endfunction
endscope
Top