[Spell] Problem with dummy caster in a JASS-spell.

Level 16
Joined
Jul 19, 2007
Messages
942
I have imported a JASS-spell into my map named "Fiery Vortex" and it seems to be working like it should, except one thing. The dummy unit is not create to owned of the casting Hero, it's create for neutral passive so the Hero doesn't get the kills for the units that dies of this spell... How to solve that?
JASS:
library FieryVortex requires optional DummyCaster, optional TreeDestruction, optional Preload

    //---------------------------------------------------------------
    // Fiery Vortex v1.1
    // Author: Blightsower
    //
    // Description - Channels a vortex of flames at the target location, dealing 
    // damage and applying a short stun and burn effect to enemies. While channeling, 
    // the caster gains a shield that increases armor. If fully channeled, the vortex explodes, 
    // dealing massive damage, applying a stronger burn and a longer stun to all affected enemies.
    //---------------------------------------------------------------
    // Requirements:
    //  * Enable JassHelper
    //
    // How to enable JassHelper
    //  * In the Trigger Editor window, look for the menu labeled JassHelper
    //    and click Enable JassHelper
    //-------------------------------------------------------------------------
    // How to Import:
    // Advanced apology, this spell contains a lot of objects
    // 1. Copy and paste the objects from this map to your map namely:
    //    * Fiery Vortex - (Hero)
    //    * Fiery Vortex - (Disabled)
    //    * Fiery Vortex - (Swap)
    //    * Fiery Vortex - (Bonuses)
    //    * Fiery Vortex - (Minor Stun)
    //    * Fiery Vortex - (Final Stun)
    //    * Fiery Vortex - (Minor Burn)
    //    * Fiery Vortex - (Final Burn)
    //    * Fiery Vortex - (Debuff)
    //    * DummyCaster
    // 2. Also export the imported models by opening the Asset Manager (F12)
    //    * Import the dummy.mdx to your map
    //    * Import the EmberGlow6.blp to your map
    //    * Import the Ice Shard.mdx to your map
    // 3. Copy and paste the Fiery Vortex Category into your map
    // 4. Configure the spell so that everything points to the correct
    //    variables. Additional information about the variables are
    //    provided for clarity.
    //-------------------------------------------------------------------------
    // Note:
    // This spell includes some optional libraries to handle tree destruction, dummy casting
    // and preloading. The spell is arranged so that you can easily get rid of those libraries without
    // going into the code. You might want to get rid of those libraries if you already have a system
    // that handles those functionalities or you just feel like writing your own. Should you choose to keep
    // the libraries, there are a few things that you need to configure to fit your needs. Check the libraries
    // individually to have the variables point to the correct objects.
    //
    // How to remove optional libraries:
    // To remove DummyCaster:
    //     Remove DummyCaster from the requires and delete whats inside the onDummyCast function found
    //     at the bottom of configurables. Delete DummyCaster script
    // To remove TreeDestruction:
    //     Remove TreeDestrcution from the requires and delete whats inside the onDestroyTree
    //     function found at the bottom of configurables. Delete TreeDestruction script
    // To remove Preload:
    //     Remove Preload from the requires and delete whats inside the onPreload function found
    //     at the bottom of configurables. Delete Preload script
    //-------------------------------------------------------------------------
    // Credits:
    // Vexorian - dummy.mdx
    // Vinz - Flamestrike II.mdx
    //      - EmberGlow6.blp
    //-------------------------------------------------------------------------

    //-------------------------------------------------------------
    // CONFIGURABLES
    //-------------------------------------------------------------
    globals
        private constant real TIMER_SPEED = 0.03
    endglobals

    native UnitAlive takes unit id returns boolean

    private module FieryVortexConfiguration
        //-------------------------------------------------------------
        // OBJECT EDITOR
        //-------------------------------------------------------------
        // Hint:
        // Press ctrl + d to check the object ids. 
        //-------------------------------------------------------------
 
        // Must point to Fiery Vortex - (Hero)
        static constant integer ABILITY_ID = 'A0PR'

        // Order string of ABILITY_ID
        static constant string ORDER_STRING = "monsoon"

        // Must point to Fiery Vortex - (Swap)
        static constant integer CAST_SAFETY = 'A0PL'

        // Must point to Fiery Vortex - (Bonuses)
        static constant integer BONUSES = 'A0PQ'

        //-------------------------------------------------------------
        // DAMAGE
        // Hint:
        // 1.0 = 100% of the attribute
        //-------------------------------------------------------------
        static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE = null

        // Damage area of effect
        static method GET_DAMAGE_AOE takes integer level returns real
            return 300.
        endmethod

        // Damage per interval
        static method GET_ABSOLUTE_DAMAGE takes integer level returns real
            return 15. + (10*level)
        endmethod
        static method GET_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod      
        
        // Damage on final explosion
        static method GET_FINAL_ABSOLUTE_DAMAGE takes integer level returns real
            return 50. + 50.*level
        endmethod
        static method GET_FINAL_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod  

        //-------------------------------------------------------------
        // BEHAVIOR
        //-------------------------------------------------------------

        // the full channel duration of the spell
        static method GET_CHANNEL_TIME takes integer level returns real
            return 3.5
        endmethod

        // How long will the flame swirl before the main explosion happens
        static method GET_RANGE_INDICATOR_DURATION takes integer level returns real
            return .75
        endmethod

        // Starting angle of the flame sparks
        static constant real RANGE_INDICATOR_STARTING_ANGLE_DEGREES = 0.

        // Damage interval
        static method GET_DAMAGE_INTERVAL takes integer level returns real
            return .5
        endmethod

        // Special effect interval
        static method GET_SFX_INTERVAL takes integer level returns real
            return .5
        endmethod

        //-------------------------------------------------------------
        // VISUALS
        //-------------------------------------------------------------

        // The swirling flame model before the main explosion starts
        static constant string RANGE_INDICATOR_SPARK_MODEL = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"

        // The size of the swirling flames
        static constant real RANGE_INDICATOR_SPARK_SIZE = 1.0

        // How many swirling flames
        static constant integer RANGE_INDICATOR_SPARK_COUNT = 3

        // How many times the flames will swirl before the explosion starts.
        // I find that it looks great at 540 degrees (one and a half rotation. 360 is a full circle)
        static constant real RANGE_INDICATOR_SPARK_ORBIT = 540.

        // The vortex model
        static constant string MAIN_EXPLOSION = "war3mapImported\\Flamestrike II.mdx"

        // The size of the vortex
        static method GET_MAIN_EXPLOSION_SIZE takes integer level returns real
            return 1.5
        endmethod

        // The model played when the caster fully completed the channel duration of the spell
        static constant string FINAL_EXPLOSION = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"

        // Size of the final explosion
        static method GET_FINAL_EXPLOSION_SIZE takes integer level returns real
            return 2.5
        endmethod

        // The model that lingers on the ground while the explosion is happening
        static constant string GROUND_EFFECT = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"

        // Size of the lingering ground model
        static method GET_GROUND_EFFECT_SIZE takes integer level returns real
            return 4.
        endmethod

        // The model attached to the caster when the explosion starts to occur
        static constant string CASTER_MODEL = "NONE.mdl"
        static constant string CASTER_ATTACHMENT = "chest"
        
        // Size of the caster model
        static method GET_CASTER_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod

        // Model shown when a unit is hit by the spell
        readonly static constant string HIT_MODEL = "Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl"
        readonly static constant string HIT_ATTACHMENT = "chest"
        static constant method GET_HIT_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod

        //-------------------------------------------------------------
        // API
        //-------------------------------------------------------------

        //-------------------------------------------------------------
        // onDestroyTree - function called by the spell when destroying trees
        // (x, y) - target point of fiery vortex
        // aoe - radius of the destruction
        // uses the ClearTrees function from the optional library
        //-------------------------------------------------------------
        static constant boolean DESTROYS_TREES = false
        static method onDestroyTree takes real x, real y, real aoe returns nothing
            call ClearTrees(x, y, aoe)
        endmethod

        //-------------------------------------------------------------
        // onDamage - is the damage event of the spell.
        // unit caster - is the caster of the spell
        // unit target - is the enemy about to get damaged by the spell
        // real damage - is the amount of damage about to be dealt
        // attacktype attackType - attacktype of the spell
        // damagetype damageType - damagetype of the spell
        // weapontype weaponType - weapontype of the spell
        // isFinalDamage - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static method onDamage takes unit caster, unit target, real damage, attacktype attackType, damagetype damageType, weapontype weaponType, boolean isFinalDamage returns nothing
            call UnitDamageTarget(caster, target, damage, true, false, attackType, damageType, weaponType)
        endmethod

        //-------------------------------------------------------------
        // isVortexable - is the damage filter of the spell.
        // unit u - is the unit in question
        // player p - is the owner of the spell
        // Original Conditions:
        //  - u is alive
        //  - u is not a structure
        //  - u is not magic immune
        //  - u is is an enemy of player p
        //-------------------------------------------------------------
        static method isVortexable takes unit u, player p returns boolean
            return UnitAlive(u) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and IsUnitEnemy(u,p)
        endmethod

        //-------------------------------------------------------------
        // onDummyCast - is the ministun event of the spell
        // unit u - target of the burn and stun
        // integer l - level of the spell
        // uses the OrderCaster function from the optional library
        // isFinalBurn - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static constant boolean USES_DUMMY_CASTER = true
        static constant integer MINOR_STUN_ABILITY_ID = 'A0PP'    // must point to Fiery Vortex - (Minor Stun)
        static constant integer MINOR_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer FINAL_STUN_ABILITY_ID = 'A0PN'    // must point to Fiery Vortex - (Final Stun)
        static constant integer FINAL_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer MINOR_BURN_ABILITY_ID = 'A0PO'    // must point to Fiery Vortex - (Minor Burn)
        static constant integer MINOR_BURN_ABILITY_ORDER = 852662 // acid bomb
        static constant integer FINAL_BURN_ABILITY_ID = 'A0PM'    // must point to Fiery Vortex - (Final Burn)
        static constant integer FINAL_BURN_ABILITY_ORDER = 852662 // acid bomb
        static method onDummyCast takes unit u, integer l, boolean isFinalBurn, player owner returns nothing
            if isFinalBurn then
                call OrderCasterFromPlayer(u, l, FINAL_BURN_ABILITY_ID, FINAL_BURN_ABILITY_ORDER, owner)
                call OrderCasterFromPlayer(u, l, FINAL_STUN_ABILITY_ID, FINAL_STUN_ABILITY_ORDER, owner)
            else
                call OrderCasterFromPlayer(u, l, MINOR_BURN_ABILITY_ID, MINOR_BURN_ABILITY_ORDER, owner)
                call OrderCasterFromPlayer(u, l, MINOR_STUN_ABILITY_ID, MINOR_STUN_ABILITY_ORDER, owner)
            endif
        endmethod

        //-------------------------------------------------------------
        // onPreload - runs at map initialization
        // uses the preload function from the optional library
        //-------------------------------------------------------------
        static method onPreload takes nothing returns nothing
            call Initialize.preloadAbility(ABILITY_ID)
            call Initialize.preloadAbility(CAST_SAFETY)
            call Initialize.preloadAbility(BONUSES)
            call Initialize.preloadAbility(MINOR_STUN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_STUN_ABILITY_ID)
            call Initialize.preloadAbility(MINOR_BURN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_BURN_ABILITY_ID)

            call Initialize.preloadEffect(RANGE_INDICATOR_SPARK_MODEL)
            call Initialize.preloadEffect(MAIN_EXPLOSION)
            call Initialize.preloadEffect(FINAL_EXPLOSION)
            call Initialize.preloadEffect(GROUND_EFFECT)
            call Initialize.preloadEffect(CASTER_MODEL)
            call Initialize.preloadEffect(HIT_MODEL)
        endmethod

    endmodule
    //-------------------------------------------------------------
    // END CONFIGURABLES
    //-------------------------------------------------------------

    private module SinglyLinkedList
        thistype next
        thistype prev

        static method insertAtHead takes thistype this returns nothing
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
        endmethod

        static method pop takes thistype this returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod
    endmodule

    scope FieryRangeIndicator
        struct FieryRangeIndicator
            implement SinglyLinkedList

            private static timer rangeTimer = CreateTimer()
            private static integer index = 0

            private unit caster
            private effect spark
            private real x
            private real y
            private real centerX
            private real centerY
            private real radius
            private real orbitSpeed
            private real currentAngle
            private real t = 0
            private real duration

            private static method lerp takes real a, real b, real t returns real
                return a + (b - a) * t
            endmethod

            private method update takes nothing returns boolean
                local real z
                local real r

                set .currentAngle = .currentAngle + .orbitSpeed

                set r = lerp(.radius, 0, .t)

                set .x = .centerX + r * Cos(.currentAngle)
                set .y = .centerY + r * Sin(.currentAngle)
                call MoveLocation(FieryVortex.loc, .x, .y)
                set z = GetLocationZ(FieryVortex.loc)

                call BlzSetSpecialEffectPosition(.spark, .x, .y, z)

                set .t = .t + 1/(.duration/TIMER_SPEED)

                return .t > 1.
            endmethod

            private method onCleanUp takes nothing returns nothing
                call this.deallocate()
                call pop(this)

                call DestroyEffect(.spark)
                set .caster = null

                set index = index - 1
                if index == 0 then
                    call PauseTimer(rangeTimer)
                endif
            endmethod


            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
 
                loop
                    exitwhen this == 0

                    if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                        call onCleanUp()
                    endif 

                    set this = this.next
                endloop
            endmethod
 
            static method createRangeIndicator takes real centerX, real centerY, real radius, unit caster, real duration returns nothing
                local thistype this
                local integer i = 0
                local real angleStep = FieryVortex.angleStep
                local real orbitSpeed = ((FieryVortex.RANGE_INDICATOR_SPARK_ORBIT) / (duration + 0.01)) * bj_DEGTORAD

                loop
                    exitwhen i > FieryVortex.RANGE_INDICATOR_SPARK_COUNT

                    set this = thistype.allocate()
                    call insertAtHead(this)
                    
                    set .caster = caster
                    set .centerX = centerX
                    set .centerY = centerY

                    set .orbitSpeed = orbitSpeed
                    set .radius = radius
                    set .duration = duration

                    set .currentAngle = (FieryVortex.RANGE_INDICATOR_STARTING_ANGLE_DEGREES * bj_DEGTORAD) + (i * angleStep)
                    set .x = .centerX + radius * Cos(.currentAngle)
                    set .y = .centerY + radius * Sin(.currentAngle)

                    set .spark = AddSpecialEffect(FieryVortex.RANGE_INDICATOR_SPARK_MODEL, .x, .y)

                    call BlzSetSpecialEffectScale(.spark, FieryVortex.RANGE_INDICATOR_SPARK_SIZE)

                    set index = index + 1
                    if index == 1 then                   
                        call TimerStart(rangeTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif

                    set i = i + 1
                endloop
            endmethod
        endstruct
    endscope

    scope FieryVortex
        struct FieryVortex
            implement FieryVortexConfiguration
            implement SinglyLinkedList

            // statics
            static location loc = Location(0,0)
            static real angleStep = (360. / FieryVortex.RANGE_INDICATOR_SPARK_COUNT) * bj_DEGTORAD
            private static group damageGroup = CreateGroup()
            private static timer vortexTimer = CreateTimer()
            private static integer index = 0
            
            // caster data
            private unit caster
            private real targetX
            private real targetY
            private real targetZ
            private player owner
            private integer level
            private real aoe

            // timing
            private real damagePerInterval
            private real damageFinalExplosion
            private real waitTimeLeft
            private real damageTimeLeft
            private real channelTimeLeft
            private real specialEffectTimeLeft
            
            // effects
            private effect groundEffect
            private effect casterEffect

            // flags
            private boolean isCompleted
            private boolean isWaiting

            // damage
            private static method getDamageFromAttribute takes unit u, real s, real a, real i returns real
                local real damage_agi = I2R(GetHeroStatBJ(bj_HEROSTAT_AGI, u, true)) * a
                local real damage_str = I2R(GetHeroStatBJ(bj_HEROSTAT_STR, u, true)) * s
                local real damage_int = I2R(GetHeroStatBJ(bj_HEROSTAT_INT, u, true)) * i
                return damage_agi + damage_str + damage_int
            endmethod

            private method dealDamage takes real whereX, real whereY, real distanceR, real damage, boolean isFinalDamage returns nothing
                local unit u
                local effect e

                call GroupEnumUnitsInRange(damageGroup, whereX, whereY, distanceR, null)
                loop
                    set u = FirstOfGroup(damageGroup)
                    exitwhen u == null

                    if .isVortexable(u, .owner) then
                        set e = AddSpecialEffectTarget(HIT_MODEL, u, HIT_ATTACHMENT)
                        call BlzSetSpecialEffectScale(e, GET_HIT_MODEL_SIZE(.level))
                        call DestroyEffect(e)

                        call .onDamage(.caster, u, damage, .ATTACK_TYPE, .DAMAGE_TYPE, .WEAPON_TYPE, isFinalDamage)

                        if USES_DUMMY_CASTER then
                            call onDummyCast(u, .level, isFinalDamage, .owner)
                        endif

                    endif
                    call GroupRemoveUnit(damageGroup,u)
                endloop
            endmethod

            private method clean takes nothing returns nothing
                call this.deallocate()
                call pop(this)

                call UnitRemoveAbility(.caster, CAST_SAFETY)
                call UnitRemoveAbility(.caster, BONUSES)
                call DestroyEffect(.groundEffect)
                call DestroyEffect(.casterEffect)
                set .caster = null
                set .owner = null

                set index = index - 1
                if index == 0 then
                    call PauseTimer(vortexTimer)
                endif
            endmethod

            private method update takes nothing returns boolean
                local effect e
  
                if .isWaiting then
                    if .waitTimeLeft > 0. then
                        set .waitTimeLeft = .waitTimeLeft - TIMER_SPEED
                    else
                        set .groundEffect = AddSpecialEffect(GROUND_EFFECT, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(.groundEffect, GET_GROUND_EFFECT_SIZE(.level))

                        set .casterEffect = AddSpecialEffectTarget(CASTER_MODEL, .caster, CASTER_ATTACHMENT)
                        call BlzSetSpecialEffectScale(.casterEffect, GET_CASTER_MODEL_SIZE(.level))

                        call UnitAddAbility(.caster, BONUSES)
                        call SetUnitAbilityLevel(.caster, BONUSES, .level)

                        if DESTROYS_TREES then
                            call onDestroyTree(.targetX, .targetY, .aoe)
                        endif

                        set .isWaiting = false
                    endif
                else
                    if .damageTimeLeft > 0 then
                        set .damageTimeLeft = .damageTimeLeft - TIMER_SPEED
                    else
                        set .damageTimeLeft = GET_DAMAGE_INTERVAL(.level)

                        call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damagePerInterval, false)
                    endif

                    if specialEffectTimeLeft > 0 then
                        set .specialEffectTimeLeft = .specialEffectTimeLeft - TIMER_SPEED
                    else                        
                        set e = AddSpecialEffect(MAIN_EXPLOSION, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(e, GET_MAIN_EXPLOSION_SIZE(.level))
                        call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                        call DestroyEffect(e)

                        set .specialEffectTimeLeft = GET_SFX_INTERVAL(.level)
                    endif

                    if .channelTimeLeft > 0 then
                        set .channelTimeLeft = .channelTimeLeft - TIMER_SPEED
                    else
                        set .isCompleted = true
                        call IssueImmediateOrder(.caster, "stop")
                    endif
                endif              
                    
                return isCompleted
            endmethod

            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
                local effect e

                loop
                    exitwhen this == 0
                        if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                            
                            if .isCompleted then
                                set e = AddSpecialEffect(FINAL_EXPLOSION, .targetX, .targetY)
                                call BlzSetSpecialEffectScale(e, GET_FINAL_EXPLOSION_SIZE(.level))
                                call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                                call DestroyEffect(e)

                                call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damageFinalExplosion, true)
                            endif

                            call clean()
                        endif
                    set this = this.next
                endloop
            endmethod

            private static method onCast takes nothing returns nothing
                local thistype this

                if GetSpellAbilityId() == ABILITY_ID then
                    set this = thistype.allocate()
                    call insertAtHead(this)

                    set .caster = GetTriggerUnit()
                    set .owner = GetTriggerPlayer()
                    set .level = GetUnitAbilityLevel(caster, ABILITY_ID)

                    set .targetX = GetSpellTargetX()
                    set .targetY = GetSpellTargetY()
                    call MoveLocation(.loc, .targetX, .targetY)
                    set .targetZ = GetLocationZ(FieryVortex.loc)

                    set .damagePerInterval = GET_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_STRENGTH_DAMAGE(.level), GET_AGILITY_DAMAGE(.level), GET_INTELLIGENCE_DAMAGE(.level))
                    set .damageFinalExplosion = GET_FINAL_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_FINAL_STRENGTH_DAMAGE(.level), GET_FINAL_AGILITY_DAMAGE(.level), GET_FINAL_INTELLIGENCE_DAMAGE(.level))

                    set .aoe = GET_DAMAGE_AOE(.level)
                    set .channelTimeLeft = GET_CHANNEL_TIME(.level)

                    set .damageTimeLeft = 0.
                    set .specialEffectTimeLeft = 0.
                    set .waitTimeLeft = GET_RANGE_INDICATOR_DURATION(.level)

                    set .isCompleted = false
                    set .isWaiting = true

                    call UnitAddAbility(.caster, CAST_SAFETY)

                    call FieryRangeIndicator.createRangeIndicator(.targetX, .targetY, .aoe, .caster, .waitTimeLeft)

                    set index = index + 1
                    if index == 1 then
                        call TimerStart(vortexTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif
                endif
            endmethod

            private static method disableAbility takes nothing returns nothing
                local integer i = 0
                loop
                    call SetPlayerAbilityAvailableBJ(false, CAST_SAFETY, Player(i))
                    call SetPlayerAbilityAvailableBJ(false, BONUSES, Player(i))

                    set i = i + 1
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                endloop
            endmethod

            static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()

                call onPreload()
                call disableAbility()

                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddAction(t, function thistype.onCast)

                set t = null
            endmethod
        endstruct
    endscope
endlibrary
JASS:
library DummyCaster initializer OnInit
    //---------------------------------------------------
    // Make sure that CASTER_TYPE correctly points to the DummyCaster's Id
    // press ctrl + d to show the id in the object editor
    //---------------------------------------------------
    globals
        private integer CASTER_TYPE = 'h03R'

        // do not touch
        private unit dummyCaster
    endglobals
    private function LoadCaster takes nothing returns nothing
        set dummyCaster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTER_TYPE, 0, 0, 0)
    endfunction
    function OrderCaster takes unit u, integer level, integer a, integer o returns nothing
        call SetUnitX(dummyCaster, GetUnitX(u))
        call SetUnitY(dummyCaster, GetUnitY(u))
        call UnitAddAbility(dummyCaster, a)
        call SetUnitAbilityLevel(dummyCaster, a, level)
        call IssueTargetOrderById(dummyCaster,o,u)
        call UnitRemoveAbility(dummyCaster, a)
    endfunction
    function OrderCasterFromPlayer takes unit u, integer level, integer a, integer o, player p returns nothing
        call SetUnitOwner(dummyCaster, p, false )
        call SetUnitX(dummyCaster, GetUnitX(u))
        call SetUnitY(dummyCaster, GetUnitY(u))
        call UnitAddAbility(dummyCaster, a)
        call SetUnitAbilityLevel(dummyCaster, a, level)
        call IssueTargetOrderById(dummyCaster,o,u)
        call UnitRemoveAbility(dummyCaster, a)
        call SetUnitOwner(dummyCaster, Player(PLAYER_NEUTRAL_PASSIVE), false )
    endfunction
    private function OnInit takes nothing returns nothing
        call LoadCaster()
    endfunction
