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

Default Spells Triggered 1.08

This is my first spell upload. This is going to be a pack of spells, which are triggered versions of warcraft 3 default spells. Needless to say, these ideas aren't very original as they're spell remakes, but I believe someone will find these spells useful. For example, triggering all spell damage may be required for differentiating spell/attack damage. These triggered versions are also a lot more configurable than standard versions.

I'm going to add more spells into this spellpack. I'm also taking requests on which spells I'll remake next.

Import instructions:
1. Import Dummy.mdx to your map (not necessary, but recommended) and create a dummy unit using that model.
2. Create a dummy ability for the spell
3. Copy the spell trigger and required libraries to your map
4. Set the configurables and you're done.

Credits:
Vexorian for Dummy.mdx

Changelog:
1.08: Improved coding to match moderation. details
1.07: Improved coding: changed checking if a unit is alive to use UnitAlive method, now spells only use one dummycaster, changed default timeout from 0.04 to 0.03125, removed unneeded nulling of loop and trigger variables
1.06: Added spell: Volcano
1.05: Shockwave: added an option to create fill effects without dummy units
1.04: Added spell: Shockwave
1.03: Added spell: Flame Strike
1.02: Added static ifs, removed unused libraries
1.01: Added sound effects and optimized code
1.00: Spell uploaded (containing blizzard)

Spell Descriptions:

Calls down waves of freezing ice shards.

Level 1 - 14 waves at 30 damage each.
Level 2 - 20 waves at 40 damage each.
Level 3 - 29 waves at 50 damage each.

Configurables:
  • falling special effect
  • speciall effect on landing
  • spell cast on units hit by the spell
  • falling effect type (missile, e.g. arrow, or stationary effect like ordinary blizzard)
  • damage by individual missiles or once per wave
  • missile collision
  • missile falling speed
  • falling height
  • delay between effect and damage
  • looping sound
  • requires/doesn't require channelling
  • allowed targets
  • spell area (level support)
  • damage (level support)
  • interval between waves (level support)
  • number of shards (level support)
Surrounds an area with flames after channelling for 1.5 seconds. Deals damage for 2 seconds and leaves the ground burning for 5 seconds.

Level 1 - 12 primary damage every 0.4 seconds, 4 secondary damage every 1.8 seconds.
Level 2 - 14 primary damage every 0.3 seconds, 5 secondary damage every 1.6 seconds.
Level 3 - 16 primary damage every 0.2 seconds, 6 secondary damage every 1.4 seconds.

Configurables:
  • special effect when channelling
  • special when dealing primary damage
  • special effect when dealind secondary damage
  • delay between primary spell effect and damage
  • primary phase duration
  • secondary phase duration
  • spell area (level support)
  • primary damage (level support)
  • secondary damage (level support)
  • casting time (level support)
  • primary damage interval (level support)
  • secondary damage interval
  • number of special effect particles at secondary phase
  • allowed targets
Sends a shockwave towards the target location, dealing damage to enemy units in a line.

Level 1 - 100 damage.
Level 2 - 150 damage.
Level 3 - 200 damage.

Configurables:
  • missile effect
  • missile speed
  • spell cast on hit units
  • can units be hit several times By one spell
  • terrain deformation
  • optional secondary missiles at the borders of a cone
  • spell area can be filled with small missile effects
  • distance (level support)
  • damage (level support)
  • starting area (level support)
  • end area (level support)
  • allowed targets
Creates a volcano, which sends 7 molten rocks every 3 seconds. Rocks damage and stun enemy units they hit.

Level 1 - 60 damage, 400 area.
Level 2 - 80 damage, 500 area.
Level 3 - 100 damage, 600 area.

Configurables:
  • missile effect
  • speciall effect on landing
  • spell cast on units hit by the spell
  • channelling effect
  • missile speed
  • missile collision
  • max height of the missile projectory
  • time between missiles of the same wave
  • time between waves
  • time before first wave
  • requires/doesn't require channelling
  • point target/instant ability
  • allowed targets
  • spell area (level support)
  • damage (level support)
  • duration (level support)


Triggers:
JASS:
//#################################
//#Blizzard triggered version     #
//#Created by Erkki2              #
//#################################
scope Blizzard

    native UnitAlive takes unit id returns boolean
    
//  #################
//  # CONFIGURABLES #
//  #################
    globals
        //Dummy unit used for moving effects and casting spell
        private constant integer DUMMY_ID = 'h000'
        
        //Raw code of the spell ability
        private constant integer ABIL_CODE = 'A000'
        
        //Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
        private constant integer DUMMY_ABILITY = 'A001'
        
        //Order string for dummy spell
        private constant string DUMMY_ORDER = "slow"
        
        //If NO_MISSILES == false, this effect is used as a falling down missile.
        //If NO_MISILES == true, this effect is created as a stationary effect (works well for standard blizzard effect).
        private constant string MISSILE_ART = "Abilities\\Spells\\Human\\Blizzard\\BlizzardTarget.mdl"
        
        //Iteration interval
        private constant real TIMEOUT = 0.03125
        
        //Collision size of missiles. This has no effect if INDIVIDUAL_DAMAGE == false.
        private constant real COLLISION = 100
        
        //If NO_MISSILES == false, this is the time it takes for a missile to land.
        //If NO_MISSILES == true, this serves as a delay between effect and damage. For standard blizzard effect, 0.8 is recommended.
        private constant real FALLING_TIME = 0.8
        
        //Heigh that missiles fall from. Has no effect if NO_MISSILES == true.
        private constant real HEIGHT = 900
        
        //Effect which is created when missiles hit the ground.
        private constant string LANDING_EFFECT = ""
        
        //Sound which is played on every wave
        private constant string LOOP_SOUND = "BlizzardWave"
        
        //If true, all missiles will damage small area around them.
        //If false, each wave will damage the whole spell area, regardless where the missiles fall.
        private constant boolean INDIVIDUAL_DAMAGE = false
        
        //If true, the effect is treated as an effect without falling down.
        //If false, the effect is treated as a falling down missile.
        private constant boolean NO_MISSILES = true
        
        //If true, spell will be interrupted if caster stops channelling.
        private constant boolean REQUIRES_CHANNELLING = true
        
        //True if ability is cast at a target point, false if it's centered on caster (instant, no target).
        private constant boolean TARGET_ABILITY = true
        
        //Attack type of the spell damage (hero, siege, chaos, jne...)
        private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
        
        //Damage type of the spell (normal, universal, magic etc...)
        private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
    endglobals
    
    //Returns the interval between waves (in seconds)
    private function setWaveInterval takes unit caster returns real
        return 0.8 - 0.1 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the number of shards in each wave
    private function setShardsPerWave takes unit caster returns integer
        return 10 + 2 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the spell area
    private function setArea takes unit caster returns real
        return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the spell duration.
    private function setDuration takes unit caster returns real
        return 7.0 + 3.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //If INDIVIDUAL_DAMAGE == true, returns damage dealt by shard.
    //If INDIVIDUAL_DAMAGE == false, returns damage dealt by wave.
    private function setDamage takes unit caster returns real
        return 20.0 + 10.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns true, if target unit can be hit by the spell
    private function isTargetAllowed takes unit target, unit caster returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
    endfunction
    
//  ############################################################
//  # CONFIGURABLES END, Don't touch anything after this line. #
//  ############################################################

    private struct Dummy

        private thistype next
        private thistype prev

        private static integer count = 0
        private static group enumGroup = CreateGroup()
        
        private integer shardsPerWave
        private real area

        private unit dummy
        private integer executions
        private real heightDecrement
        private unit caster
        private player ownerOfCaster
        private real damage
        private effect missileEffect
        private real x
        private real y
        private boolean onlyVisual

        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set count = count - 1
            call DestroyEffect(this.missileEffect)
            set this.missileEffect = null
            set this.caster = null
            set this.dummy = null
            set this.ownerOfCaster = null
        endmethod

        static method move takes nothing returns boolean
    
            local thistype this = thistype(0).next
            local real x
            local real y
            local unit u
            local real damageArea
            local unit dummyCaster
            
            loop
                exitwhen this == 0
            
                //Move dummy units if they exist
                static if not NO_MISSILES then
                    call SetUnitFlyHeight(this.dummy, GetUnitFlyHeight(this.dummy) - this.heightDecrement, 0)
                endif
                
                //If dummy unit has reached its destination or damage delay has passed, damage area
                if executions == 0 then
                
                    //Create landing effect
                    call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, this.x, this.y))
                    
                    static if INDIVIDUAL_DAMAGE then
                        //Only damage small area around the missile
                        set damageArea = COLLISION
                    else
                        //Damage whole spell area
                        set damageArea = this.area
                    endif
                    
                    //If INDIVIDUAL_DAMAGE == false, only one of the missiles will deal damage. For that missile,
                    //onlyVisual == false
                    if  not this.onlyVisual then
                        call GroupEnumUnitsInRange(enumGroup, this.x, this.y, damageArea, null)
                        
                        set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, this.x, this.y, 0)
                        call UnitRemoveAbility(dummyCaster, 'Amov')
                        call ShowUnit(dummyCaster, false)
                        call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
                        call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
                                
                        loop
                            set u = FirstOfGroup(enumGroup)
                            exitwhen u == null
                            
                            //Check allowed targets
                            if isTargetAllowed(u, this.caster) then
                                //Deal damage and cast dummy spell on damaged units
                                call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                                call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
                            endif  
                            call GroupRemoveUnit(enumGroup, u)
                        endloop
                        
                        call RemoveUnit(dummyCaster)
                    endif
            
                    static if not NO_MISSILES then
                        call KillUnit(this.dummy)
                    endif
                    
                    call this.destroy()
                endif
                set this.executions = this.executions - 1
                set this = this.next
            
            endloop
        
            set dummyCaster = null
        
            //Return true if number of dummy units is 0. This value will be used to determine, if it's safe
            //to turn off the timer.
            return count == 0
    
        endmethod
    
        static method create takes unit caster, player owner, real x, real y, real damage, boolean onlyVisual returns thistype
            local thistype this = thistype.allocate()
            local real angle
            local real offset
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
            set count = count + 1

            set this.shardsPerWave = setShardsPerWave(caster)
            set this.area = setArea(caster)
            
            set this.heightDecrement = HEIGHT/FALLING_TIME*TIMEOUT
            
            set angle = GetRandomReal(0, 2*bj_PI)
            set offset = GetRandomReal(0, this.area)
            set this.x = x + offset*Cos(angle)
            set this.y = y + offset*Sin(angle)
            
            static if NO_MISSILES then
                //Create stationary special effect
                set this.dummy = null
                set this.missileEffect = AddSpecialEffect(MISSILE_ART, this.x, this.y)
            else
                //Create falling missile
                set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x , this.y, 0)
                set this.missileEffect = AddSpecialEffectTarget(MISSILE_ART, this.dummy, "origin")
                call UnitAddAbility(this.dummy, 'Arav')
                call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
            endif
            
            //If INDIVIDUAL_DAMAGE == false, the casting point will be used as a damage center.
            static if not INDIVIDUAL_DAMAGE then
                set this.x = x
                set this.y = y
            endif
            
            set this.executions = R2I(HEIGHT/this.heightDecrement)
            set this.damage = damage
            
            set this.caster = caster
            set this.ownerOfCaster = owner
            
            //This is true for all missiles except one per wave, if INDIVIDUAL_DAMAGE == false.
            set this.onlyVisual = onlyVisual
            
            return this
        endmethod
    endstruct

    private struct Spell
    
        private thistype next
        private thistype prev
        private static timer iterator = CreateTimer()
        private static integer count = 0

        private real waveInterval
        private integer shardsPerWave
        private real duration
        
        private unit caster
        private player ownerOfCaster
        private real damage
        private boolean channelling = true
        private real dummyInterval = 0
        private real x
        private real y
        private integer executions
        
        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set count = count - 1
            set this.caster = null
            set this.ownerOfCaster = null
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local boolean spellEnd = false
            local integer i
            local sound loopSound
            loop
                exitwhen this == 0
            
                //Create a wave of missiles once per waveInterval (seconds)
                if this.dummyInterval >= this.waveInterval then
                    set i = 0
                    loop
                        exitwhen i == this.shardsPerWave
                        //If INDIVIDUAL_DAMAGE == false, only one missile per wave will deal damage, others are "only visual"
                        call Dummy.create(this.caster, this.ownerOfCaster, this.x, this.y, this.damage, not ((i == 0) or INDIVIDUAL_DAMAGE))
                        set i = i + 1
                    endloop
                    set this.dummyInterval = 0
                else
                    set this.dummyInterval = this.dummyInterval + TIMEOUT
                endif
                
                set loopSound = CreateSoundFromLabel(LOOP_SOUND, false, true, true, 2000, 2000)
                call SetSoundPosition(loopSound, this.x, this.y, 0)
                call SetSoundVolume(loopSound, 127)
                call StartSound(loopSound)
                call KillSoundWhenDone(loopSound)
            
                set this.executions = this.executions - 1
            
                //Destroy this spell instance
                static if REQUIRES_CHANNELLING then
                    if (not this.channelling) or executions <= 0 then
                        call this.destroy()
                    endif
                else
                    if executions <= 0 then
                        call this.destroy()
                    endif
                endif
            
                set this = this.next
            
            endloop
        
            //move all dummy units
            set spellEnd = Dummy.move()
        
            //if there are no dummy units and no units casting the spell, stop timer
            if spellEnd and count == 0 then
                call PauseTimer(iterator)
            endif
            
            set loopSound = null
        endmethod

        private static method run takes nothing returns boolean
            local thistype this
            
            if GetSpellAbilityId() == ABIL_CODE then
        
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
                set count = count + 1
                
                if count == 1 then
                    call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
                endif

                set this.caster = GetTriggerUnit()
                set this.ownerOfCaster = GetOwningPlayer(this.caster)
                
                static if TARGET_ABILITY then
                    set this.x = GetSpellTargetX()
                    set this.y = GetSpellTargetY()
                else
                    set this.x = GetUnitX(this.caster)
                    set this.y = GetUnitY(this.caster)
                endif
                
                set this.waveInterval = setWaveInterval(this.caster)
                set this.shardsPerWave = setShardsPerWave(this.caster)
                set this.duration = setDuration(this.caster)
                set this.damage = setDamage(this.caster)
                
                set this.executions = R2I(this.duration/TIMEOUT)
                set this.dummyInterval = this.waveInterval
            endif

            return false
        endmethod

        private static method stop takes nothing returns boolean
            local thistype this = thistype(0).next
            if GetSpellAbilityId() == ABIL_CODE then
                loop
                    exitwhen GetTriggerUnit() == this.caster  or this == 0
                    set this = this.next
                endloop
                set this.channelling = false
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local trigger t2 = CreateTrigger()        
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
        
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(t2, Condition(function thistype.stop))
        endmethod
        
    endstruct