endlibrary
JASS:
library Preload 
    //---------------------------------------------------
    // Make sure that PRELOAD_TYPE correctly points to the DummyCaster's Id
    // press ctrl + d to show the id in the object editor
    //
    // Also change the (x, y) to point at the unused point in your map (usually the corner)
    // You can find it by using the terrain editor and hovering over the point that you want
    // the point is listed on the lower left portion of the editor labeled as point followed
    // by the x and y values
    //---------------------------------------------------
    globals
        private integer PRELOAD_TYPE = 'h03R'
        private real x = -1231.7
        private real y = 988.4
        private real cleanUpDelay = 0
    endglobals

    private module Init
        private static unit preloadDummy
        private static method onInit takes nothing returns nothing
            set preloadDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), PRELOAD_TYPE, x, y, 0)
        endmethod
        static method preloadEffect takes string s returns nothing
            call DestroyEffect(AddSpecialEffectTarget(s, thistype.preloadDummy, "origin"))
        endmethod
        static method preloadAbility takes integer i returns nothing
            call UnitAddAbility(thistype.preloadDummy, i)
            call UnitRemoveAbility(thistype.preloadDummy, i)
        endmethod
        static method cleanPreload takes nothing returns nothing
            call RemoveUnit(thistype.preloadDummy)
            set preloadDummy = null
        endmethod
    endmodule

    struct Initialize
        implement Init
        private static method clean takes nothing returns nothing
            call cleanPreload()
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterTimerEventSingle(t, cleanUpDelay)
            call TriggerAddAction(t, function thistype.clean)
            set t = null
        endmethod
    endstruct