endscope
JASS:
//#################################
//#Flame Strike triggered version #
//#Created by Erkki2              #
//#################################
scope FlameStrike

    native UnitAlive takes unit id returns boolean
    
//  #################
//  # CONFIGURABLES #
//  #################
    globals
        
        //Raw code of the spell ability
        private constant integer ABIL_CODE = 'A004'
        
        //Iteration interval
        private constant real TIMEOUT = 0.03125
        
        //Effect shown while channelling
        private constant string CHANNEL_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl"
        
        //The primary effect of the spell
        private constant string SPELL_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrike1.mdl"
        
        //Burning ground effect
        private constant string BURN_EFFECT = "Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeEmbers.mdl"
        
        //Delay between ahowing SPELL_EFFECT and starting to deal primary damage
        private constant real DAMAGE_DELAY = 0.5
        
        //duration of primary damage and effect
        private constant real SPELL_TIME = 3
        
        //duration of secondary damage and burning effect
        private constant real BURN_TIME = 5
        
        //Attack type of the spell damage (hero, siege, chaos, jne...)
        private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
        
        //Damage type of the spell (normal, universal, magic etc...)
        private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
    endglobals

    //The spell area
    private function setArea takes unit caster returns real
        return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Damage that is dealt periodically by spell
    private function setPrimaryDamage takes unit caster returns real
        return 10.0 + 2.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Damage that is dealt periodically by burning ground
    private function setSecondaryDamage takes unit caster returns real
        return 3.0 + 1.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Time it takes to cast the spell.
    //The spell must have equally long or longer casting time in object editor.
    private function setChannelTime takes unit caster returns real
        return 1.5
    endfunction
    
    //How often primary damage is dealt
    private function setDamageInterval takes unit caster returns real
        return 0.5 - 0.1 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //how often secondary damage is dealt
    private function setBurnInterval takes unit caster returns real
        return 2.0 - 0.2 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //How many burning particles there are on the ground.
    //Even number is recommended for more symmetric effect.
    private function setParticleCount takes unit caster returns integer
        return 10 + 4 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction

    //Returns true, if target unit can be hit by the spell
    private function isTargetAllowed takes unit target, unit caster returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
    endfunction
    
//  ############################################################
//  # CONFIGURABLES END, Don't touch anything after this line. #
//  ############################################################


    private struct BurningGround

        private thistype next
        private thistype prev

        private static integer count = 0
        
        private thistype prevParticle
        private effect eff

        //Destroy particle and all other particles, that are linked to it
        method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set count = count - 1
            call DestroyEffect(this.eff)
            set this.eff = null
            if not (this.prevParticle == 0) then
                call this.prevParticle.destroy()
            endif
        endmethod
    
        static method create takes unit caster, real x, real y, real area, integer particleCount returns thistype
            local thistype this = 0
            local real angle
            local real offset
            local integer i = 0
            local thistype previous = 0
            local boolean outerRound = true
            
            //Create a chain of particles, and return the last particle
            loop
                exitwhen i == particleCount
                
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
                set count = count + 1
                
                set this.prevParticle = previous
                set previous = this
                
                set angle = i*2*bj_PI/particleCount
                if outerRound then
                    set offset = 0.8*area
                    set outerRound = false
                else
                    set offset = 0.3*area
                    set outerRound = true
                endif
                set eff = AddSpecialEffect(BURN_EFFECT, x + offset*Cos(angle), y + offset*Sin(angle))
                
                set i = i + 1
            endloop
            return this
        endmethod
    endstruct

    private struct Spell
    
        private thistype next
        private thistype prev
        private static timer iterator = CreateTimer()
        private static integer count = 0
        private static group enumGroup = CreateGroup()
        
        private unit caster
        private boolean channelling
        private real x
        private real y
        
        //The time, how long the current phase has been going
        private real castTime
        
        private real damageCounter = 0
        private effect eff
        private BurningGround burnEffect
        
        private real area
        private real channelTime
        private real primaryDamage
        private real secondaryDamage
        private real damageInterval
        private real burnInterval
        
        //phase == 0: channelling
        //phase == 1: delay between effect and damage
        //phase == 2: spell primary damage
        //phase == 3: burning, secondary damage
        private integer phase
        
        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set count = count - 1
            call DestroyEffect(this.eff)
            set this.eff = null
            set this.caster = null
            
            //if there are no spell instances running, pause the timer
            if count == 0 then
                call PauseTimer(iterator)
            endif
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local unit u
                        
            loop
                exitwhen this == 0
            
                //The spell is being channelled
                if this.phase == 0 then
                    
                    set this.castTime = this.castTime + TIMEOUT
                    if this.castTime >= this.channelTime then
                        set this.phase = 1
                        call DestroyEffect(this.eff)
                        set this.eff = AddSpecialEffect(SPELL_EFFECT, this.x, this.y)
                        set this.castTime = 0
                    elseif not this.channelling then
                        call this.destroy()
                    endif
                
                //Delay between the primary spell effect and primary damage
                elseif this.phase == 1 then
                    set this.castTime = this.castTime + TIMEOUT
                    if this.castTime >= DAMAGE_DELAY then
                        set this.phase = 2
                        set this.castTime = 0
                    endif
                    
                //Deal primary damage while showing SPELL_EFFECT
                elseif this.phase == 2 then
                    set this.castTime = this.castTime + TIMEOUT
                    if this.castTime >= SPELL_TIME then
                        set this.phase = 3
                        call DestroyEffect(this.eff)
                        set this.burnEffect = BurningGround.create(this.caster, this.x, this.y, this.area, setParticleCount(this.caster))
                        set this.castTime = 0
                        set this.damageCounter = this.burnInterval
                    else
                        if this.damageCounter >= this.damageInterval then
                            call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
                            loop
                                set u = FirstOfGroup(enumGroup)
                                exitwhen u == null
                                        
                                //Check allowed targets
                                if isTargetAllowed(u, this.caster) then
                                    //Deal damage
                                    call UnitDamageTarget(this.caster, u, this.primaryDamage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                                endif  
                                call GroupRemoveUnit(enumGroup, u)
                            endloop
                            set this.damageCounter = this.damageCounter - this.damageInterval
                        endif
                        set this.damageCounter = this.damageCounter + TIMEOUT
                    endif
                    
                //Deal secondary damage while showing BURN_EFFECT
                else //this.phase == 3
                    set this.castTime = this.castTime + TIMEOUT
                    if this.castTime >= BURN_TIME then
                        call this.burnEffect.destroy()
                        call this.destroy()
                    else
                        if this.damageCounter >= this.burnInterval then
                            call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.area, null)
                            loop
                                set u = FirstOfGroup(enumGroup)
                                exitwhen u == null
                                        
                                //Check allowed targets
                                if isTargetAllowed(u, this.caster) then
                                    //Deal damage
                                    call UnitDamageTarget(this.caster, u, this.secondaryDamage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                                endif  
                                call GroupRemoveUnit(enumGroup, u)
                            endloop
                            set this.damageCounter = this.damageCounter - this.damageInterval
                        endif
                        set this.damageCounter = this.damageCounter + TIMEOUT
                    endif
                
                endif
            
                set this = this.next
            
            endloop
            
        endmethod

        private static method run takes nothing returns boolean
            local thistype this
            
            if GetSpellAbilityId() == ABIL_CODE then
        
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
                set count = count + 1
                
                if count == 1 then
                    call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
                endif

                set this.caster = GetTriggerUnit()
                set this.x = GetSpellTargetX()
                set this.y = GetSpellTargetY()
                set this.phase = 0
                set this.channelling = true
                set this.castTime = 0
                
                set this.area = setArea(this.caster)
                set this.channelTime = setChannelTime(this.caster)
                set this.primaryDamage = setPrimaryDamage(this.caster)
                set this.secondaryDamage = setSecondaryDamage(this.caster)
                set this.damageInterval = setDamageInterval(this.caster)
                set this.burnInterval = setBurnInterval(this.caster)
                
                set this.damageCounter = this.damageInterval
                set this.eff = AddSpecialEffect(CHANNEL_EFFECT, this.x, this.y)
            endif

            return false
        endmethod

        private static method stop takes nothing returns boolean
            local thistype this = thistype(0).next
            if GetSpellAbilityId() == ABIL_CODE then
                loop
                    exitwhen this == 0
                    if this.caster == GetTriggerUnit() then
                        set this.channelling = false
                    endif
                    set this = this.next
                endloop
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local trigger t2 = CreateTrigger()        
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
        
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(t2, Condition(function thistype.stop))
        endmethod
        
    endstruct

endscope
JASS:
//#################################
//#Shockwave triggered version    #
//#Created by Erkki2              #
//#################################
scope Shockwave

    native UnitAlive takes unit id returns boolean
    
//  #################
//  # CONFIGURABLES #
//  #################
    globals
        
        //Raw code of the spell ability
        private constant integer ABIL_CODE = 'A006'
        
        private constant integer DUMMY_ID = 'h000'
        
        //Iteration interval
        private constant real TIMEOUT = 0.03125
        
        //The missile effect
        private constant string MISSILE_EFFECT = "Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl"
        
        //Raw code of the dummy ability. This spell will be cast on all units hit by the spell. If no spell is wanted, set this to 0.
        private constant integer DUMMY_ABILITY = 0
        
        //Order string for dummy spell
        private constant string DUMMY_ORDER = ""
        
        //Speed of the projectile
        private constant real SPEED = 700.0
        
        //Set this to true, if you want a unit to be damaged only once by a single spell
        private constant boolean DAMAGE_ONLY_ONCE = true
        
        //Set this to true to enable terrain deformation
        private constant boolean TERRAIN_DEFORMATION = true
        
        //This determines how wide area is damaged. If < 1, the whole are isn't covered. If 2, every point in the path is damaged twice.
        private constant real DAMAGE_OVERLAP = 2.0
        
        //If true, the spell area is filled by selected special effects
        private constant boolean FILL_AREA = false
        
        //Distance between fill particles
        private constant real FILL_DISTANCE = 30.0
        
        //Special effect which is used for filling area
        private constant string FILL_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
        
        //If true, fill effects are attached on dummy units for a more realistic effect. If that is not necessary,
        //it is advisable to have this option set to false for better performance.
        private constant boolean FILL_CORRECT_FACING = false
        
        //If true, secondary missiles are sent following the borders of the spell area
        private constant boolean BORDER_MISSILES = false
        
        //Art of the secondary missiles
        private constant string BORDER_EFFECT = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"

        //Attack type of the spell damage (hero, siege, chaos, jne...)
        private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
        
        //Damage type of the spell (normal, universal, magic etc...)
        private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
    endglobals

    //The spell distance
    private function setDistance takes unit caster returns real
        return 800.0
    endfunction
    
    //Cone width at caster
    private function setStartArea takes unit caster returns real
        return 100.0
    endfunction
    
    //Cone width at target
    private function setEndArea takes unit caster returns real
        return 100.0
    endfunction
    
    //Damage that is dealt by spell
    private function setDamage takes unit caster returns real
        return 100.0 + 50*GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction

    //Returns true, if target unit can be hit by the spell
    private function isTargetAllowed takes unit target, unit caster returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target)
    endfunction
    
//  ############################################################
//  # CONFIGURABLES END, Don't touch anything after this line. #
//  ############################################################

    private struct Spell
    
        private thistype next
        private thistype prev
        private static timer iterator = CreateTimer()
        private static integer count = 0
        private static group enumGroup = CreateGroup()
        
        private unit caster
        private player ownerOfCaster
        private unit dummy
        private integer executions
        private effect eff
        private real dx
        private real dy
        private real tipX
        private real tipY
        private real sliceWidth
        private real maxArea
        private real damage
        private real rightAngle
        private real leftAngle
        private group damagedUnits
        private real x
        private real y
        
        private real currWidth
        private real widthInc
        private real angle
        
        private real border1X
        private real border1Y
        private real border2X
        private real border2Y
        private unit border1Unit
        private unit border2Unit
        private effect border1Eff
        private effect border2Eff
        
        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            set count = count - 1
            call KillUnit(this.dummy)
            call KillUnit(this.border1Unit)
            call KillUnit(this.border2Unit)
            call DestroyEffect(this.eff)
            call DestroyEffect(this.border1Eff)
            call DestroyEffect(this.border2Eff)
            call DestroyGroup(this.damagedUnits)
            set this.damagedUnits = null
            set this.border1Unit = null
            set this.border2Unit = null
            set this.border1Eff = null
            set this.border2Eff = null
            set this.eff = null
            set this.caster = null
            set this.dummy = null
            set this.ownerOfCaster = null
            
            //if there are no spell instances running, pause the timer
            if count == 0 then
                call PauseTimer(iterator)
            endif
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local unit u
            local real dummyDist
            local real targetDist
            local real diffX
            local real diffY
            local real angleToTip
            local real targetX
            local real targetY
            local unit dummyCaster
            local real width
            local real reverseAngle
            local real cosOfReverseAngle
            local real sinOfReverseAngle
                        
            loop
                exitwhen this == 0
            
                if this.executions == 0 then
                    call this.destroy()
                else
                
                    //Move the projectile
                    set this.x = GetUnitX(this.dummy) + this.dx
                    set this.y = GetUnitY(this.dummy) + this.dy
                    call SetUnitX(this.dummy, this.x)
                    call SetUnitY(this.dummy, this.y)
                    
                    //if border missiles are enabled, move them too
                    static if BORDER_MISSILES then
                        call SetUnitX(this.border1Unit, GetUnitX(this.border1Unit) + this.border1X)
                        call SetUnitY(this.border1Unit, GetUnitY(this.border1Unit) + this.border1Y)
                        call SetUnitX(this.border2Unit, GetUnitX(this.border2Unit) + this.border2X)
                        call SetUnitY(this.border2Unit, GetUnitY(this.border2Unit) + this.border2Y)
                    endif
                    
                    static if FILL_AREA then
                        set width = 0
                        set reverseAngle = this.angle + bj_PI/2
                        set cosOfReverseAngle = Cos(reverseAngle)
                        set sinOfReverseAngle = Sin(reverseAngle)
                        
                        //Create one effect at the middle line
                        static if FILL_CORRECT_FACING then
                            set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, this.angle*bj_RADTODEG)
                            call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
                            call KillUnit(u)
                        else
                            call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x, this.y))
                        endif
                        
                        //Create N more effects vertical to the cast angle, with FILL_DISTANCE between each effect
                        loop
                            //Set offset from middle line, and create effect on each side
                            set width = width + FILL_DISTANCE
                            exitwhen width > this.currWidth
                            static if FILL_CORRECT_FACING then
                                set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + width*cosOfReverseAngle, this.y + width*sinOfReverseAngle, this.angle*bj_RADTODEG)
                                call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
                                call KillUnit(u)
                                set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - width*cosOfReverseAngle, this.y - width*sinOfReverseAngle, this.angle*bj_RADTODEG)
                                call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
                                call KillUnit(u)
                            else
                                call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x + width*cosOfReverseAngle, this.y + width*sinOfReverseAngle))
                                call DestroyEffect(AddSpecialEffect(FILL_EFFECT, this.x - width*cosOfReverseAngle, this.y - width*sinOfReverseAngle))
                            endif
                        endloop
                        set this.currWidth = this.currWidth + this.widthInc
                    endif
                    
                    //Create caster for optional spell effects
                    set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, this.x, this.y, 0)
                    call UnitRemoveAbility(dummyCaster, 'Amov')
                    call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
                    call ShowUnit(dummyCaster, false)
                    call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
                    call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
                    
                    call GroupEnumUnitsInRange(enumGroup, this.x, this.y, this.maxArea, null)
                    loop
                        set u = FirstOfGroup(enumGroup)
                        exitwhen u == null
                                
                        set targetX = GetUnitX(u)
                        set targetY = GetUnitY(u)
                        
                        //Calculate target unit's distance from the tip of the cone
                        set diffX = targetX - tipX
                        set diffY = targetY - tipY
                        set targetDist = SquareRoot(diffX*diffX + diffY*diffY)
                        
                        //Calculate the distance between the projectile and tip
                        set diffX = this.x - tipX
                        set diffY = this.y - tipY
                        set dummyDist = SquareRoot(diffX*diffX + diffY*diffY)
                        
                        //Calculate target unit's direction from the tip
                        set angleToTip = Atan2(this.tipY - targetY, this.tipX - targetX)
                                
                        //The unit is in the right direction...
                        if ((angleToTip > this.leftAngle and angleToTip < this.rightAngle)                                      /* 
*/                      or (this.leftAngle > this.rightAngle and (angleToTip < this.leftAngle or angleToTip > this.rightAngle)))  /*
                        //...and at the right distance.
*/                      and targetDist - dummyDist < this.sliceWidth and dummyDist - targetDist < this.sliceWidth               /*
                        //Check allowed targets
*/                      and isTargetAllowed(u, this.caster) then

                            //If DAMAGE_ONLY_ONCE is enabled, only damage units which aren't in damagedUnits group, and add damaged units to group...
                            static if DAMAGE_ONLY_ONCE then
                                if not IsUnitInGroup(u, this.damagedUnits) then
                                    call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                                    call GroupAddUnit(this.damagedUnits, u)
                                    if not (DUMMY_ABILITY == 0) then
                                        call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
                                    endif
                                endif
                            //...else damage just damage units
                            else
                                call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                                if not (DUMMY_ABILITY == 0) then

                                    call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
                                endif
                            endif
                        endif  
                        call GroupRemoveUnit(enumGroup, u)
                    endloop
                    
                    call RemoveUnit(dummyCaster)
                    
                    set this.executions = this.executions - 1
                endif
            
                set this = this.next
            
            endloop
            
            set dummyCaster = null
        endmethod

        private static method run takes nothing returns boolean
            local thistype this
            local real distPerIter
            local real distance
            local real startArea
            local real endArea
            local real distToTip
            local real targetX
            local real targetY
            local real areaOffsetX
            local real areaOffsetY
            
            if GetSpellAbilityId() == ABIL_CODE then
        
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
                set count = count + 1
                
                if count == 1 then
                    call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
                endif

                set this.caster = GetTriggerUnit()
                set this.ownerOfCaster = GetOwningPlayer(this.caster)
                
                set this.x = GetUnitX(this.caster)
                set this.y = GetUnitY(this.caster)
                set targetX = GetSpellTargetX()
                set targetY = GetSpellTargetY()
                
                set distance = setDistance(this.caster)
                set this.executions = R2I(distance/SPEED/TIMEOUT)
                
                //Angle from cast point to target point
                set this.angle = Atan2(targetY - this.y, targetX - this.x)
                
                //set target point to a "correct" value
                set targetX = this.x + distance*Cos(this.angle)
                set targetY = this.y + distance*Sin(this.angle)

                //Calculate travelled distance per iteration
                set distPerIter = distance/this.executions
                set this.dx = distPerIter*Cos(this.angle)
                set this.dy = distPerIter*Sin(this.angle)
                
                //Create projectile
                set this.dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x, this.y, this.angle*bj_RADTODEG)
                set this.eff = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
                
                //Calculate the width of an area, which is damaged per iteration
                set this.sliceWidth = DAMAGE_OVERLAP*distPerIter/2
                set startArea = setStartArea(this.caster)
                set endArea = setEndArea(this.caster)
                
                //Cone is \/ shaped (cast upwards)
                if startArea < endArea then
                
                    //Tip of the cone is at cast point
                    if startArea == 0 then
                        set distToTip = 0
                    else
                        set distToTip = distance*startArea/(endArea - startArea)
                    endif
                    set this.tipX = this.x - distToTip*Cos(this.angle)
                    set this.tipY = this.y - distToTip*Sin(this.angle)
                    set this.maxArea = endArea
                    
                    //Calculate borderlines of the cone
                    set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
                    set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
                    set this.rightAngle = Atan2(this.tipY - (targetY + areaOffsetY), this.tipX - (targetX + areaOffsetX))
                    set this.leftAngle = Atan2(this.tipY - (targetY - areaOffsetY), this.tipX - (targetX - areaOffsetX))
                    
                //The cone is /\ shaped (cast upwards)
                elseif startArea > endArea then
                
                    //Tip of the cone is at target point
                    if endArea == 0 then
                        set distToTip = 0
                    else
                        set distToTip = distance*endArea/(startArea - endArea)
                    endif
                    set this.tipX = targetX + distToTip*Cos(this.angle)
                    set this.tipY = targetY + distToTip*Sin(this.angle)
                    set this.maxArea = startArea
                    
                    //Calculate borderlines of the cone
                    set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
                    set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
                    set this.leftAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
                    set this.rightAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
                
                //The Cone is || shaped, in other words a line
                else // startArea == endArea
                
                    //Set tip of the cone very far behind the caster, which makes the cone to spread very little
                    set distToTip = 100000
                    set this.tipX = this.x - distToTip*Cos(this.angle)
                    set this.tipY = this.y - distToTip*Sin(this.angle)
                    set this.maxArea = startArea
                    
                    //Calculate borderlines of the cone
                    set areaOffsetX = endArea*Cos(this.angle + bj_PI/2)
                    set areaOffsetY = endArea*Sin(this.angle + bj_PI/2)
                    set this.rightAngle = Atan2(this.tipY - (this.y + areaOffsetY), this.tipX - (this.x + areaOffsetX))
                    set this.leftAngle = Atan2(this.tipY - (this.y - areaOffsetY), this.tipX - (this.x - areaOffsetX))
                endif
                
                set this.damage = setDamage(this.caster)
                set this.damagedUnits = CreateGroup()
                
                static if TERRAIN_DEFORMATION then
                    call TerrainDeformWave(this.x, this.y, targetX, targetY, distance, SPEED, startArea, 150, 2, 1)
                endif
                
                static if FILL_AREA then
                    set this.currWidth = startArea
                    set this.widthInc = (endArea - startArea)/this.executions
                endif
                
                static if BORDER_MISSILES then
                    set border1X = -distPerIter*Cos(this.rightAngle)
                    set border1Y = -distPerIter*Sin(this.rightAngle)
                    set border2X = -distPerIter*Cos(this.leftAngle)
                    set border2Y = -distPerIter*Sin(this.leftAngle)
                    set areaOffsetX = startArea*Cos(this.angle + bj_PI/2)
                    set areaOffsetY = startArea*Sin(this.angle + bj_PI/2)
                    set this.border1Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + areaOffsetX, this.y + areaOffsetY, this.rightAngle*bj_RADTODEG)
                    set this.border2Unit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - areaOffsetX, this.y - areaOffsetY, this.leftAngle*bj_RADTODEG)
                    set this.border1Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border1Unit, "origin")
                    set this.border2Eff = AddSpecialEffectTarget(BORDER_EFFECT, this.border2Unit, "origin")
                endif
            endif

            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
        endmethod
        
    endstruct