endlibrary
 
Here's an updated FieryVortex script. I removed the need for the DummyCaster library. Instead, it creates a new Dummy unit owned by the Player whenever it casts Storm Bolt (Stun) and Acid Bomb (Burn). Note that the Dummy unit is casting Acid Bomb, meaning it's the one dealing the Acid Bomb damage and will be considered the (Killing unit).

The Dummy uses this ID from the Object Editor:
vJASS:
'h03R'
Remember, a proper Dummy unit requires these settings to cast spells instantly:
Movement Type = None, Speed Base = 0, Art - Cast Point = 0.00, Art - Backswing = 0.00.

You also don't want it to be able to Attack, Decay, or anything that would interfere with the game. The Locust works as a good base for any Dummy unit since it has most of the settings you'd want.

vJASS:
library FieryVortex requires optional TreeDestruction, optional Preload
    //---------------------------------------------------------------
    // Fiery Vortex v1.1
    // Author: Blightsower
    //
    // Description - Channels a vortex of flames at the target location, dealing
    // damage and applying a short stun and burn effect to enemies. While channeling,
    // the caster gains a shield that increases armor. If fully channeled, the vortex explodes,
    // dealing massive damage, applying a stronger burn and a longer stun to all affected enemies.
    //---------------------------------------------------------------
    // Requirements:
    //  * Enable JassHelper
    //
    // How to enable JassHelper
    //  * In the Trigger Editor window, look for the menu labeled JassHelper
    //    and click Enable JassHelper
    //-------------------------------------------------------------------------
    // How to Import:
    // Advanced apology, this spell contains a lot of objects
    // 1. Copy and paste the objects from this map to your map namely:
    //    * Fiery Vortex - (Hero)
    //    * Fiery Vortex - (Disabled)
    //    * Fiery Vortex - (Swap)
    //    * Fiery Vortex - (Bonuses)
    //    * Fiery Vortex - (Minor Stun)
    //    * Fiery Vortex - (Final Stun)
    //    * Fiery Vortex - (Minor Burn)
    //    * Fiery Vortex - (Final Burn)
    //    * Fiery Vortex - (Debuff)
    //    * DummyCaster
    // 2. Also export the imported models by opening the Asset Manager (F12)
    //    * Import the dummy.mdx to your map
    //    * Import the EmberGlow6.blp to your map
    //    * Import the Ice Shard.mdx to your map
    // 3. Copy and paste the Fiery Vortex Category into your map
    // 4. Configure the spell so that everything points to the correct
    //    variables. Additional information about the variables are
    //    provided for clarity.
    //-------------------------------------------------------------------------
    // Note:
    // This spell includes some optional libraries to handle tree destruction, dummy casting
    // and preloading. The spell is arranged so that you can easily get rid of those libraries without
    // going into the code. You might want to get rid of those libraries if you already have a system
    // that handles those functionalities or you just feel like writing your own. Should you choose to keep
    // the libraries, there are a few things that you need to configure to fit your needs. Check the libraries
    // individually to have the variables point to the correct objects.
    //
    // How to remove optional libraries:
    // To remove DummyCaster:
    //     Remove DummyCaster from the requires and delete whats inside the onDummyCast function found
    //     at the bottom of configurables. Delete DummyCaster script
    // To remove TreeDestruction:
    //     Remove TreeDestrcution from the requires and delete whats inside the onDestroyTree
    //     function found at the bottom of configurables. Delete TreeDestruction script
    // To remove Preload:
    //     Remove Preload from the requires and delete whats inside the onPreload function found
    //     at the bottom of configurables. Delete Preload script
    //-------------------------------------------------------------------------
    // Credits:
    // Vexorian - dummy.mdx
    // Vinz - Flamestrike II.mdx
    //      - EmberGlow6.blp
    //-------------------------------------------------------------------------
    //-------------------------------------------------------------
    // CONFIGURABLES
    //-------------------------------------------------------------
    globals
        private constant real TIMER_SPEED = 0.03
    endglobals
    native UnitAlive takes unit id returns boolean
    private module FieryVortexConfiguration
        //-------------------------------------------------------------
        // OBJECT EDITOR
        //-------------------------------------------------------------
        // Hint:
        // Press ctrl + d to check the object ids.
        //-------------------------------------------------------------
 
        // Must point to Fiery Vortex - (Hero)
        static constant integer ABILITY_ID = 'A0PR'
        // Order string of ABILITY_ID
        static constant string ORDER_STRING = "monsoon"
        // Must point to Fiery Vortex - (Swap)
        static constant integer CAST_SAFETY = 'A0PL'
        // Must point to Fiery Vortex - (Bonuses)
        static constant integer BONUSES = 'A0PQ'
        //-------------------------------------------------------------
        // DAMAGE
        // Hint:
        // 1.0 = 100% of the attribute
        //-------------------------------------------------------------
        static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE = null
        // Damage area of effect
        static method GET_DAMAGE_AOE takes integer level returns real
            return 300.
        endmethod
        // Damage per interval
        static method GET_ABSOLUTE_DAMAGE takes integer level returns real
            return 15. + (10*level)
        endmethod
        static method GET_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod
  
        // Damage on final explosion
        static method GET_FINAL_ABSOLUTE_DAMAGE takes integer level returns real
            return 50. + 50.*level
        endmethod
        static method GET_FINAL_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod
        //-------------------------------------------------------------
        // BEHAVIOR
        //-------------------------------------------------------------
        // the full channel duration of the spell
        static method GET_CHANNEL_TIME takes integer level returns real
            return 3.5
        endmethod
        // How long will the flame swirl before the main explosion happens
        static method GET_RANGE_INDICATOR_DURATION takes integer level returns real
            return .75
        endmethod
        // Starting angle of the flame sparks
        static constant real RANGE_INDICATOR_STARTING_ANGLE_DEGREES = 0.
        // Damage interval
        static method GET_DAMAGE_INTERVAL takes integer level returns real
            return .5
        endmethod
        // Special effect interval
        static method GET_SFX_INTERVAL takes integer level returns real
            return .5
        endmethod
        //-------------------------------------------------------------
        // VISUALS
        //-------------------------------------------------------------
        // The swirling flame model before the main explosion starts
        static constant string RANGE_INDICATOR_SPARK_MODEL = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
        // The size of the swirling flames
        static constant real RANGE_INDICATOR_SPARK_SIZE = 1.0
        // How many swirling flames
        static constant integer RANGE_INDICATOR_SPARK_COUNT = 3
        // How many times the flames will swirl before the explosion starts.
        // I find that it looks great at 540 degrees (one and a half rotation. 360 is a full circle)
        static constant real RANGE_INDICATOR_SPARK_ORBIT = 540.
        // The vortex model
        static constant string MAIN_EXPLOSION = "war3mapImported\\Flamestrike II.mdx"
        // The size of the vortex
        static method GET_MAIN_EXPLOSION_SIZE takes integer level returns real
            return 1.5
        endmethod
        // The model played when the caster fully completed the channel duration of the spell
        static constant string FINAL_EXPLOSION = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
        // Size of the final explosion
        static method GET_FINAL_EXPLOSION_SIZE takes integer level returns real
            return 2.5
        endmethod
        // The model that lingers on the ground while the explosion is happening
        static constant string GROUND_EFFECT = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"
        // Size of the lingering ground model
        static method GET_GROUND_EFFECT_SIZE takes integer level returns real
            return 4.
        endmethod
        // The model attached to the caster when the explosion starts to occur
        static constant string CASTER_MODEL = "NONE.mdl"
        static constant string CASTER_ATTACHMENT = "chest"
  
        // Size of the caster model
        static method GET_CASTER_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod
        // Model shown when a unit is hit by the spell
        readonly static constant string HIT_MODEL = "Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl"
        readonly static constant string HIT_ATTACHMENT = "chest"
        static constant method GET_HIT_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod
        //-------------------------------------------------------------
        // API
        //-------------------------------------------------------------
        //-------------------------------------------------------------
        // onDestroyTree - function called by the spell when destroying trees
        // (x, y) - target point of fiery vortex
        // aoe - radius of the destruction
        // uses the ClearTrees function from the optional library
        //-------------------------------------------------------------
        static constant boolean DESTROYS_TREES = false
        static method onDestroyTree takes real x, real y, real aoe returns nothing
            call ClearTrees(x, y, aoe)
        endmethod
        //-------------------------------------------------------------
        // onDamage - is the damage event of the spell.
        // unit caster - is the caster of the spell
        // unit target - is the enemy about to get damaged by the spell
        // real damage - is the amount of damage about to be dealt
        // attacktype attackType - attacktype of the spell
        // damagetype damageType - damagetype of the spell
        // weapontype weaponType - weapontype of the spell
        // isFinalDamage - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static method onDamage takes unit caster, unit target, real damage, attacktype attackType, damagetype damageType, weapontype weaponType, boolean isFinalDamage returns nothing
            call UnitDamageTarget(caster, target, damage, true, false, attackType, damageType, weaponType)
        endmethod
        //-------------------------------------------------------------
        // isVortexable - is the damage filter of the spell.
        // unit u - is the unit in question
        // player p - is the owner of the spell
        // Original Conditions:
        //  - u is alive
        //  - u is not a structure
        //  - u is not magic immune
        //  - u is is an enemy of player p
        //-------------------------------------------------------------
        static method isVortexable takes unit u, player p returns boolean
            return UnitAlive(u) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and IsUnitEnemy(u,p)
        endmethod
        //-------------------------------------------------------------
        // onDummyCast - is the ministun event of the spell
        // unit u - target of the burn and stun
        // integer l - level of the spell
        // uses the OrderCaster function from the optional library
        // isFinalBurn - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static constant boolean USES_DUMMY_CASTER = true
        static constant integer MINOR_STUN_ABILITY_ID = 'A0PP'    // must point to Fiery Vortex - (Minor Stun)
        static constant integer MINOR_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer FINAL_STUN_ABILITY_ID = 'A0PN'    // must point to Fiery Vortex - (Final Stun)
        static constant integer FINAL_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer MINOR_BURN_ABILITY_ID = 'A0PO'    // must point to Fiery Vortex - (Minor Burn)
        static constant integer MINOR_BURN_ABILITY_ORDER = 852662 // acid bomb
        static constant integer FINAL_BURN_ABILITY_ID = 'A0PM'    // must point to Fiery Vortex - (Final Burn)
        static constant integer FINAL_BURN_ABILITY_ORDER = 852662 // acid bomb
        static method onDummyCast takes unit u, integer l, boolean isFinalBurn, player owner returns nothing
            local unit dmmy = CreateUnit(owner, 'h03R', GetUnitX(u), GetUnitY(u), 270)
            call UnitApplyTimedLife(dmmy, 'BTLF', 20.00)
            if isFinalBurn then
                // Acid Bomb
                call UnitAddAbility(dmmy, FINAL_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, FINAL_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_STUN_ABILITY_ID, u)
            else
                // Acid Bomb
                call UnitAddAbility(dmmy, MINOR_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, MINOR_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_STUN_ABILITY_ORDER, u)
            endif
            set dmmy = null
        endmethod
        //-------------------------------------------------------------
        // onPreload - runs at map initialization
        // uses the preload function from the optional library
        //-------------------------------------------------------------
        static method onPreload takes nothing returns nothing
            call Initialize.preloadAbility(ABILITY_ID)
            call Initialize.preloadAbility(CAST_SAFETY)
            call Initialize.preloadAbility(BONUSES)
            call Initialize.preloadAbility(MINOR_STUN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_STUN_ABILITY_ID)
            call Initialize.preloadAbility(MINOR_BURN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_BURN_ABILITY_ID)
            call Initialize.preloadEffect(RANGE_INDICATOR_SPARK_MODEL)
            call Initialize.preloadEffect(MAIN_EXPLOSION)
            call Initialize.preloadEffect(FINAL_EXPLOSION)
            call Initialize.preloadEffect(GROUND_EFFECT)
            call Initialize.preloadEffect(CASTER_MODEL)
            call Initialize.preloadEffect(HIT_MODEL)
        endmethod
    endmodule
    //-------------------------------------------------------------
    // END CONFIGURABLES
    //-------------------------------------------------------------
    private module SinglyLinkedList
        thistype next
        thistype prev
        static method insertAtHead takes thistype this returns nothing
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
        endmethod
        static method pop takes thistype this returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod
    endmodule
    scope FieryRangeIndicator
        struct FieryRangeIndicator
            implement SinglyLinkedList
            private static timer rangeTimer = CreateTimer()
            private static integer index = 0
            private unit caster
            private effect spark
            private real x
            private real y
            private real centerX
            private real centerY
            private real radius
            private real orbitSpeed
            private real currentAngle
            private real t = 0
            private real duration
            private static method lerp takes real a, real b, real t returns real
                return a + (b - a) * t
            endmethod
            private method update takes nothing returns boolean
                local real z
                local real r
                set .currentAngle = .currentAngle + .orbitSpeed
                set r = lerp(.radius, 0, .t)
                set .x = .centerX + r * Cos(.currentAngle)
                set .y = .centerY + r * Sin(.currentAngle)
                call MoveLocation(FieryVortex.loc, .x, .y)
                set z = GetLocationZ(FieryVortex.loc)
                call BlzSetSpecialEffectPosition(.spark, .x, .y, z)
                set .t = .t + 1/(.duration/TIMER_SPEED)
                return .t > 1.
            endmethod
            private method onCleanUp takes nothing returns nothing
                call this.deallocate()
                call pop(this)
                call DestroyEffect(.spark)
                set .caster = null
                set index = index - 1
                if index == 0 then
                    call PauseTimer(rangeTimer)
                endif
            endmethod

            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
 
                loop
                    exitwhen this == 0
                    if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                        call onCleanUp()
                    endif
                    set this = this.next
                endloop
            endmethod
 
            static method createRangeIndicator takes real centerX, real centerY, real radius, unit caster, real duration returns nothing
                local thistype this
                local integer i = 0
                local real angleStep = FieryVortex.angleStep
                local real orbitSpeed = ((FieryVortex.RANGE_INDICATOR_SPARK_ORBIT) / (duration + 0.01)) * bj_DEGTORAD
                loop
                    exitwhen i > FieryVortex.RANGE_INDICATOR_SPARK_COUNT
                    set this = thistype.allocate()
                    call insertAtHead(this)
              
                    set .caster = caster
                    set .centerX = centerX
                    set .centerY = centerY
                    set .orbitSpeed = orbitSpeed
                    set .radius = radius
                    set .duration = duration
                    set .currentAngle = (FieryVortex.RANGE_INDICATOR_STARTING_ANGLE_DEGREES * bj_DEGTORAD) + (i * angleStep)
                    set .x = .centerX + radius * Cos(.currentAngle)
                    set .y = .centerY + radius * Sin(.currentAngle)
                    set .spark = AddSpecialEffect(FieryVortex.RANGE_INDICATOR_SPARK_MODEL, .x, .y)
                    call BlzSetSpecialEffectScale(.spark, FieryVortex.RANGE_INDICATOR_SPARK_SIZE)
                    set index = index + 1
                    if index == 1 then             
                        call TimerStart(rangeTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif
                    set i = i + 1
                endloop
            endmethod
        endstruct
    endscope
    scope FieryVortex
        struct FieryVortex
            implement FieryVortexConfiguration
            implement SinglyLinkedList
            // statics
            static location loc = Location(0,0)
            static real angleStep = (360. / FieryVortex.RANGE_INDICATOR_SPARK_COUNT) * bj_DEGTORAD
            private static group damageGroup = CreateGroup()
            private static timer vortexTimer = CreateTimer()
            private static integer index = 0
      
            // caster data
            private unit caster
            private real targetX
            private real targetY
            private real targetZ
            private player owner
            private integer level
            private real aoe
            // timing
            private real damagePerInterval
            private real damageFinalExplosion
            private real waitTimeLeft
            private real damageTimeLeft
            private real channelTimeLeft
            private real specialEffectTimeLeft
      
            // effects
            private effect groundEffect
            private effect casterEffect
            // flags
            private boolean isCompleted
            private boolean isWaiting
            // damage
            private static method getDamageFromAttribute takes unit u, real s, real a, real i returns real
                local real damage_agi = I2R(GetHeroStatBJ(bj_HEROSTAT_AGI, u, true)) * a
                local real damage_str = I2R(GetHeroStatBJ(bj_HEROSTAT_STR, u, true)) * s
                local real damage_int = I2R(GetHeroStatBJ(bj_HEROSTAT_INT, u, true)) * i
                return damage_agi + damage_str + damage_int
            endmethod
            private method dealDamage takes real whereX, real whereY, real distanceR, real damage, boolean isFinalDamage returns nothing
                local unit u
                local effect e
                call GroupEnumUnitsInRange(damageGroup, whereX, whereY, distanceR, null)
                loop
                    set u = FirstOfGroup(damageGroup)
                    exitwhen u == null
                    if .isVortexable(u, .owner) then
                        set e = AddSpecialEffectTarget(HIT_MODEL, u, HIT_ATTACHMENT)
                        call BlzSetSpecialEffectScale(e, GET_HIT_MODEL_SIZE(.level))
                        call DestroyEffect(e)
                        call .onDamage(.caster, u, damage, .ATTACK_TYPE, .DAMAGE_TYPE, .WEAPON_TYPE, isFinalDamage)
                        if USES_DUMMY_CASTER then
                            call onDummyCast(u, .level, isFinalDamage, .owner)
                        endif
                    endif
                    call GroupRemoveUnit(damageGroup,u)
                endloop
            endmethod
            private method clean takes nothing returns nothing
                call this.deallocate()
                call pop(this)
                call UnitRemoveAbility(.caster, CAST_SAFETY)
                call UnitRemoveAbility(.caster, BONUSES)
                call DestroyEffect(.groundEffect)
                call DestroyEffect(.casterEffect)
                set .caster = null
                set .owner = null
                set index = index - 1
                if index == 0 then
                    call PauseTimer(vortexTimer)
                endif
            endmethod
            private method update takes nothing returns boolean
                local effect e
 
                if .isWaiting then
                    if .waitTimeLeft > 0. then
                        set .waitTimeLeft = .waitTimeLeft - TIMER_SPEED
                    else
                        set .groundEffect = AddSpecialEffect(GROUND_EFFECT, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(.groundEffect, GET_GROUND_EFFECT_SIZE(.level))
                        set .casterEffect = AddSpecialEffectTarget(CASTER_MODEL, .caster, CASTER_ATTACHMENT)
                        call BlzSetSpecialEffectScale(.casterEffect, GET_CASTER_MODEL_SIZE(.level))
                        call UnitAddAbility(.caster, BONUSES)
                        call SetUnitAbilityLevel(.caster, BONUSES, .level)
                        if DESTROYS_TREES then
                            call onDestroyTree(.targetX, .targetY, .aoe)
                        endif
                        set .isWaiting = false
                    endif
                else
                    if .damageTimeLeft > 0 then
                        set .damageTimeLeft = .damageTimeLeft - TIMER_SPEED
                    else
                        set .damageTimeLeft = GET_DAMAGE_INTERVAL(.level)
                        call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damagePerInterval, false)
                    endif
                    if specialEffectTimeLeft > 0 then
                        set .specialEffectTimeLeft = .specialEffectTimeLeft - TIMER_SPEED
                    else                  
                        set e = AddSpecialEffect(MAIN_EXPLOSION, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(e, GET_MAIN_EXPLOSION_SIZE(.level))
                        call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                        call DestroyEffect(e)
                        set .specialEffectTimeLeft = GET_SFX_INTERVAL(.level)
                    endif
                    if .channelTimeLeft > 0 then
                        set .channelTimeLeft = .channelTimeLeft - TIMER_SPEED
                    else
                        set .isCompleted = true
                        call IssueImmediateOrder(.caster, "stop")
                    endif
                endif        
              
                return isCompleted
            endmethod
            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
                local effect e
                loop
                    exitwhen this == 0
                        if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                      
                            if .isCompleted then
                                set e = AddSpecialEffect(FINAL_EXPLOSION, .targetX, .targetY)
                                call BlzSetSpecialEffectScale(e, GET_FINAL_EXPLOSION_SIZE(.level))
                                call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                                call DestroyEffect(e)
                                call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damageFinalExplosion, true)
                            endif
                            call clean()
                        endif
                    set this = this.next
                endloop
            endmethod
            private static method onCast takes nothing returns nothing
                local thistype this
                if GetSpellAbilityId() == ABILITY_ID then
                    set this = thistype.allocate()
                    call insertAtHead(this)
                    set .caster = GetTriggerUnit()
                    set .owner = GetTriggerPlayer()
                    set .level = GetUnitAbilityLevel(caster, ABILITY_ID)
                    set .targetX = GetSpellTargetX()
                    set .targetY = GetSpellTargetY()
                    call MoveLocation(.loc, .targetX, .targetY)
                    set .targetZ = GetLocationZ(FieryVortex.loc)
                    set .damagePerInterval = GET_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_STRENGTH_DAMAGE(.level), GET_AGILITY_DAMAGE(.level), GET_INTELLIGENCE_DAMAGE(.level))
                    set .damageFinalExplosion = GET_FINAL_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_FINAL_STRENGTH_DAMAGE(.level), GET_FINAL_AGILITY_DAMAGE(.level), GET_FINAL_INTELLIGENCE_DAMAGE(.level))
                    set .aoe = GET_DAMAGE_AOE(.level)
                    set .channelTimeLeft = GET_CHANNEL_TIME(.level)
                    set .damageTimeLeft = 0.
                    set .specialEffectTimeLeft = 0.
                    set .waitTimeLeft = GET_RANGE_INDICATOR_DURATION(.level)
                    set .isCompleted = false
                    set .isWaiting = true
                    call UnitAddAbility(.caster, CAST_SAFETY)
                    call FieryRangeIndicator.createRangeIndicator(.targetX, .targetY, .aoe, .caster, .waitTimeLeft)
                    set index = index + 1
                    if index == 1 then
                        call TimerStart(vortexTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif
                endif
            endmethod
            private static method disableAbility takes nothing returns nothing
                local integer i = 0
                loop
                    call SetPlayerAbilityAvailableBJ(false, CAST_SAFETY, Player(i))
                    call SetPlayerAbilityAvailableBJ(false, BONUSES, Player(i))
                    set i = i + 1
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                endloop
            endmethod
            static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                call onPreload()
                call disableAbility()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddAction(t, function thistype.onCast)
                set t = null
            endmethod
        endstruct
    endscope
endlibrary
 
Last edited:
Here's an updated FieryVortex script. I removed the need for the DummyCaster library. Instead, it creates a new Dummy unit owned by the Player whenever it casts Storm Bolt (Stun) and Acid Bomb (Burn). Note that the Dummy unit is casting Acid Bomb, meaning it's the one dealing the Acid Bomb damage and will be considered the (Killing unit).

The Dummy uses this ID from the Object Editor:
vJASS:
'h03R'
Remember, a proper Dummy unit requires these settings to cast spells instantly:
Movement Type = None, Speed Base = 0, Art - Cast Point = 0.00, Art - Backswing = 0.00.

You also don't want it to be able to Attack, Decay, or anything that would interfere with the game. The Locust works as a good base for any Dummy unit since it has most of the settings you'd want.

vJASS:
library FieryVortex requires optional TreeDestruction, optional Preload
    //---------------------------------------------------------------
    // Fiery Vortex v1.1
    // Author: Blightsower
    //
    // Description - Channels a vortex of flames at the target location, dealing
    // damage and applying a short stun and burn effect to enemies. While channeling,
    // the caster gains a shield that increases armor. If fully channeled, the vortex explodes,
    // dealing massive damage, applying a stronger burn and a longer stun to all affected enemies.
    //---------------------------------------------------------------
    // Requirements:
    //  * Enable JassHelper
    //
    // How to enable JassHelper
    //  * In the Trigger Editor window, look for the menu labeled JassHelper
    //    and click Enable JassHelper
    //-------------------------------------------------------------------------
    // How to Import:
    // Advanced apology, this spell contains a lot of objects
    // 1. Copy and paste the objects from this map to your map namely:
    //    * Fiery Vortex - (Hero)
    //    * Fiery Vortex - (Disabled)
    //    * Fiery Vortex - (Swap)
    //    * Fiery Vortex - (Bonuses)
    //    * Fiery Vortex - (Minor Stun)
    //    * Fiery Vortex - (Final Stun)
    //    * Fiery Vortex - (Minor Burn)
    //    * Fiery Vortex - (Final Burn)
    //    * Fiery Vortex - (Debuff)
    //    * DummyCaster
    // 2. Also export the imported models by opening the Asset Manager (F12)
    //    * Import the dummy.mdx to your map
    //    * Import the EmberGlow6.blp to your map
    //    * Import the Ice Shard.mdx to your map
    // 3. Copy and paste the Fiery Vortex Category into your map
    // 4. Configure the spell so that everything points to the correct
    //    variables. Additional information about the variables are
    //    provided for clarity.
    //-------------------------------------------------------------------------
    // Note:
    // This spell includes some optional libraries to handle tree destruction, dummy casting
    // and preloading. The spell is arranged so that you can easily get rid of those libraries without
    // going into the code. You might want to get rid of those libraries if you already have a system
    // that handles those functionalities or you just feel like writing your own. Should you choose to keep
    // the libraries, there are a few things that you need to configure to fit your needs. Check the libraries
    // individually to have the variables point to the correct objects.
    //
    // How to remove optional libraries:
    // To remove DummyCaster:
    //     Remove DummyCaster from the requires and delete whats inside the onDummyCast function found
    //     at the bottom of configurables. Delete DummyCaster script
    // To remove TreeDestruction:
    //     Remove TreeDestrcution from the requires and delete whats inside the onDestroyTree
    //     function found at the bottom of configurables. Delete TreeDestruction script
    // To remove Preload:
    //     Remove Preload from the requires and delete whats inside the onPreload function found
    //     at the bottom of configurables. Delete Preload script
    //-------------------------------------------------------------------------
    // Credits:
    // Vexorian - dummy.mdx
    // Vinz - Flamestrike II.mdx
    //      - EmberGlow6.blp
    //-------------------------------------------------------------------------
    //-------------------------------------------------------------
    // CONFIGURABLES
    //-------------------------------------------------------------
    globals
        private constant real TIMER_SPEED = 0.03
    endglobals
    native UnitAlive takes unit id returns boolean
    private module FieryVortexConfiguration
        //-------------------------------------------------------------
        // OBJECT EDITOR
        //-------------------------------------------------------------
        // Hint:
        // Press ctrl + d to check the object ids.
        //-------------------------------------------------------------
 
        // Must point to Fiery Vortex - (Hero)
        static constant integer ABILITY_ID = 'A0PR'
        // Order string of ABILITY_ID
        static constant string ORDER_STRING = "monsoon"
        // Must point to Fiery Vortex - (Swap)
        static constant integer CAST_SAFETY = 'A0PL'
        // Must point to Fiery Vortex - (Bonuses)
        static constant integer BONUSES = 'A0PQ'
        //-------------------------------------------------------------
        // DAMAGE
        // Hint:
        // 1.0 = 100% of the attribute
        //-------------------------------------------------------------
        static constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE = null
        // Damage area of effect
        static method GET_DAMAGE_AOE takes integer level returns real
            return 300.
        endmethod
        // Damage per interval
        static method GET_ABSOLUTE_DAMAGE takes integer level returns real
            return 15. + (10*level)
        endmethod
        static method GET_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod
 
        // Damage on final explosion
        static method GET_FINAL_ABSOLUTE_DAMAGE takes integer level returns real
            return 50. + 50.*level
        endmethod
        static method GET_FINAL_STRENGTH_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_AGILITY_DAMAGE takes integer level returns real
            return 0.
        endmethod
        static method GET_FINAL_INTELLIGENCE_DAMAGE takes integer level returns real
            return .5
        endmethod
        //-------------------------------------------------------------
        // BEHAVIOR
        //-------------------------------------------------------------
        // the full channel duration of the spell
        static method GET_CHANNEL_TIME takes integer level returns real
            return 3.5
        endmethod
        // How long will the flame swirl before the main explosion happens
        static method GET_RANGE_INDICATOR_DURATION takes integer level returns real
            return .75
        endmethod
        // Starting angle of the flame sparks
        static constant real RANGE_INDICATOR_STARTING_ANGLE_DEGREES = 0.
        // Damage interval
        static method GET_DAMAGE_INTERVAL takes integer level returns real
            return .5
        endmethod
        // Special effect interval
        static method GET_SFX_INTERVAL takes integer level returns real
            return .5
        endmethod
        //-------------------------------------------------------------
        // VISUALS
        //-------------------------------------------------------------
        // The swirling flame model before the main explosion starts
        static constant string RANGE_INDICATOR_SPARK_MODEL = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
        // The size of the swirling flames
        static constant real RANGE_INDICATOR_SPARK_SIZE = 1.0
        // How many swirling flames
        static constant integer RANGE_INDICATOR_SPARK_COUNT = 3
        // How many times the flames will swirl before the explosion starts.
        // I find that it looks great at 540 degrees (one and a half rotation. 360 is a full circle)
        static constant real RANGE_INDICATOR_SPARK_ORBIT = 540.
        // The vortex model
        static constant string MAIN_EXPLOSION = "war3mapImported\\Flamestrike II.mdx"
        // The size of the vortex
        static method GET_MAIN_EXPLOSION_SIZE takes integer level returns real
            return 1.5
        endmethod
        // The model played when the caster fully completed the channel duration of the spell
        static constant string FINAL_EXPLOSION = "Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
        // Size of the final explosion
        static method GET_FINAL_EXPLOSION_SIZE takes integer level returns real
            return 2.5
        endmethod
        // The model that lingers on the ground while the explosion is happening
        static constant string GROUND_EFFECT = "Abilities\\Spells\\Items\\VampiricPotion\\VampPotionCaster.mdl"
        // Size of the lingering ground model
        static method GET_GROUND_EFFECT_SIZE takes integer level returns real
            return 4.
        endmethod
        // The model attached to the caster when the explosion starts to occur
        static constant string CASTER_MODEL = "NONE.mdl"
        static constant string CASTER_ATTACHMENT = "chest"
 
        // Size of the caster model
        static method GET_CASTER_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod
        // Model shown when a unit is hit by the spell
        readonly static constant string HIT_MODEL = "Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl"
        readonly static constant string HIT_ATTACHMENT = "chest"
        static constant method GET_HIT_MODEL_SIZE takes integer level returns real
            return 1.
        endmethod
        //-------------------------------------------------------------
        // API
        //-------------------------------------------------------------
        //-------------------------------------------------------------
        // onDestroyTree - function called by the spell when destroying trees
        // (x, y) - target point of fiery vortex
        // aoe - radius of the destruction
        // uses the ClearTrees function from the optional library
        //-------------------------------------------------------------
        static constant boolean DESTROYS_TREES = false
        static method onDestroyTree takes real x, real y, real aoe returns nothing
            call ClearTrees(x, y, aoe)
        endmethod
        //-------------------------------------------------------------
        // onDamage - is the damage event of the spell.
        // unit caster - is the caster of the spell
        // unit target - is the enemy about to get damaged by the spell
        // real damage - is the amount of damage about to be dealt
        // attacktype attackType - attacktype of the spell
        // damagetype damageType - damagetype of the spell
        // weapontype weaponType - weapontype of the spell
        // isFinalDamage - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static method onDamage takes unit caster, unit target, real damage, attacktype attackType, damagetype damageType, weapontype weaponType, boolean isFinalDamage returns nothing
            call UnitDamageTarget(caster, target, damage, true, false, attackType, damageType, weaponType)
        endmethod
        //-------------------------------------------------------------
        // isVortexable - is the damage filter of the spell.
        // unit u - is the unit in question
        // player p - is the owner of the spell
        // Original Conditions:
        //  - u is alive
        //  - u is not a structure
        //  - u is not magic immune
        //  - u is is an enemy of player p
        //-------------------------------------------------------------
        static method isVortexable takes unit u, player p returns boolean
            return UnitAlive(u) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and IsUnitEnemy(u,p)
        endmethod
        //-------------------------------------------------------------
        // onDummyCast - is the ministun event of the spell
        // unit u - target of the burn and stun
        // integer l - level of the spell
        // uses the OrderCaster function from the optional library
        // isFinalBurn - true if the caster has fully channeled the spell
        //-------------------------------------------------------------
        static constant boolean USES_DUMMY_CASTER = true
        static constant integer MINOR_STUN_ABILITY_ID = 'A0PP'    // must point to Fiery Vortex - (Minor Stun)
        static constant integer MINOR_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer FINAL_STUN_ABILITY_ID = 'A0PN'    // must point to Fiery Vortex - (Final Stun)
        static constant integer FINAL_STUN_ABILITY_ORDER = 852095 // storm bolt
        static constant integer MINOR_BURN_ABILITY_ID = 'A0PO'    // must point to Fiery Vortex - (Minor Burn)
        static constant integer MINOR_BURN_ABILITY_ORDER = 852662 // acid bomb
        static constant integer FINAL_BURN_ABILITY_ID = 'A0PM'    // must point to Fiery Vortex - (Final Burn)
        static constant integer FINAL_BURN_ABILITY_ORDER = 852662 // acid bomb
        static method onDummyCast takes unit u, integer l, boolean isFinalBurn, player owner returns nothing
            local unit dmmy = CreateUnit(owner, 'h03R', GetUnitX(u), GetUnitY(u), 270)
            call UnitApplyTimedLife(dmmy, 'BTLF', 20.00)
            if isFinalBurn then
                // Acid Bomb
                call UnitAddAbility(dmmy, FINAL_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, FINAL_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_STUN_ABILITY_ID, u)
            else
                // Acid Bomb
                call UnitAddAbility(dmmy, MINOR_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, MINOR_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_STUN_ABILITY_ORDER, u)
            endif
            set dmmy = null
        endmethod
        //-------------------------------------------------------------
        // onPreload - runs at map initialization
        // uses the preload function from the optional library
        //-------------------------------------------------------------
        static method onPreload takes nothing returns nothing
            call Initialize.preloadAbility(ABILITY_ID)
            call Initialize.preloadAbility(CAST_SAFETY)
            call Initialize.preloadAbility(BONUSES)
            call Initialize.preloadAbility(MINOR_STUN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_STUN_ABILITY_ID)
            call Initialize.preloadAbility(MINOR_BURN_ABILITY_ID)
            call Initialize.preloadAbility(FINAL_BURN_ABILITY_ID)
            call Initialize.preloadEffect(RANGE_INDICATOR_SPARK_MODEL)
            call Initialize.preloadEffect(MAIN_EXPLOSION)
            call Initialize.preloadEffect(FINAL_EXPLOSION)
            call Initialize.preloadEffect(GROUND_EFFECT)
            call Initialize.preloadEffect(CASTER_MODEL)
            call Initialize.preloadEffect(HIT_MODEL)
        endmethod
    endmodule
    //-------------------------------------------------------------
    // END CONFIGURABLES
    //-------------------------------------------------------------
    private module SinglyLinkedList
        thistype next
        thistype prev
        static method insertAtHead takes thistype this returns nothing
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
        endmethod
        static method pop takes thistype this returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod
    endmodule
    scope FieryRangeIndicator
        struct FieryRangeIndicator
            implement SinglyLinkedList
            private static timer rangeTimer = CreateTimer()
            private static integer index = 0
            private unit caster
            private effect spark
            private real x
            private real y
            private real centerX
            private real centerY
            private real radius
            private real orbitSpeed
            private real currentAngle
            private real t = 0
            private real duration
            private static method lerp takes real a, real b, real t returns real
                return a + (b - a) * t
            endmethod
            private method update takes nothing returns boolean
                local real z
                local real r
                set .currentAngle = .currentAngle + .orbitSpeed
                set r = lerp(.radius, 0, .t)
                set .x = .centerX + r * Cos(.currentAngle)
                set .y = .centerY + r * Sin(.currentAngle)
                call MoveLocation(FieryVortex.loc, .x, .y)
                set z = GetLocationZ(FieryVortex.loc)
                call BlzSetSpecialEffectPosition(.spark, .x, .y, z)
                set .t = .t + 1/(.duration/TIMER_SPEED)
                return .t > 1.
            endmethod
            private method onCleanUp takes nothing returns nothing
                call this.deallocate()
                call pop(this)
                call DestroyEffect(.spark)
                set .caster = null
                set index = index - 1
                if index == 0 then
                    call PauseTimer(rangeTimer)
                endif
            endmethod

            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
 
                loop
                    exitwhen this == 0
                    if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                        call onCleanUp()
                    endif
                    set this = this.next
                endloop
            endmethod
 
            static method createRangeIndicator takes real centerX, real centerY, real radius, unit caster, real duration returns nothing
                local thistype this
                local integer i = 0
                local real angleStep = FieryVortex.angleStep
                local real orbitSpeed = ((FieryVortex.RANGE_INDICATOR_SPARK_ORBIT) / (duration + 0.01)) * bj_DEGTORAD
                loop
                    exitwhen i > FieryVortex.RANGE_INDICATOR_SPARK_COUNT
                    set this = thistype.allocate()
                    call insertAtHead(this)
             
                    set .caster = caster
                    set .centerX = centerX
                    set .centerY = centerY
                    set .orbitSpeed = orbitSpeed
                    set .radius = radius
                    set .duration = duration
                    set .currentAngle = (FieryVortex.RANGE_INDICATOR_STARTING_ANGLE_DEGREES * bj_DEGTORAD) + (i * angleStep)
                    set .x = .centerX + radius * Cos(.currentAngle)
                    set .y = .centerY + radius * Sin(.currentAngle)
                    set .spark = AddSpecialEffect(FieryVortex.RANGE_INDICATOR_SPARK_MODEL, .x, .y)
                    call BlzSetSpecialEffectScale(.spark, FieryVortex.RANGE_INDICATOR_SPARK_SIZE)
                    set index = index + 1
                    if index == 1 then            
                        call TimerStart(rangeTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif
                    set i = i + 1
                endloop
            endmethod
        endstruct
    endscope
    scope FieryVortex
        struct FieryVortex
            implement FieryVortexConfiguration
            implement SinglyLinkedList
            // statics
            static location loc = Location(0,0)
            static real angleStep = (360. / FieryVortex.RANGE_INDICATOR_SPARK_COUNT) * bj_DEGTORAD
            private static group damageGroup = CreateGroup()
            private static timer vortexTimer = CreateTimer()
            private static integer index = 0
     
            // caster data
            private unit caster
            private real targetX
            private real targetY
            private real targetZ
            private player owner
            private integer level
            private real aoe
            // timing
            private real damagePerInterval
            private real damageFinalExplosion
            private real waitTimeLeft
            private real damageTimeLeft
            private real channelTimeLeft
            private real specialEffectTimeLeft
     
            // effects
            private effect groundEffect
            private effect casterEffect
            // flags
            private boolean isCompleted
            private boolean isWaiting
            // damage
            private static method getDamageFromAttribute takes unit u, real s, real a, real i returns real
                local real damage_agi = I2R(GetHeroStatBJ(bj_HEROSTAT_AGI, u, true)) * a
                local real damage_str = I2R(GetHeroStatBJ(bj_HEROSTAT_STR, u, true)) * s
                local real damage_int = I2R(GetHeroStatBJ(bj_HEROSTAT_INT, u, true)) * i
                return damage_agi + damage_str + damage_int
            endmethod
            private method dealDamage takes real whereX, real whereY, real distanceR, real damage, boolean isFinalDamage returns nothing
                local unit u
                local effect e
                call GroupEnumUnitsInRange(damageGroup, whereX, whereY, distanceR, null)
                loop
                    set u = FirstOfGroup(damageGroup)
                    exitwhen u == null
                    if .isVortexable(u, .owner) then
                        set e = AddSpecialEffectTarget(HIT_MODEL, u, HIT_ATTACHMENT)
                        call BlzSetSpecialEffectScale(e, GET_HIT_MODEL_SIZE(.level))
                        call DestroyEffect(e)
                        call .onDamage(.caster, u, damage, .ATTACK_TYPE, .DAMAGE_TYPE, .WEAPON_TYPE, isFinalDamage)
                        if USES_DUMMY_CASTER then
                            call onDummyCast(u, .level, isFinalDamage, .owner)
                        endif
                    endif
                    call GroupRemoveUnit(damageGroup,u)
                endloop
            endmethod
            private method clean takes nothing returns nothing
                call this.deallocate()
                call pop(this)
                call UnitRemoveAbility(.caster, CAST_SAFETY)
                call UnitRemoveAbility(.caster, BONUSES)
                call DestroyEffect(.groundEffect)
                call DestroyEffect(.casterEffect)
                set .caster = null
                set .owner = null
                set index = index - 1
                if index == 0 then
                    call PauseTimer(vortexTimer)
                endif
            endmethod
            private method update takes nothing returns boolean
                local effect e
 
                if .isWaiting then
                    if .waitTimeLeft > 0. then
                        set .waitTimeLeft = .waitTimeLeft - TIMER_SPEED
                    else
                        set .groundEffect = AddSpecialEffect(GROUND_EFFECT, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(.groundEffect, GET_GROUND_EFFECT_SIZE(.level))
                        set .casterEffect = AddSpecialEffectTarget(CASTER_MODEL, .caster, CASTER_ATTACHMENT)
                        call BlzSetSpecialEffectScale(.casterEffect, GET_CASTER_MODEL_SIZE(.level))
                        call UnitAddAbility(.caster, BONUSES)
                        call SetUnitAbilityLevel(.caster, BONUSES, .level)
                        if DESTROYS_TREES then
                            call onDestroyTree(.targetX, .targetY, .aoe)
                        endif
                        set .isWaiting = false
                    endif
                else
                    if .damageTimeLeft > 0 then
                        set .damageTimeLeft = .damageTimeLeft - TIMER_SPEED
                    else
                        set .damageTimeLeft = GET_DAMAGE_INTERVAL(.level)
                        call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damagePerInterval, false)
                    endif
                    if specialEffectTimeLeft > 0 then
                        set .specialEffectTimeLeft = .specialEffectTimeLeft - TIMER_SPEED
                    else                 
                        set e = AddSpecialEffect(MAIN_EXPLOSION, .targetX, .targetY)
                        call BlzSetSpecialEffectScale(e, GET_MAIN_EXPLOSION_SIZE(.level))
                        call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                        call DestroyEffect(e)
                        set .specialEffectTimeLeft = GET_SFX_INTERVAL(.level)
                    endif
                    if .channelTimeLeft > 0 then
                        set .channelTimeLeft = .channelTimeLeft - TIMER_SPEED
                    else
                        set .isCompleted = true
                        call IssueImmediateOrder(.caster, "stop")
                    endif
                endif       
             
                return isCompleted
            endmethod
            private static method onEffect takes nothing returns nothing
                local thistype this = thistype(0).next
                local effect e
                loop
                    exitwhen this == 0
                        if update() or GetUnitCurrentOrder(.caster) != String2OrderIdBJ(FieryVortex.ORDER_STRING) then
                     
                            if .isCompleted then
                                set e = AddSpecialEffect(FINAL_EXPLOSION, .targetX, .targetY)
                                call BlzSetSpecialEffectScale(e, GET_FINAL_EXPLOSION_SIZE(.level))
                                call BlzSetSpecialEffectPosition(e, .targetX, .targetY, .targetZ)
                                call DestroyEffect(e)
                                call dealDamage(.targetX, .targetY, GET_DAMAGE_AOE(.level), .damageFinalExplosion, true)
                            endif
                            call clean()
                        endif
                    set this = this.next
                endloop
            endmethod
            private static method onCast takes nothing returns nothing
                local thistype this
                if GetSpellAbilityId() == ABILITY_ID then
                    set this = thistype.allocate()
                    call insertAtHead(this)
                    set .caster = GetTriggerUnit()
                    set .owner = GetTriggerPlayer()
                    set .level = GetUnitAbilityLevel(caster, ABILITY_ID)
                    set .targetX = GetSpellTargetX()
                    set .targetY = GetSpellTargetY()
                    call MoveLocation(.loc, .targetX, .targetY)
                    set .targetZ = GetLocationZ(FieryVortex.loc)
                    set .damagePerInterval = GET_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_STRENGTH_DAMAGE(.level), GET_AGILITY_DAMAGE(.level), GET_INTELLIGENCE_DAMAGE(.level))
                    set .damageFinalExplosion = GET_FINAL_ABSOLUTE_DAMAGE(.level) + getDamageFromAttribute(.caster, GET_FINAL_STRENGTH_DAMAGE(.level), GET_FINAL_AGILITY_DAMAGE(.level), GET_FINAL_INTELLIGENCE_DAMAGE(.level))
                    set .aoe = GET_DAMAGE_AOE(.level)
                    set .channelTimeLeft = GET_CHANNEL_TIME(.level)
                    set .damageTimeLeft = 0.
                    set .specialEffectTimeLeft = 0.
                    set .waitTimeLeft = GET_RANGE_INDICATOR_DURATION(.level)
                    set .isCompleted = false
                    set .isWaiting = true
                    call UnitAddAbility(.caster, CAST_SAFETY)
                    call FieryRangeIndicator.createRangeIndicator(.targetX, .targetY, .aoe, .caster, .waitTimeLeft)
                    set index = index + 1
                    if index == 1 then
                        call TimerStart(vortexTimer, TIMER_SPEED, true, function thistype.onEffect)
                    endif
                endif
            endmethod
            private static method disableAbility takes nothing returns nothing
                local integer i = 0
                loop
                    call SetPlayerAbilityAvailableBJ(false, CAST_SAFETY, Player(i))
                    call SetPlayerAbilityAvailableBJ(false, BONUSES, Player(i))
                    set i = i + 1
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                endloop
            endmethod
            static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                call onPreload()
                call disableAbility()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddAction(t, function thistype.onCast)
                set t = null
            endmethod
        endstruct
    endscope
endlibrary
There is nothing wrong with the dummy unit but the DummyCaster and Preload system is a bit stupid since it creates the dummy unit for player Neutral Passive instead of owner of casting unit of the spell... I have no idéa how to solve that but I reported it to the maker of the spell and perhaps he will solve it.
Btw I got an easy solution for the problem by changing the owner of the dummy unit everytime the "Fiery Vortex" spell is casted and it seems to work because in my map there is only 1 hero who got the "Fiery Vortex" spell so it won't conflict with any other hero.
  • Cast
    • Events
      • Unit - A unit Begins channeling an ability
    • Conditions
      • (Ability being cast) Equal to Fiery Vortex - (Hero)
    • Actions
      • Unit Group - Pick every unit in (Units in (Playable map area)) and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Unit-type of (Picked unit)) Equal to DummyCaster (Aura of Silence)
            • Then - Actions
              • Wait 0.50 seconds
              • Unit - Change ownership of (Picked unit) to (Owner of (Triggering unit)) and Change color
            • Else - Actions
 