endscope
JASS:
//###################################
//#Volcano triggered version        #
//#Created by Erkki2                #
//#Required libraries:              #
//#     Geometry by Erkki2          #
//###################################
scope Volcano

    native UnitAlive takes unit id returns boolean
    
    globals

//  #################
//  # CONFIGURABLES #
//  #################

        //Dummy unit used for moving effects and casting spell
        private constant integer DUMMY_ID = 'h000'
        
        //Spell ability
        private constant integer ABIL_CODE = 'A008'
        
        //Iteration interval
        private constant real TIMEOUT = 0.03125
        
        //Speed of the missiles
        private constant real SPEED = 350
        
        //Max height of the missiles
        private constant real HEIGHT = 600
        
        //Effect when missiles land
        private constant string LANDING_EFFECT = "Abilities\\Spells\\Orc\\LiquidFire\\Liquidfire.mdl"
        
        //Missile art
        private constant string MISSILE_EFFECT = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl"
        
        //Effect that stays at the target point while the spell is cast
        private constant string CAST_EFFECT = "Abilities\\Spells\\Other\\Volcano\\Volcano.mdl"
        
        //Ability that is cast on all hit units. Set this to 0 if there's no ability.
        private constant integer DUMMY_ABILITY = 'A009'
        
        //Order string for DUMMY_ABILITY
        private constant string DUMMY_ORDER = "creepthunderbolt"
        
        //Time between missiles in the same wave
        private constant real MISSILE_INTERVAL = 0.2
        
        //Time between waves
        private constant real WAVE_INTERVAL = 3
        
        //Time before first wave
        private constant real TIME_BEFORE_FIRST_WAVE = 3
        
        //If true, the spell end when caster stops channelling
        private constant boolean REQUIRES_CHANNELLING = true
        
        //Area which is damaged when missiles land
        private constant real MISSILE_COLLISION = 100
        
        //Set this to true if the spell has a target point, and to false if the spell is instant.
        private constant boolean TARGET_ABILITY = true
    
        //Attack type of the spell damage (hero, siege, chaos, jne...)
        private constant attacktype ATTACK_TYPE= ATTACK_TYPE_NORMAL
        
        //Damage type of the spell (normal, universal, magic etc...)
        private constant damagetype DAMAGE_TYPE= DAMAGE_TYPE_MAGIC
    endglobals
    
    //Returns the number of missiles in one wave
    private function missilesPerWave takes unit caster returns integer
        return 7
    endfunction
    
    //Returns the spell area
    private function setArea takes unit caster returns real
        return 300.0 + 100.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the spell duration.
    private function setDuration takes unit caster returns real
        return 12.0 + 4.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns damage dealt when a missile lands
    private function setDamage takes unit caster returns real
        return 40.0 + 20.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns true, if target unit can be hit by the spell
    private function isTargetAllowed takes unit target, unit caster returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and UnitAlive(target)
    endfunction

//  ############################################################
//  # CONFIGURABLES END, Don't touch anything after this line. #
//  ############################################################

    private struct Dummy

        private thistype next
        private thistype prev

        private static integer count = 0
        private static group enumGroup = CreateGroup()

        private unit dummy
        //The distance the dummy unit is about to travel
        private real dist
        //Already travelled distance
        private real travelled
        private real dx
        private real dy
        private unit caster
        private player ownerOfCaster
        private real damage
        private effect attachment

        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            call KillUnit(this.dummy)
            call DestroyEffect(this.attachment)
            set this.attachment = null
            set this.caster = null
            set this.dummy = null
            set this.ownerOfCaster = null
            set this.count = this.count - 1
        endmethod

        static method move takes nothing returns boolean
    
            local thistype this = thistype(0).next
            local real x
            local real y
            local group enumGroup
            local unit u
            local unit dummyCaster
            loop
                exitwhen this == 0
            
                //Move dummy units
                set x = GetUnitX(this.dummy) + this.dx
                set y = GetUnitY(this.dummy) + this.dy
                call SetUnitX(this.dummy, x)
                call SetUnitY(this.dummy, y)
                call SetUnitFlyHeight(this.dummy, getParabolaHeight(this.dist, HEIGHT, this.travelled), 0)
                set this.travelled = this.travelled + SquareRoot(this.dx*this.dx + this.dy*this.dy)        
            
                //If dummy unit has reached its destination, destroy it and damage area
                if this.travelled >= this.dist then
                    call DestroyEffect(AddSpecialEffect(LANDING_EFFECT, x, y))
                    set enumGroup = CreateGroup()
                    
                    //Create caster for optional spell effects
                    set dummyCaster = CreateUnit(ownerOfCaster, DUMMY_ID, x, y, 0)
                    call UnitRemoveAbility(dummyCaster, 'Amov')
                    call UnitApplyTimedLife(dummyCaster, 'BTLF', 3)
                    call ShowUnit(dummyCaster, false)
                    call UnitAddAbility(dummyCaster, DUMMY_ABILITY)
                    call SetUnitAbilityLevel(dummyCaster, DUMMY_ABILITY, GetUnitAbilityLevel(this.caster, ABIL_CODE))
                    
                    call GroupEnumUnitsInRange(enumGroup, x, y, MISSILE_COLLISION, null)
                    loop
                        set u = FirstOfGroup(enumGroup)
                        exitwhen u == null
                        if isTargetAllowed(u, this.caster) then
                            call UnitDamageTarget(this.caster, u, this.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE_WHOKNOWS)
                            
                            //Cast DUMMY_ABILITY on all hit units
                            if not (DUMMY_ABILITY == 0) then
                                call IssueTargetOrder(dummyCaster, DUMMY_ORDER, u)
                            endif
                        endif  
                        call GroupRemoveUnit(enumGroup, u)
                    endloop
                    call RemoveUnit(dummyCaster)
            
                    call this.destroy()
                endif
            
                set this = this.next
            
            endloop
        
            set dummyCaster = null
        
            //return true if number of dummy units is 0
            return count == 0
    
        endmethod
    
        static method create takes unit caster, player owner, real x, real y, real damage, real area returns thistype
            local thistype this = thistype.allocate()
            local real angle
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
            set count = count + 1

            set angle = GetRandomReal(0, 2*bj_PI)
            set this.dist = GetRandomReal(10, area)
            set this.dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, x, y, angle*bj_RADTODEG)
            set this.attachment = AddSpecialEffectTarget(MISSILE_EFFECT, this.dummy, "origin")
            set this.travelled = 0
            set this.damage = damage
            set this.caster = caster
            set this.ownerOfCaster = owner
            set this.dx = Cos(angle)*SPEED*TIMEOUT*dist/area
            set this.dy = Sin(angle)*SPEED*TIMEOUT*dist/area
            
            return this
        endmethod
    endstruct

    private struct Spell
        private thistype next
        private thistype prev

        private static timer iterator = CreateTimer()
        private static integer count = 0

        private unit caster
        private player ownerOfCaster
        private real damage
        private boolean channelling = true
        
        //Time before next dummy unit is created
        private real dummyDelay
        private real x
        private real y
        private integer executions
        private effect eff
        
        //Number of dummy units in a wave
        private integer dummyCountTotal
        private real area
        
        //How many dummies of this wave have been already created
        private integer dummyCountCurrent 

        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            call DestroyEffect(this.eff)
            set this.eff = null
            set this.count = this.count - 1
            set this.caster = null
            set this.ownerOfCaster = null
        endmethod

        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local boolean spellEnd = false
            local integer i
            loop
                exitwhen this == 0
            
                set this.dummyDelay = this.dummyDelay - TIMEOUT
            
                set i = 0
                
                //Create dummy units. If MISSILE_INTERVAL < TIMEOUT, more than one will be created.
                loop
                    exitwhen this.dummyDelay >= 0
                    call Dummy.create(this.caster, this.ownerOfCaster, this.x, this.y, this.damage, this.area)
                    set i = i + 1
                    //This dummy is the last of this wave
                    if dummyCountCurrent == dummyCountTotal then
                        set this.dummyDelay = this.dummyDelay + WAVE_INTERVAL
                        set this.dummyCountCurrent = 0
                    else
                        set this.dummyDelay = this.dummyDelay + MISSILE_INTERVAL
                        set this.dummyCountCurrent = this.dummyCountCurrent + 1
                    endif
                endloop

                //Caster stops channelling
                static if REQUIRES_CHANNELLING then
                    if not this.channelling then
                        call this.destroy()
                    endif
                endif
                
                //Spell duration is reached
                set this.executions = this.executions - 1
                if this.executions == 0 then
                    call this.destroy()
                endif
            
                set this = this.next
            
            endloop
        
            //Move all dummy units
            set spellEnd = Dummy.move()
        
            //If there are no dummy units and no units casting the spell, stop timer
            if spellEnd and count == 0 then
                call PauseTimer(iterator)
            endif
        
        endmethod

        private static method run takes nothing returns boolean
            local thistype this
            if GetSpellAbilityId() == ABIL_CODE then
        
                set this = thistype.allocate()
                set this.next = 0
                set this.prev = thistype(0).prev
                set thistype(0).prev.next = this
                set thistype(0).prev = this
                set count = count + 1
                if count == 1 then
                    call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
                endif

                set this.caster = GetTriggerUnit()
                set this.ownerOfCaster = GetOwningPlayer(this.caster)
                set this.damage = setDamage(this.caster)
                
                static if TARGET_ABILITY then
                    set this.x = GetSpellTargetX()
                    set this.y = GetSpellTargetY()
                else
                    set this.x = GetUnitX(this.caster)
                    set this.y = GetUnitY(this.caster)
                endif
                
                set this.area = setArea(this.caster)
                set this.executions = R2I(setDuration(this.caster)/TIMEOUT)
                set this.eff = AddSpecialEffect(CAST_EFFECT, this.x, this.y)
                set this.dummyCountTotal = missilesPerWave(this.caster)
                set this.dummyCountCurrent = 0
                set this.dummyDelay = TIME_BEFORE_FIRST_WAVE
            endif
            return false
        endmethod

        private static method stop takes nothing returns boolean
            local thistype this = thistype(0).next
            if GetSpellAbilityId() == ABIL_CODE then
                loop
                    exitwhen GetTriggerUnit() == this.caster  or this == 0
                    set this = this.next
                endloop
                set this.channelling = false
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local trigger t2 = CreateTrigger()        
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
        
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
            call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(t2, Condition(function thistype.stop))
        endmethod
    endstruct