The main issue with the Dummy unit was that it belonged to Neutral Passive. Technically, it belonged to the caster, but then it swapped back to Neutral Passive immediately after casting it's spells. That's why the kill credit was going to the wrong player, Storm Bolt and Acid Bomb deal damage AFTER it changes back.

I fixed this by removing the Dummy recycling library and instead doing things the old fashioned way, just creating a new Dummy unit on the spot.
Instead, it creates a new Dummy unit owned by the Player
vJASS:
        static method onDummyCast takes unit u, integer l, boolean isFinalBurn, player owner returns nothing
            local unit dmmy = CreateUnit(owner, 'h03R', GetUnitX(u), GetUnitY(u), 270)
            call UnitApplyTimedLife(dmmy, 'BTLF', 20.00)
            if isFinalBurn then
                // Acid Bomb
                call UnitAddAbility(dmmy, FINAL_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, FINAL_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, FINAL_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, FINAL_STUN_ABILITY_ID, u)
            else
                // Acid Bomb
                call UnitAddAbility(dmmy, MINOR_BURN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_BURN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_BURN_ABILITY_ORDER, u)
                // Storm Bolt
                call UnitAddAbility(dmmy, MINOR_STUN_ABILITY_ID)
                call SetUnitAbilityLevel(dmmy, MINOR_STUN_ABILITY_ID, l)
                call IssueTargetOrderById(dmmy, MINOR_STUN_ABILITY_ORDER, u)
            endif
            set dmmy = null
        endmethod

I mentioned that other "Dummy stuff" because the majority of the time people create/use Dummy units incorrectly. Unfortunately, a lot of the old guides on Dummy units are outdated and have misinformation, so I think everyone was misled for a while.

Furthermore, you should really only have 2 Dummy units in your map. One for casting spells and another for being used as a Missile (which is obsolete as of 1.31). I can understand having some unique ones for certain triggers but most of the time it's unnecessary.
 
Last edited:
Back
Top