endscope



Keywords:
standard, pack, blizzard, spellpack, flame strike, fire, ice, shockwave, volcano, molten, rock, earth, ground, fissure
Contents

DefaultSpellsTriggered (Map)

Reviews
IcemanBo: Well coded, and nicely documented. Recommended if you want your own versions of these default spells. http://www.hiveworkshop.com/forums/spells-569/default-spells-triggered-1-08-a-254699/index3.html#post2642126 IcemanBo...

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
Despite being configurable there is no sound of blizzard, unlike the default blizzard, it triggers the loop sound. If this is a pack, why you only post blizzard code? The time out variable should be 0.03125, though it is configurable.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
JASS:
if not (GetSpellAbilityId() == ABIL_CODE) then
    return false
endif
-->
JASS:
if (GetSpellAbilityId() == ABIL_CODE) then
    ......
endif
return false
JASS:
                if (REQUIRES_CHANNELLING and not this.channelling) or executions <= 0 then
                    call this.destroy()
                endif
-->
JASS:
                static if REQUIRES_CHANNELLING then
                    if (not this.channelling) or executions <= 0 then
                        call this.destroy()
                    endif
                endif
if NO_MISSILES then --> static if NO_MISSILES then

if not INDIVIDUAL_DAMAGE then --> static if not INDIVIDUAL_DAMAGE then

For the group enumeration create one global group instead of multiple local ones.

Very readable and clean. Good job.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for feedback, I made the suggested changes. Static ifs were a new thing for me.

@Wrda: Good point, I tested this spell without speakers, so I didn't even notice. Now I've added sound. I haven't made the other spells yet, because I wanted to wait for feedback first. But this is going to be a pack.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
if not NO_MISSILES then --> static if not NO_MISSILES then

if INDIVIDUAL_DAMAGE then --> static if INDIVIDUAL_DAMAGE then

The tooltip is lacking.

You could consider using only one unit as dummycaster instead of using multiple ones.
You will need call SetUnitOwner(...) and call SetUnitAbilityLevel(..)
More or less like
JASS:
                    call SetUnitOwner(dummycaster, GetTriggerPlayer(), false)
                    call IssueTargetOrderById(dummycaster, ORDER_ID, this.aim)
                    call SetUnitOwner(dummycaster, NEUTRAL, false)

All toghete good coding, I'll do a final check later including object editor settings, but it looks ready for approval for me.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Hmm... would it work with only one caster? Casting isn't instant, so I believe that only last dummy per wave would have enough time to cast. I'll test it anyway.

Edit: I tried to make the spell to only use one dummy caster, but couldn't get it to work.
 
Last edited:
Level 18
Joined
Sep 14, 2012
Messages
3,413
JASS:
private function setWaveInterval takes unit caster returns real
        return 0.8 - 0.1 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the number of shards in each wave
    private function setShardsPerWave takes unit caster returns integer
        return 10 + 2 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the spell area
    private function setArea takes unit caster returns real
        return 200.0 + 50.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction
    
    //Returns the spell duration.
    private function setDuration takes unit caster returns real
        return 7.0 + 3.0 * GetUnitAbilityLevel(caster, ABIL_CODE)
    endfunction

Make those funcs constant and give the level of the ability in parameters. Like this, you store once the lvl of the ability in the function that will call those little ones and you win perf :)
 
Level 10
Joined
Jun 6, 2007
Messages
392
The reason why those configuration functions take unit as a parameter instead of level of the ability, is that the spell effects may depend on additional factors (e.g. hero stats or spell power system). These functions are only called once per spell anyway, so the improvement in the performance wouldn't be noticeable. Thanks for the suggestion though. :)
 
Level 10
Joined
Jun 6, 2007
Messages
392
Spell added: Shockwave. Using FILL_AREA option isn't advisable in the current version, because it begins to lag after many casts. The reason is probably the massive number of dummy units that aren't removed correctly (Yes, they are removed with RemoveUnit(), but that doesn't free all the space). They're required for rotating the effects. Suggestions for fixing this issue are welcome.
 
Level 10
Joined
Jun 6, 2007
Messages
392
@Malhorne: In some of my tests TERRAIN_DEFORMATION == false (e.g. Wave of Frost spell on the test map), so there is no leak and that's not the cause for lag. And the lag only appears when FILL_AREA == true.

@edo494: Alright, that clarifies why terrain deformation is bad. I guess for single player maps it's safe though? Anyway, it can be configured off.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for your request, done. I already had a quite similar spell, so that one was pretty fast to make. Tell me if you think that there should be more configurables with level support, I'm trying to keep a balance between simplicity and configurability.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
UNIT_STATE_LIFE > 0 is not a proper way to check if a unit is dead. Use native UnitAlive trick or UNIT_TYPE_DEAD boolean. This is the only critical flaw I noticed by checking the code briefly. Therefore Needs Fix

You don't have to null u when exitwhen u == null already nulls it.
You could change the default timeout since 0.04 does not look smooth.
You could consider using optional Spell Effect Event library.
You could look into using custom allocators since now the structs (without extending array and the default allocator) generate bloated extra code.
No need to null trigger variables if you are not going to destroy the triggers.
You only need one dummy to damage every unit, not a dummy for each target.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for the feedback! I already made most of the changes, but I'll upload a new version, when I've done them all. There are some things, for which I need clarification:

I replaced GetUnitState(target, UNIT_STATE_LIFE) > 0 with UnitAlive(target), but that gives me an error about an undefined function. So I did it with UNIT_TYPE_DEAD instead.

How can I use only one dummy? If I create one dummy and order it to cast on all targets, it would only have time to cast on the last target, if I'm not mistaken.

For this spellpack, I'm not going to use more external libraries, but I'll check them out for future projects.
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
You can declare UnitAlive like this. You only need one line.
native UnitAlive takes unit id returns boolean

Set cast point and cast backswings to 0. Remove the move ability when the unit is created, 'Amov'. Then the dummy can not move by itself, but you can move it with SetUnitX/Y. And it will instantly cast.

If you set movement speed to 0 in object editor, the unit will also insta-cast but it can not be moved at all.
 
These points pretty much are for all scopes the same:

- Attack- Damage type should be configurable
- Check to pause the periodic timer can be done in method destroy(). No need to check it each in each callback.
- You could use create method for allocation. It will be generated anyways. I don't quite understand why you always make a new static method for it.
- OwnerOfCaster can be set onCast as member. (filter might call it more often)
- I would prefer it if your setter functions would directly take level of ability as argument, not the unit itself.
- If dummies are not recylcled you should manually remove them, or it's death should be set to "should not raise, not decay". So they will be removed onDeath automatically.
- this.channeling probably should be set to false onDeath. Not?

Shockwave:

- Use bj_RADTODEG over Rad2Deg.
- Cos(reverseAngle), Sin(reverseAngle) could be stored in locals, if needed.
- Could you not just create one dummy unit here, then move and then create the second effect:
JASS:
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x + width*Cos(reverseAngle), this.y + width*Sin(reverseAngle), Rad2Deg(this.angle))
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, this.x - width*Cos(reverseAngle), this.y - width*Sin(reverseAngle), Rad2Deg(this.angle))
call DestroyEffect(AddSpecialEffectTarget(FILL_EFFECT, u, "origin"))
call KillUnit(u)

Volcano:

-
JASS:
//How many dummies of this wave have been already created
private integer currentDummy
^Doesn't sound like a fitting name for an integer. It should be like "dummyCountCurrent", and the other one "dummyCountMax".
- Use one static group. Do not create one each iteration, and destroy it after usage.
-
JASS:
set angle = GetRandomReal(0, 2*bj_PI)
set this.dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, x, y, angle)
UnitFacing is in Deg, not Rad. So angle should be between 0 and 360.
____________________________

I like your coding style. Documentation was helpful. Good job so far, I like this coded pack.
For now some things need to be changed. Needs Fix
 
Level 10
Joined
Jun 6, 2007
Messages
392
Changes made:
- Attack and damage types added to configuration constants
- In Flame Strike and Shockwave the check to pause timer is moved to destroy-method. In Blizzard and Volcano that isn't possible, because dummies can still exist after all spell instances are destroyed.
- Renamed new-method to create
- Added owner of caster to members
- The reason why configuration functions take unit as a parameters is, that spell damage can depend on other factors than spell level (hero stats, custom spell damage system, ...). Therefore that is not changed.
- Dummies are set to "can't raise, does not decay"
- Added death event to trigger stop-method call
- Replaced Rad2Deg with bj_RADTODEG
- cosOfReverseAngel and sinOfReverseAngle are now set to variables
- In shockwave, only creating one dummy and moving it won't work, because the effect is attached to the dummy and therefore is also moved. I tested this with frost wave, and only half of the effects were visible.
- replaced dummyCount and currentDummy with dummyCountTotal and dummyCountCurrent
- Modified volcano to use static group for group enums
- In volcano, dummy unit's facing is now in degrees
 
Spell works fine. Approved.

Minor notes:

In your Blizzard scope the endspell variable is not needed in method periodic.
You could directly use Dummy.move() in the if statement, because it already returns a boolean.

Player variables don't need to be nulled, because they will never be destroyed/removed.

In Triggerfunctions that run with EVENT_PLAYER_UNIT events you directly can use GetTriggerPlayer() instead of GetOwningPlayer(parameter).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
in the world of cycles, it costs quite a bit actually.

I believe players are not to be nulled, since you dont create new players when calling Player natives, but I dont say it is wasting performance to do so, because why the statement above is true, wc3 is not about CPU cycles(some people act as if it was tho), I just dont find the logic behind nulling it. Also nonagent handles are not ref-counted, so no need to null these either, from logical point of view(they will get recycled, or at least blizzard proposes so, and until someone can prove this is not true, I will keep this opinion)
 
Level 10
Joined
Jun 6, 2007
Messages
392
Hmm.. for me it works. Try removing line
JASS:
native UnitAlive takes unit id returns boolean
from the beginning of each trigger and create a new trigger, convert it to custom text and replace its contents with
JASS:
library NativeUnitAlive
    native UnitAlive takes unit id returns boolean
endlibrary
 
Hey so sorry for the necrobump, but I tried implementing the shockwave spell in my map, and it's working fine, except for some reason the missile art isn't showing up? Haven't messed with anything related to that though so I dunno what the problem could be :/

edit: maybe I'm dumb lol, is it not going to show up if I don't use dummy.mdx?
edit 2: got dummy.mdx and now the missile shows up, but it just hovers over the caster and doesn't move
 
Last edited:
Top