• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Frozen Spellpack v2.0

.
BANSHEE QUEEN

A. Description
....A mystical hero adept at ice magics. She is low at strength and agility, but has the most intelligence in the Undead race. Has a bit faster move speed but slower attack rate compared to other Undead heroes. She is good at slowing down enemy forces' movement. Is also able to revive some mutants from corpses around when you need some additional muscles. She is quite weak in a direct combats. Strength Heroes is a great danger for her. Most of her abilities are magic based, so magic immune units could also be troublesome.

....She is quite a versatile Hero. She can instantly vanishes away to freeze enemy units' movement, to maximize their casualties when they are losing. Or to run away from them in a losing situation. Basically, she can't do much in a defensive situation, but when offending she may be your main key to defeat them completely.

.
B. Story & Lore
....Banshees are malicious evil creatures. They are formerly known as ancient elves who were fell down at War of the Ancients 10000 years ago. They lost their sanity and realism of their existance, and eventually transformed into a banshee. But, during the rise of the Lich King, Kil'jaeden had found a way how to raise banshees from night elves to strengthen his Scourge army. Some of banshees are raised from legendary heroes, such as Lady Vinlash, Enira Winterwind, Reina Icecrown, and many other heroes.
elven.jpg
....To strengthen his army even more, the Lich King gifted his mighty ice elemental power of Frostmourne down to those special banshees, which soon will be known as Banshee Queens. So unlikely the other banshees, they have ice element as their base abilities. They served the Scourge and the Lich King for a long time until eventually Lady Sylvanas, the Forsaken Queen, rose up the Forsaken. Some of Banshee Queens and their minions were soon joined the Forsaken to rebel againsts Arthas and the Lich King himself, to achieve their true freedoms.​

.
C. Details
235220-albums7600-picture88807.jpg
Icon .... .... .... .... :
[tr][td]
:[td]- Crystalaxzea
- Enira Winterwind
- Elena Faysong
- Lady Vinlash[td]....[td]- Spirit Lanse
- Vion Frostsage
- Chopathrea Frostgale
- Reina Icecrown
[tr]Attributes[td]:[td]Agility
Intelligence
Strength[td]: 11 + 1
: 22 + 3.25
: 16 + 1.75
[tr]Miscs[td]:[td]Base damage
Attack range
Movespeed[td]: 24 - 30
: 500
: 280
Proper names
[tr][td]
Mystical Hero, adept at ice magics. Can learn Cold Addict, Frostwave, Frost Phase, and Frozen Fate.
Tooltips .... .... .. :
[tr][td]

.
D. Abilities
A. Cold Addict

235220-albums7600-picture88808.jpg
Icon :
[tr][td]
Descriptions:
....Inflicts frost magic at a target, slowing it's movement and attack rate, and stopping any mana regeneration. All damages taken from caster's spell will be amplified.

Usage:
....Cold Addict can be used to slow a target and amplify your spell damage on it. This ability can be deadly because it's amplification amount is stackable, not the duration. But the duration will be resetted on every cast on a same unit.

....You can use this ability anytime in any condition. Since this ability has a short cooldown duration and low manacost.

Details:
Level
[TD]
Duration
[TD]
Amount
[TD]
Cooldown
[TD]
Manacost
[tr]
1
[td]
15 s
[td]
10%
[td]
6 s
[td]
25
[tr]
2
[td]
15 s
[td]
25%
[td]
6 s
[td]
25
[tr]
3
[td]
15 s
[td]
40%
[td]
6 s
[td]
25
[tr][TD]​


B. Frostwave

235220-albums7600-picture88809.jpg
Icon :
[tr][td]
Descriptions:
....Sends a lance of ice shards wave to the target point, dealing damage to land units in a line and slowing them for a short duration.

Usage:
....No need much descriptions this ability is simply the main ability of this Hero. Has a good damage, AoE, and slow effect. But has a moderate cooldown duration.

Details:
Level
[TD]
Distance
[TD]
Damage
[TD]
AoE
[TD]
Duration
[TD]
Cooldown
[TD]
Manacost
[tr]
1
[td]
1000
[td]
80
[td]
128
[td]
4(1.325) s
[td]
16 s
[td]
90
[tr]
2
[td]
1000
[td]
140
[td]
128
[td]
4(1.325) s
[td]
16 s
[td]
115
[tr]
3
[td]
1000
[td]
200
[td]
128
[td]
4(1.325) s
[td]
16 s
[td]
140
[tr][TD]​


C. Frost Phase

235220-albums7600-picture88810.jpg
Icon :
[tr][td]
Descriptions:
....Quickly vanishes to the target point. Leaving ice explosion behind and at the target point. Freezes nearby enemy units for a short duration and deals minor damage.

Usage:
....A very versatile ability that has so many advantages in multiple different circumstance. In most cases, it's mainly used to get out from the crowd (battle). While it deals damage it can also freezes all enemies around, both ground and flying units. In a winning condition, this ability can be used to catch some fleeing enemy units to maximize their casualties.

This ability can't be used to blink through obstacles like trees, cliffs, buildings, etc.

Details:
Level
[TD]
Distance
[TD]
Damage
[TD]
AoE
[TD]
Duration
[TD]
Cooldown
[TD]
Manacost
[tr]
1
[td]
400
[td]
40
[td]
300
[td]
2.35(1.4) s
[td]
14 s
[td]
50
[tr]
2
[td]
600
[td]
60
[td]
300
[td]
3(1.9) s
[td]
12 s
[td]
50
[tr]
3
[td]
800
[td]
80
[td]
300
[td]
3.65(2.4) s
[td]
10 s
[td]
50
[tr][TD]​


D. Frozen Fate

235220-albums7600-picture88811.jpg
Icon :
[tr][td]
Descriptions:
....Brings nearby corpses back to life as a muntant. Mutant has Frost Attack ability which will slows their victim down on every attack.

Usage:
....This ability can be important in a defensive situation, when you need some army, you can use this ability to get some. Mutant has a moderate health amount and fast attack speed, but has no armor, low damage. Mutant also has a frost attacks. Very useful to slow enemies movement down in the combat.

Details:
Level
[TD]
AoE
[TD]
Max Count
[TD]
Duration
[TD]
Cooldown
[TD]
Manacost
[tr]
1
[td]
900
[td]
12
[td]
60 s
[td]
150 s
[td]
200
[tr][TD]​


Keywords:
Hero, Banshee, Queen, frozen, spellpack
Contents

Frozen Spellpack v2.0 (Map)

Reviews
IcemanBo: Great spell pack. It is well coded and also provides some nice visuals. Recommended with tendency to Highly Recommended. Some notes: http://www.hiveworkshop.com/forums/spells-569/frozen-spellpack-v1-5-a-257177/index2.html#post2638509...

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
So, that was all the descriptions about the hero.

This is one of my submission for the ongoing hero contest. Yes, currently I'm making another hero for the contest and not sure which one I should submit.

The spellpack was actually named ForzenSpellpack. But since I upload here with the Hero and her details, so I will just name the thread Hero Forsaken Queen.

Here is the code, 4 in 1
JASS:
scope FrozenSpellpack
   /*
    *              BANSGEE QUEEN SPELLPACK
    *                              v2.0
    *
    *   This spellpack consists of 4 active spells
    *   They  are  Cold Addict,  Frostwave,  Frost
    *   Phase, and Frozen Fate (ultimate).
    *   Their  codes  are packed into one  library
    *   means  that they are requiring each other.
    *
    *   !This spellpack requires JNGP to work!
    *
    *   External Dependencies:
    *     (Required)
    *       - CTL
    *       - IsTerrainWalkable
    *       - IsUnitCorpse
    *       - WorldBounds
    *       - UnitIndexer
    *
    *     (Optional)
    *       - SpellEffectEvent
    *       - AutoFly
    *
    *   How to install:
    *       - Install  and  configure   all   external
    *         dependecies properly.
    *       - Copy all abilities (UD), units (UD), and
    *         buffs at Object Editor into your map.
	*	    - Copy this trigger into your map.
    *       - Configure the spells correctly.
    *
    *   Credits:
    *       - Nestharus
    *       - Bribe
    *       - Cokemonkey11
    *       - Anitarf
    *       - Vexorian
    *       - Magtheridon96
    *
    *
    *                   CONFIGURATION
    *
    *   Configuration  is  separated  into  several
    *   parts. Each part is reserved for one spell.
    */
    /* A. Global */
    globals
        /* Dummy unit's rawcode at Object Editor */
        private constant integer    DUMMY_ID        = 'h000'
        
        /* General decay time for all created sfx */
        private constant real       SFX_DECAY_TIME  = 2.0
    endglobals
    
    /* B. Slow Effect */
    private module SlowData
        /* Slow ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A001'
        
        /* Slow buff's rawcode at Object Editor */
        static constant integer     BUFF_ID         = 'B001'
        
        /* Slow order id */
        static constant integer     SLOW_ORDER      = 852096 /* thunderclap */
        
        /* Attached sfx on slow targets */
        static constant string      SLOW_SFX        = "Abilities\\Spells\\Other\\FrostDamage\\FrostDamage.mdl"
        static constant string      SLOW_SFX_PT     = "chest"
        
        /* Duration is stackable */
        static constant boolean     STACKABLE       = true
        
        /* Will reset the duration if not stacked */
        static constant boolean     RESETABLE       = false
        
        /* The higher, the safer. But just leave it */
        static constant real        BUFF_SAFETY     = 100.0
    endmodule
    
    /* C. Freeze Effect */
    private module FreezeData
        /* Freeze ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A000'
        
        /* Freeze buff's rawcode at Object Editor */
        static constant integer     BUFF_ID         = 'B000'
        
        /* Freeze order id */
        static constant integer     FREEZE_ORDER    = 852127 /* stomp */
        
        /* Attached sfx on freeze targets */
        static constant string      FREEZE_SFX      = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"
        static constant string      FREEZE_SFX_PT   = "origin"
        
        /* Duration is stackable */
        static constant boolean     STACKABLE       = false
        
        /* Will reset the duration if not stacked */
        static constant boolean     RESETABLE       = true
        
        /* The higher, the safer. But just leave it */
        static constant real        BUFF_SAFETY     = 100.0
    endmodule
    
   /*
    *              COLD ADDICT
    
    *   Inflicts frost magic at a  target,  slowing
    *   it's movement and attack rate, and stopping
    *   any mana regeneration.  All  damages  taken
    *   from  caster's  spell  will  be  amplified.
   
    */
    private module ColdAddictData
        /* Main ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A003'
        
        /* Attached sfx on targets */
        static constant string      TARGET_SFX      = "Abilities\\Spells\\Items\\AIob\\AIobTarget.mdl"
        static constant string      TARGET_SFX_PT   = "overhead"
        
        /* Damage amplification amount is stackable */
        static constant boolean     STACKABLE_BUFF  = true
        
        /* Duration is stackable */
        static constant boolean     STACKABLE_DUR   = false
        
        /* Will reset the duration if not stacked */
        static constant boolean     RESETABLE       = true
        
        /* Damage amplification amount */
        static constant method damage takes integer l returns real
            return -0.05 + 0.15 * l
        endmethod
        
        /* Buff duration on normal units */
        static constant method durationNormal takes integer l returns real
            return 15.0
        endmethod
        
        /* Buff duration on hero units */
        static constant method durationHero takes integer l returns real
            return 5.0
        endmethod
    endmodule
    
   /*
    *              FROSTWAVE
    
    *   Sends a lance of ice  shards  wave  to  the
    *   target point, dealing damage to land  units
    *   in a  line and  slowing  them  for a  short
    *   duration.
   
    */
    private module FrostwaveData
        /* Main ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A005'
        
        /* Total of created missiles on every cast */
        static constant integer     SHARD_COUNT     = 23
        
        /* Normal height z for every missile */
        static constant real        LAUNCH_Z        = 64.0
        
        /* Initial speed of every missile */
        static constant real        SPEED_MIN       = 20.0
        
        /* Maximum speed of every missile */
        static constant real        SPEED_MAX       = 40.0
        
        /* Acceleration rate of every missile */
        static constant real        ACCELERATION    = 0.25
        
        /* Turning rate of every missile */
        static constant real        TURN_RATE       = 12.5 * bj_DEGTORAD
        
        /* Missile model path */
        static constant string      MISSILE_PATH    = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
        
        /* Attached sfx on targets */
        static constant string      TARGET_SFX      = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        static constant string      TARGET_SFX_PT   = "origin"
        
        /* Dealt damage configuration */
        static constant attacktype  ATTACK_TYPE     = ATTACK_TYPE_HERO
        static constant damagetype  DAMAGE_TYPE     = DAMAGE_TYPE_COLD
        static constant weapontype  WEAPON_TYPE     = WEAPON_TYPE_WHOKNOWS
        
        /* Max distance from missile to hits a target */
        static constant method aoe takes integer l returns real
            return 128.0
        endmethod
        
        /* Dealt damage amount */
        static constant method damage takes integer l returns real
            return 20.0 + 60.0 * l
        endmethod
        
        /* Travel distance of every missile */
        static constant method distance takes integer l returns real
            return 1000.0
        endmethod
        
        /* Slow duration for normal units */
        static constant method durationNormal takes integer l returns real
            return 4.0
        endmethod
        
        /* Slow duration for hero units */
        static constant method durationHero takes integer l returns real
            return 1.325
        endmethod
        
        /* Configure the targets, by default it's unable to hit structures and magic immune */
        static constant method filter takes unit u, player p returns boolean
            return not(IsUnitAlly(u, p) or IsUnitType(u, UNIT_TYPE_FLYING) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
        endmethod
    endmodule
    
   /*
    *              FROST PHASE
    
    *   Instantly vanishes  to  the  target  point.
    *   Leaving  ice explosion behind  and  at  the
    *   target  point.  Freezes  nearby enemy units
    *   for a short duration and deals minor damage
   
    */
    private module FrostPhaseData
        /* Main ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A004'
        
        /* How often the spell will check for terrain's pathability */
        static constant real        ACCURACY        = 20.0
        
        /* Explosion sfx */
        static constant real        EXPLODE_SIZE    = 2.0
        static constant string      EXPLODE_SFX     = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        
        /* Only damage once for each target */
        static constant boolean     DAMAGE_ONCE     = false
        
        /* Dealt damage configuration */
        static constant attacktype  ATTACK_TYPE     = ATTACK_TYPE_HERO
        static constant damagetype  DAMAGE_TYPE     = DAMAGE_TYPE_COLD
        static constant weapontype  WEAPON_TYPE     = WEAPON_TYPE_WHOKNOWS
        
        /* Dealt damage amount */
        static constant method amount takes integer l returns real
            return 20.0 + 20.0 * l
        endmethod
        
        /* Max range from the center of explosion to hit a target */
        static constant method AoE takes integer l returns real
            return 300.0
        endmethod
        
        /* Max teleport distance */
        static constant method range takes integer l returns real
            return 200.0 + 200.0 * l
        endmethod
        
        /* Freeze duration for normal units */
        static constant method durationNormal takes integer l returns real
            return 1.7 + 0.65 * l
        endmethod
        
        /* Freeze duration for hero units */
        static constant method durationHero takes integer l returns real
            return 0.9 + 0.5 * l
        endmethod
        
        /* Configure the targets, by default it's unable to hit structures and magic immune */
        static constant method filter takes unit u, player p returns boolean
            return not(IsUnitAlly(u, p) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
        endmethod
    endmodule
    
   /*
    *              FROZEN FATE
    
    *   Brings  nearby corpses back to  life  as  a
    *   muntant.  Mutant  has  Frost Attack ability
    *   which  will  slows  their  victim  down  on
    *   every attack.
    
    *   Behind  the  scene,  this  ability  is  not
    *   actually reviving unit as  other  unit  but
    *   removing  old  unit  and  creating new unit
    *   So keep warned.
   
    */
    private module FrozenFateData
        /* Main ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A002'
        
        /* Summoned unit's rawcode at Object Editor */
        static constant integer     SUMMON_ID       = 'u001'
        
        /* Timed life buff's rawcode at Object Editor */
        static constant integer     BUFF_ID         = 'BTLF'
        
        /* Created sfx on missile impact */
        static constant string      IMPACT_SFX      = "Abilities\\Spells\\Undead\\RaiseSkeletonWarrior\\RaiseSkeleton.mdl"
        
        /* Missile model's file path */
        static constant string      MISSILE_SFX     = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
        static constant real        MISSILE_SIZE    = 1.0
        
        /* Movement rate of each missile */
        static constant real        MISSILE_SPEED   = 15.0
        
        /* Normal height z of each missile */
        static constant real        MISSILE_Z       = 0.0
        
        /* Vertical arc of missiles */
        static constant real        V_ARC_MIN       = 0.1
        static constant real        V_ARC_MAX       = 0.4
        
        /* Horizontal arc of missiles */
        static constant real        H_ARC_MIN       = 0.1
        static constant real        H_ARC_MAX       = 0.4
        
        /* Maximum distance to the corpse to successfully revive it */
        static constant real        TOLERATION      = 100.0
        
        /* Maximum summoned unit count */
        static constant method count takes integer l returns integer
            return 12
        endmethod
        
        /* Summoned unit lifespan */
        static constant method lifespan takes integer l returns real
            return 60.0
        endmethod
        
        /* Maximum distance from caster for corpses to be revived */
        static constant method aoe takes integer l returns real
            return 900.0
        endmethod
        
        /* Configure the targeted corpse */
        static constant method filter takes unit u, player p returns boolean
            return not(IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_SUMMONED))
        endmethod
    endmodule
   /*
    *               END OF CONFIGURATION
    
    * ================================================== */
    
    native UnitAlive takes unit id returns boolean
    
    globals
        private player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
    endglobals
    
    /* Function that returns distance between coordinates */
    private function getDistance takes real x, real y, real xt, real yt returns real
        return SquareRoot((xt - x) * (xt - x) + (yt - y) * (yt - y))
    endfunction
    
    /* Function that returns angle between coordinates */
    private function getAngle takes real x, real y, real xt, real yt returns real
        return Atan2(yt - y, xt - x)
    endfunction
    
    /* Function that checks whether given coordinate is in map bound or not */
    private constant function isInBound takes real x, real y returns boolean
        return x > WorldBounds.minX and x < WorldBounds.maxX and y > WorldBounds.minY and y < WorldBounds.maxY
    endfunction

    private struct Slow extends array
    
        implement SlowData
        
        unit target
        real duration
        effect sfx
        
        static unit dummy
        static group group = CreateGroup()
        static boolean array isAffected
        static thistype array index
        
        implement CTLExpire
            set .duration = .duration - .03125
            if .duration <= 0 or not UnitAlive(.target) then
                call DestroyEffect(.sfx)
                call UnitRemoveAbility(.target, BUFF_ID)
                set isAffected[GetUnitUserData(.target)] = false
                call destroy()
                set .sfx = null
                set .target = null
            endif
        
        implement CTLEnd
        
        static method apply takes unit t, real d returns boolean
        
            local thistype this
            local integer i = GetUnitUserData(t)
            
            if isAffected[i] then
                set this = index[i]
                
                /* Stack */
                static if thistype.STACKABLE then
                    set .duration = .duration + d
                else
                    /* Reset */
                    static if thistype.RESETABLE then
                        if .duration < d then
                            set .duration = d
                        endif
                    endif
                endif
                
                return true
            else
                // Basically, the buff doesn't work only for magic immune and structures
                if not(IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
                    set this = create()
                
                    set .target = t
                    set .duration = d
                    set .sfx = AddSpecialEffectTarget(SLOW_SFX, t, SLOW_SFX_PT)
                        
                    set isAffected[i] = true
                    set index[i] = this
                    // If unit don't have the buff yet
                    if GetUnitAbilityLevel(t, BUFF_ID) == 0 then
                        call SetUnitX(dummy, GetUnitX(t))
                        call SetUnitY(dummy, GetUnitY(t))
                        call IssueImmediateOrderById(dummy, SLOW_ORDER)
                        call SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
                    endif
                
                    // Remove unwanted buff
                    call GroupEnumUnitsInRange(group, GetUnitX(.target), GetUnitY(.target), BUFF_SAFETY, null)
                    loop
                        set t = FirstOfGroup(group)
                        exitwhen t == null
                        call GroupRemoveUnit(group, t)
                        if GetUnitAbilityLevel(t, BUFF_ID) > 0 and not isAffected[GetUnitUserData(t)] then
                            call UnitRemoveAbility(t, BUFF_ID)
                        endif
                    endloop
                    return true
                debug else
                    debug call BJDebugMsg("Error occured: failed to attach buff")
                endif
            endif
            
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
            call UnitAddAbility(dummy, SPELL_ID)
        endmethod
    endstruct

    private struct Freeze extends array
    
        implement FreezeData
        
        unit target
        real duration
        effect sfx
        
        static unit dummy
        static group group = CreateGroup()
        static boolean array isAffected
        static thistype array index
        
        implement CTLExpire
            set .duration = .duration - .03125
            if .duration <= 0 or not UnitAlive(.target) then
                call DestroyEffect(.sfx)
                call UnitRemoveAbility(.target, BUFF_ID)
                set isAffected[GetUnitUserData(.target)] = false
                call destroy()
                set .sfx = null
                set .target = null
            endif
        
        implement CTLEnd
        
        static method apply takes unit t, real d returns boolean
        
            local thistype this
            local integer i = GetUnitUserData(t)
            
            if isAffected[i] then
                set this = index[i]
                /* Stack */
                static if thistype.STACKABLE then
                    set .duration = .duration + d
                else
                    /* Reset */
                    static if thistype.RESETABLE then
                        if .duration < d then
                            set .duration = d
                        endif
                    endif
                endif
                return true
            else
                /* Basically, the buff doesn't work for magic immune and structures only */
                if not(IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
                    set this = create()
                
                    set .target = t
                    set .duration = d
                    set .sfx = AddSpecialEffectTarget(FREEZE_SFX, t, FREEZE_SFX_PT)
                        
                    set isAffected[i] = true
                    set index[i] = this
                    /* If unit don't have the buff yet */
                    if GetUnitAbilityLevel(t, BUFF_ID) == 0 then
                        call SetUnitX(dummy, GetUnitX(t))
                        call SetUnitY(dummy, GetUnitY(t))
                        call IssueImmediateOrderById(dummy, FREEZE_ORDER)
                        call SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
                    endif
                
                    /* Remove unwanted buff */
                    call GroupEnumUnitsInRange(group, GetUnitX(.target), GetUnitY(.target), BUFF_SAFETY, null)
                    loop
                        set t = FirstOfGroup(group)
                        exitwhen t == null
                        call GroupRemoveUnit(group, t)
                        if GetUnitAbilityLevel(t, BUFF_ID) > 0 and not isAffected[GetUnitUserData(t)] then
                            call UnitRemoveAbility(t, BUFF_ID)
                        endif
                    endloop
                    return true
                debug else
                    debug call BJDebugMsg("Error occured: failed to attach buff")
                endif
            endif
            
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
            call UnitAddAbility(dummy, SPELL_ID)
        endmethod
    endstruct
    
    /* Cold Addict spell*/
    scope ColdAddict
    
        struct ColdAddict extends array
        
            implement ColdAddictData
            
            unit target
            real duration
            real mana
            effect sfx
            
            static real array amount
            static boolean array isAffected
            static thistype array index
            
            implement CTL
                local integer i
                local real m
            
            implement CTLExpire
                set .duration = .duration - .03125
                
                /* Stops mana regeneration */
                set m = GetUnitState(.target, UNIT_STATE_MANA)
                if m > .mana then
                    call SetUnitState(.target, UNIT_STATE_MANA, .mana)
                elseif m < .mana then
                    set .mana = m
                endif
                
                if .duration <= 0 or not UnitAlive(.target) then
                    set i = GetUnitUserData(.target)
                    call DestroyEffect(.sfx)
                    set amount[i] = 0
                    set isAffected[i] = false
                    call destroy()
                    set .target = null
                    set .sfx = null
                endif
            
            implement CTLEnd
            
            static method onCast takes nothing returns nothing
                
                local thistype this = create()
                local unit t = GetSpellTargetUnit()
                local integer i = GetUnitUserData(t)
                local integer l = GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ID)
                local real d
                
                
                if isAffected[i] then
                    set this = index[i]
                    static if thistype.STACKABLE_DUR then
                        if IsUnitType(t, UNIT_TYPE_HERO) then
                            set .duration = .duration + durationHero(l)
                        else
                            set .duration = .duration + durationNormal(l)
                        endif
                    else
                        static if thistype.RESETABLE then
                            if IsUnitType(t, UNIT_TYPE_HERO) then
                                set .duration = durationHero(l)
                            else
                                set .duration = durationNormal(l)
                            endif
                        endif
                    endif
                    
                    /* Stack damage amplification amount */
                    static if thistype.STACKABLE_BUFF then
                        set amount[i] = amount[i] + damage(l)
                    endif
                    
                    /*
                    *   Prevent slow buff from being removed before
                    *   cold addict has expired
                    */
                    if Slow.index[i].duration < .duration then
                        set Slow.index[i].duration = .duration
                    endif
                else
                    if IsUnitType(t, UNIT_TYPE_HERO) then
                        set d = durationHero(l)
                    else
                        set d = durationNormal(l)
                    endif
                
                    if Slow.apply(t, duration) then
                        set this = create()
                        
                        set .target = t
                        set .duration = d
                        set .mana = GetUnitState(.target, UNIT_STATE_MANA)
                        
                        set amount[i] = damage(l)
                        set isAffected[i] = true
                        set index[i] = this
                        set .sfx = AddSpecialEffectTarget(TARGET_SFX, .target, TARGET_SFX_PT)
                    endif
                endif
                set t = null
                
            endmethod
        
            static if not LIBRARY_SpellEffectEvent then
                private static method check takes nothing returns isAffectedean
                    if GetSpellAbilityId() == SPELL_ID then
                        call thistype.onCast()
                    endif
                    return false
                endmethod
            endif
            
            static method onInit takes nothing returns nothing

                local trigger t
                
                static if LIBRARY_SpellEffectEvent then
                    call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
                else
                    set t = CreateTrigger()
                    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                    call TriggerAddCondition(t, Condition(function thistype.check))
                endif
                
            endmethod
        endstruct
        
    endscope
    
    /* Frostwave spell*/
    scope Frostwave
        
        private struct data extends array
            implement FrostwaveData
            static constant real ANGLE_ADD = (bj_PI * 2)/data.SHARD_COUNT
        endstruct
            
        private keyword Frostwave
        
        private struct IceShard
            
            integer count
            
            real array angle[data.SHARD_COUNT]
            real array speed[data.SHARD_COUNT]
            real array x[data.SHARD_COUNT]
            real array y[data.SHARD_COUNT]
            
            boolean array bool[data.SHARD_COUNT]
            effect array sfx[data.SHARD_COUNT]
            unit array dummy[data.SHARD_COUNT]
            
            /* For FoG looping */
            static group tempGroup = CreateGroup()
            
            /* Function to compare two distances */
            static constant method inRadius takes real x, real y, real xt, real yt, real r returns boolean
                return (x - xt) * (x - xt) + (y - yt) * (y - yt) <= r * r
            endmethod
            
            static method move takes Frostwave i returns boolean
                
                local boolean b = false
                local integer j = 0
                local real a
                local real d
                local unit u
                
                loop
                    exitwhen j > data.SHARD_COUNT - 1
                    
                    if i.missile.bool[j] then
                        set i.missile.x[j] = i.missile.x[j] + i.missile.speed[j] * Cos(i.missile.angle[j])
                        set i.missile.y[j] = i.missile.y[j] + i.missile.speed[j] * Sin(i.missile.angle[j])
                        
                        /* If has reached the destination */
                        if isInBound(i.missile.x[j], i.missile.y[j]) and not inRadius(i.missile.x[j], i.missile.y[j], i.tX, i.tY, i.missile.speed[j]) then
                            set a = getAngle(i.missile.x[j], i.missile.y[j], i.tX, i.tY)
                            
                            call SetUnitX(i.missile.dummy[j], i.missile.x[j])
                            call SetUnitY(i.missile.dummy[j], i.missile.y[j])
                            call SetUnitFacing(i.missile.dummy[j], i.missile.angle[j] * bj_RADTODEG)
                               
                            call GroupEnumUnitsInRange(tempGroup, i.missile.x[j], i.missile.y[j], i.angle, null)
                            loop
                                set u = FirstOfGroup(tempGroup)
                                exitwhen u == null
                                if UnitAlive(u) and not IsUnitInGroup(u, i.targets) and data.filter(u, i.owner) then
                                    if IsUnitType(u, UNIT_TYPE_HERO) then
                                        set d = data.durationHero(i.level)
                                    else
                                        set d = data.durationNormal(i.level)
                                    endif
                                    if Slow.apply(u, d) then
                                        call UnitDamageTarget(i.count, u, i.damage + i.damage * ColdAddict.amount[GetUnitUserData(u)], false, false, data.ATTACK_TYPE, data.DAMAGE_TYPE, data.WEAPON_TYPE)
                                        call DestroyEffect(AddSpecialEffectTarget(data.TARGET_SFX, u, data.TARGET_SFX_PT))
                                    endif
                                    call GroupAddUnit(i.targets, u)
                                endif
                                call GroupRemoveUnit(tempGroup, u)
                            endloop
                            
                            /*
                            *   Turn the missile slowly to face the target
                            *   point based on TURN_RATE
                            */
                            if data.TURN_RATE != 0 and Cos(i.missile.angle[j] - a) < Cos(data.TURN_RATE) then
                                if Sin(a - i.missile.angle[j]) >= 0 then
                                    set i.missile.angle[j] = i.missile.angle[j] + data.TURN_RATE
                                else
                                    set i.missile.angle[j] = i.missile.angle[j] - data.TURN_RATE
                                endif
                            else
                                set i.missile.angle[j] = a
                                /* Accelerate if has faced the target point correctly */
                                set i.missile.speed[j] = i.missile.speed[j] + data.ACCELERATION
                                if i.missile.speed[j] > data.SPEED_MAX then
                                    set i.missile.speed[j] = data.SPEED_MAX
                                endif
                            endif
                        else
                            call SetUnitX(i.missile.dummy[j], i.tX)
                            call SetUnitY(i.missile.dummy[j], i.tY)
                            
                            call DestroyEffect(i.missile.sfx[j])
                            call UnitApplyTimedLife(i.missile.dummy[j], 'BTLF', SFX_DECAY_TIME)
                            
                            set i.missile.sfx[j] = null
                            set i.missile.bool[j] = false
                            set i.missile.count = i.missile.count - 1
                            
                            if i.missile.count < 0 then
                                call i.missile.destroy()
                                set b = true
                            endif
                        endif
                    endif
                    
                    set j = j + 1
                endloop
                
                return b
            endmethod
            
            static method create takes real x, real y, real f returns thistype
                
                local thistype this = allocate()
                local integer i = 0
                
                /* Determine the lowest angle */
                set f = f - data.ANGLE_ADD * (data.SHARD_COUNT/2)
                set .count = data.SHARD_COUNT - 1
                
                loop
                    exitwhen i > .count
                    
                    set .speed[i] = data.SPEED_MIN
                    set .angle[i] = f
                    set .bool[i] = true
                    
                    set .x[i] = x
                    set .y[i] = y
                    set .dummy[i] = CreateUnit(PASSIVE, DUMMY_ID, x, y, f * bj_RADTODEG)
                    set .sfx[i] = AddSpecialEffectTarget(data.MISSILE_PATH, .dummy[i], "origin")
                    
                    static if not LIBRARY_AutoFly then
                        if UnitAddAbility(.dummy[i], 'Amrf') and UnitRemoveAbility(.dummy[i], 'Amrf') then
                        endif
                    endif
                    call SetUnitFlyHeight(.dummy[i], data.LAUNCH_Z, 0)
                    
                    set f = f + data.ANGLE_ADD
                    set i = i + 1
                endloop
                
                return this
            endmethod
        endstruct
        
        private struct Frostwave extends array
            
            real tX
            real tY
            
            real angle
            real damage
            
            unit count
            group targets
            player owner
            
            integer level
            IceShard missile
                
            implement CTLExpire
                if IceShard.move(this) then
                    call DestroyGroup(.targets)
                    call destroy()
                    set .count = null
                    set .targets = null
                endif
            
            implement CTLEnd
            
            static method onCast takes nothing returns nothing
                
                local thistype this = create()
                local real a
                local real x
                local real y
                
                set .count = GetTriggerUnit()
                set .owner = GetTriggerPlayer()
                set .targets = CreateGroup()
                
                set x = GetUnitX(.count)
                set y = GetUnitY(.count)
                set .tX = GetSpellTargetX()
                set .tY = GetSpellTargetY()
                
                set .level = GetUnitAbilityLevel(.count, data.SPELL_ID)
                /* If target point is the same as cast point */
                if x == .tX and y == .tY then
                    set a = GetUnitFacing(.count) * bj_DEGTORAD
                else
                    set a = getAngle(x, y, .tX, .tY)
                endif
                
                set .missile = IceShard.create(x, y, a)
                set .tX = x + data.distance(.level) * Cos(a)
                set .tY = y + data.distance(.level) * Sin(a)
                
                set .damage = data.damage(.level)
                set .angle = data.aoe(.level)
                
            endmethod
        
            static if not LIBRARY_SpellEffectEvent then
                private static method check takes nothing returns boolean
                    if GetSpellAbilityId() == data.SPELL_ID then
                        call thistype.onCast()
                    endif
                    return false
                endmethod
            endif
            
            static method onInit takes nothing returns nothing

                local trigger t
                
                static if LIBRARY_SpellEffectEvent then
                    call RegisterSpellEffectEvent(data.SPELL_ID, function thistype.onCast)
                else
                    set t = CreateTrigger()
                    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                    call TriggerAddCondition(t, Condition(function thistype.check))
                endif
                
            endmethod
        endstruct
        
    endscope
    
    /* Frost Phase spell*/
    scope FrostPhase
    
        private struct FrostPhase extends array
        
            implement FrostPhaseData
            
            real aoe
            real angle
            real distance
            real damage
            
            real x
            real y
            
            unit source
            group targets
            player owner
            
            integer level
            
            /* For FoG looping */
            static group tempGroup = CreateGroup()
            
            implement CTL
                local unit u
                local unit f
                local real d
                local real c = 0
                
            implement CTLExpire
               /* 
                *   Check terrain pathability between cast and
                *   target point
                */
                set c = 0
                loop
                    exitwhen c >= .distance or not IsTerrainWalkable(.x, .y)
                    set .x = .x + ACCURACY * Cos(.angle)
                    set .y = .y + ACCURACY * Sin(.angle)
                    set c = c + ACCURACY
                endloop
                
                call SetUnitPosition(.source, .x, .y)
                set .x = GetUnitX(.source)
                set .y = GetUnitY(.source)
                
                call GroupEnumUnitsInRange(tempGroup, .x, .y, .aoe, null)
                loop
                    set u = FirstOfGroup(tempGroup)
                    exitwhen u == null
                    
                    if UnitAlive(u) and filter(u, .owner) then
                        if not IsUnitInGroup(u, .targets) then
                            if IsUnitType(u, UNIT_TYPE_HERO) then
                                set d = durationHero(.level)
                            else
                                set d = durationNormal(.level)
                            endif
                            
                            if Freeze.apply(u, d) then
                                call UnitDamageTarget(.source, u, .damage + .damage * ColdAddict.amount[GetUnitUserData(u)], false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                                /* If only deal damage for once per unit*/
                                static if DAMAGE_ONCE then
                                    call GroupAddUnit(.targets, u)
                                endif
                            endif
                        endif
                    endif
                    call GroupRemoveUnit(tempGroup, u)
                endloop
                
                /* Create scaled sfx at target point */
                set f = CreateUnit(.owner, DUMMY_ID, .x, .y, 0)
                call SetUnitScale(f, EXPLODE_SIZE, 1, 1)
                call DestroyEffect(AddSpecialEffectTarget(EXPLODE_SFX, f, "origin"))
                call UnitApplyTimedLife(f, 'BTLF', SFX_DECAY_TIME)
                call DestroyGroup(.targets)
                
                call destroy()
                set .source = null
                set .targets = null
                set f = null
            
            implement CTLEnd
            
            static method onCast takes nothing returns nothing
                
                local thistype this = create()
                local real xt
                local real yt
                local real d
                local unit u
                
                set .source = GetTriggerUnit()
                set .owner = GetTriggerPlayer()
                set .targets = CreateGroup()
                
                set .x = GetUnitX(.source)
                set .y = GetUnitY(.source)
                set xt = GetSpellTargetX()
                set yt = GetSpellTargetY()
                
                set .level = GetUnitAbilityLevel(.source, SPELL_ID)
                set .angle = getAngle(.x, .y, xt, yt)
                
                set .distance = getDistance(.x, .y, xt, yt)
                /* If surpass max distance */
                if .distance > range(.level) then
                    set .distance = range(.level)
                endif
                
                set .damage = amount(.level)
                set .aoe = AoE(.level)
                
                call GroupEnumUnitsInRange(tempGroup, .x, .y, .aoe, null)
                loop
                    set u = FirstOfGroup(tempGroup)
                    exitwhen u == null
                    
                    if UnitAlive(u) and filter(u, .owner) then
                        if not IsUnitInGroup(u, .targets) then
                            if IsUnitType(u, UNIT_TYPE_HERO) then
                                set d = durationHero(.level)
                            else
                                set d = durationNormal(.level)
                            endif
                            
                            if Freeze.apply(u, d) then
                                call UnitDamageTarget(.source, u, .damage + .damage * ColdAddict.amount[GetUnitUserData(u)], false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                                /* If only deal damage for once per unit*/
                                static if DAMAGE_ONCE then
                                    call GroupAddUnit(.targets, u)
                                endif
                            endif
                        endif
                    endif
                    
                    call GroupRemoveUnit(tempGroup, u)
                endloop
                
                /* Create scaled sfx at cast point */
                set u = CreateUnit(.owner, DUMMY_ID, .x, .y, 0)
                call SetUnitScale(u, EXPLODE_SIZE, 1, 1)
                call DestroyEffect(AddSpecialEffectTarget(EXPLODE_SFX, u, "origin"))
                call UnitApplyTimedLife(u, 'BTLF', SFX_DECAY_TIME)
                set u = null
                
            endmethod
            
            static if not LIBRARY_SpellEffectEvent then
                private static method check takes nothing returns boolean
                    if GetSpellAbilityId() == SPELL_ID then
                        call thistype.onCast()
                    endif
                    return false
                endmethod
            endif
            
            static method onInit takes nothing returns nothing

                local trigger t
                
                static if LIBRARY_SpellEffectEvent then
                    call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
                else
                    set t = CreateTrigger()
                    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                    call TriggerAddCondition(t, Condition(function thistype.check))
                endif
                
            endmethod
        endstruct
        
    endscope
    
    /* Frozen Fate spell*/
    scope FrozenFate
    
        private struct FrozenFate extends array
            
            implement FrozenFateData
            
            real x
            real y
            
            real cDistance
            real tDistance
            real vertical
            real horizontal
            
            real angle
            real side
            real duration
            
            unit target
            player owner
            effect sfx
            
            unit dummy
            
            static group Group = CreateGroup()
            static constant real HP = bj_PI/2
            
            implement CTL
                local real v
                local real h
                local real x
                local real y
                local real xt
                local real yt
            
            implement CTLExpire
                set .cDistance = .cDistance + MISSILE_SPEED
                
                /* Calculate vertical and horizontal offset */
                set h = (4 * .horizontal/.tDistance) * (.tDistance - .cDistance) * (.cDistance/.tDistance)
                set v = (4 * .horizontal/.tDistance) * (.tDistance - .cDistance) * (.cDistance/.tDistance) + MISSILE_Z
                
                set .x = .x + MISSILE_SPEED * Cos(.angle)
                set .y = .y + MISSILE_SPEED * Sin(.angle)
                set x = .x + h * Cos(.angle + HP * .side)
                set y = .y + h * Sin(.angle + HP * .side)
                
                if isInBound(x, y) then
                    set xt = GetUnitX(.target)
                    set yt = GetUnitY(.target)
                    
                    call SetUnitX(.dummy, x)
                    call SetUnitY(.dummy, y)
                    call SetUnitFlyHeight(.dummy, v, 0)
                    call SetUnitFacing(.dummy, getAngle(x, y, xt, yt) * bj_RADTODEG)
                
                    if .cDistance >= .tDistance then
                        /* If the targeted corpse is still available and reachable */
                        if IsUnitIndexed(.target) and getDistance(x, y, xt, yt) <= TOLERATION then
                            call DestroyEffect(AddSpecialEffect(IMPACT_SFX, xt, yt))
                            call UnitApplyTimedLife(CreateUnit(.owner, SUMMON_ID, xt, yt, GetUnitFacing(.target)), BUFF_ID, .duration)
                            call RemoveUnit(.target)
                        endif
                        call kill()
                        call destroy()
                    endif
                else
                    call kill()
                    call destroy()
                endif
            
            implement CTLEnd
            
            method kill takes nothing returns nothing
                call DestroyEffect(.sfx)
                call RemoveUnit(.dummy)
                set .sfx = null
                set .target = null
                set .dummy = null
            endmethod
            
            static method onCast takes nothing returns nothing
                
                local thistype this
                local unit f
                local unit u = GetTriggerUnit()
                local player p = GetTriggerPlayer()
                local integer l = GetUnitAbilityLevel(u, SPELL_ID)
                local integer c = count(l)
                local real d = lifespan(l)
                local real x = GetUnitX(u)
                local real y = GetUnitY(u)
                local real xt
                local real yt
                
                call GroupEnumUnitsInRange(Group, x, y, aoe(l), null)
                loop
                    set f = FirstOfGroup(Group)
                    exitwhen f == null
                    if c > 0 and IsUnitCorpse(f) and filter(f, p) then
                        set this = create()
                        
                        set .target = f
                        set .duration = d
                        set .owner = p
                        
                        set .x = x
                        set .y = y
                        set xt = GetUnitX(f)
                        set yt = GetUnitY(f)
                        
                        set .cDistance = 0
                        set .tDistance = getDistance(.x, .y, xt, yt)
                        set .angle = getAngle(.x, .y, xt, yt)
                        
                        set .vertical = .tDistance * GetRandomReal(V_ARC_MIN, V_ARC_MAX)
                        set .horizontal = .tDistance * GetRandomReal(H_ARC_MIN, H_ARC_MAX)
                        
                        set .dummy = CreateUnit(PASSIVE, DUMMY_ID, .x, .y, .angle * bj_RADTODEG)
                        set .sfx = AddSpecialEffectTarget(MISSILE_SFX, .dummy, "origin")
                        
                        static if not LIBRARY_AutoFly then
                            if UnitAddAbility(.dummy, 'Amrf') and UnitRemoveAbility(.dummy, 'Amrf') then
                            endif
                        endif
                        
                        /* Set the horizontal offset side */
                        if GetRandomInt(1, 100) <= 50 then
                            set .side = 1
                        else
                            set .side = -1
                        endif
                        
                        call SetUnitScale(.dummy, MISSILE_SIZE, 1, 1)
                        call SetUnitFlyHeight(.dummy, MISSILE_Z, 0)
                        
                        set c = c - 1
                    endif
                    call GroupRemoveUnit(Group, f)
                endloop
                set u = null
                
            endmethod
            
            static if not LIBRARY_SpellEffectEvent then
                private static method check takes nothing returns boolean
                    if GetSpellAbilityId() == SPELL_ID then
                        call thistype.onCast()
                    endif
                    return false
                endmethod
            endif
            
            static method onInit takes nothing returns nothing

                local trigger t
                
                static if LIBRARY_SpellEffectEvent then
                    call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
                else
                    set t = CreateTrigger()
                    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                    call TriggerAddCondition(t, Condition(function thistype.check))
                endif
                
            endmethod
        endstruct
        
    endscope
endscope

Frostwave (Standalone)
JASS:
scope Frostwave

   /*
    *              FROSTWAVE (STANDALONE)
    *                              v2.0
    *
    *   Sends a lance of ice  shards  wave  to  the
    *   target point, dealing damage to land  units
    *   in a  line and  slowing  them  for a  short
    *   duration.
    *
    *   External Dependencies:
    *     (Required)
    *       - CTL
    *       - WorldBounds
    *       - UnitIndexer
    *
    *     (Optional)
    *       - SpellEffectEvent
    *       - AutoFly
    *
    *   How to install:
    *       - Install and configure all external
    *         dependecies properly.
    *       - Copy object datas as following:
    *           - Frostwave main ability (A005)
    *           - Slow ability           (A001)
    *           - Slow buff              (B001)
    *           - Dummy unit             (h000)
	*	    - Copy this trigger into your map.
    *       - Configure the spell correctly.
    *
    *   Credits:
    *       - Nestharus
    *       - Bribe
    *       - Magtheridon96
    *
    *
    *                   CONFIGURATION
    */

    /* A. Global */
    globals
        /* Dummy unit's rawcode at Object Editor */
        private constant integer    DUMMY_ID        = 'h000'
        
        /* General decay time for all created sfx */
        private constant real       SFX_DECAY_TIME  = 2.0
    endglobals

    /* B. Slow Effect */
    private module SlowData
        /* Slow ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A001'
        
        /* Slow buff's rawcode at Object Editor */
        static constant integer     BUFF_ID         = 'B001'
        
        /* Slow order id */
        static constant integer     SLOW_ORDER      = 852096 /* thunderclap */
        
        /* Attached sfx on slow targets */
        static constant string      SLOW_SFX        = "Abilities\\Spells\\Other\\FrostDamage\\FrostDamage.mdl"
        static constant string      SLOW_SFX_PT     = "chest"
        
        /* Duration is stackable */
        static constant boolean     STACKABLE       = true
        
        /* Will reset the duration if not stacked */
        static constant boolean     RESETABLE       = false
        
        /* The higher, the safer. But just leave it */
        static constant real        BUFF_SAFETY     = 100.0
    endmodule

    /* C. Frostwave */
    
    private module FrostwaveData
        /* Main ability's rawcode at Object Editor */
        static constant integer     SPELL_ID        = 'A005'
        
        /* Total of created missiles on every cast */
        static constant integer     SHARD_COUNT     = 23
        
        /* Normal height z for every missile */
        static constant real        LAUNCH_Z        = 64.0
        
        /* Initial speed of every missile */
        static constant real        SPEED_MIN       = 20.0
        
        /* Maximum speed of every missile */
        static constant real        SPEED_MAX       = 40.0
        
        /* Acceleration rate of every missile */
        static constant real        ACCELERATION    = 0.25
        
        /* Turning rate of every missile */
        static constant real        TURN_RATE       = 12.5 * bj_DEGTORAD
        
        /* Missile model path */
        static constant string      MISSILE_PATH    = "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
        
        /* Attached sfx on targets */
        static constant string      TARGET_SFX      = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        static constant string      TARGET_SFX_PT   = "origin"
        
        /* Dealt damage configuration */
        static constant attacktype  ATTACK_TYPE     = ATTACK_TYPE_HERO
        static constant damagetype  DAMAGE_TYPE     = DAMAGE_TYPE_COLD
        static constant weapontype  WEAPON_TYPE     = WEAPON_TYPE_WHOKNOWS
        
        /* Max distance from missile to hits a target */
        static constant method aoe takes integer l returns real
            return 128.0
        endmethod
        
        /* Dealt damage amount */
        static constant method damage takes integer l returns real
            return 20.0 + 60.0 * l
        endmethod
        
        /* Travel distance of every missile */
        static constant method distance takes integer l returns real
            return 1000.0
        endmethod
        
        /* Slow duration for normal units */
        static constant method durationNormal takes integer l returns real
            return 4.0
        endmethod
        
        /* Slow duration for hero units */
        static constant method durationHero takes integer l returns real
            return 1.325
        endmethod
        
        /* Configure the targets, by default it's unable to hit structures and magic immune */
        static constant method filter takes unit u, player p returns boolean
            return not(IsUnitAlly(u, p) or IsUnitType(u, UNIT_TYPE_FLYING) or IsUnitType(u, UNIT_TYPE_MECHANICAL))
        endmethod
    endmodule
    
   /*
    *               END OF CONFIGURATION
    
    * ================================================== */
    
    native UnitAlive takes unit id returns boolean
    
    globals
        private player PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
    endglobals
    
    /* Function that returns angle between coordinates */
    private function getAngle takes real x, real y, real xt, real yt returns real
        return Atan2(yt - y, xt - x)
    endfunction
    
    /* Function that checks whether given coordinate is in map bound or not */
    private constant function isInBound takes real x, real y returns boolean
        return x > WorldBounds.minX and x < WorldBounds.maxX and y > WorldBounds.minY and y < WorldBounds.maxY
    endfunction

    private struct Slow extends array
    
        implement SlowData
        
        unit target
        real duration
        effect sfx
        
        static unit dummy
        static group group = CreateGroup()
        static boolean array isAffected
        static thistype array index
        
        implement CTLExpire
            set .duration = .duration - .03125
            if .duration <= 0 or not UnitAlive(.target) then
                call DestroyEffect(.sfx)
                call UnitRemoveAbility(.target, BUFF_ID)
                set isAffected[GetUnitUserData(.target)] = false
                call destroy()
                set .sfx = null
                set .target = null
            endif
        
        implement CTLEnd
        
        static method apply takes unit t, real d returns boolean
        
            local thistype this
            local integer i = GetUnitUserData(t)
            
            if isAffected[i] then
                set this = index[i]
                
                /* Stack */
                static if thistype.STACKABLE then
                    set .duration = .duration + d
                else
                    /* Reset */
                    static if thistype.RESETABLE then
                        if .duration < d then
                            set .duration = d
                        endif
                    endif
                endif
                
                return true
            else
                // Basically, the buff doesn't work only for magic immune and structures
                if not(IsUnitType(t, UNIT_TYPE_MAGIC_IMMUNE) or IsUnitType(t, UNIT_TYPE_STRUCTURE)) then
                    set this = create()
                
                    set .target = t
                    set .duration = d
                    set .sfx = AddSpecialEffectTarget(SLOW_SFX, t, SLOW_SFX_PT)
                        
                    set isAffected[i] = true
                    set index[i] = this
                    // If unit don't have the buff yet
                    if GetUnitAbilityLevel(t, BUFF_ID) == 0 then
                        call SetUnitX(dummy, GetUnitX(t))
                        call SetUnitY(dummy, GetUnitY(t))
                        call IssueImmediateOrderById(dummy, SLOW_ORDER)
                        call SetUnitPosition(dummy, WorldBounds.maxX, WorldBounds.maxY)
                    endif
                
                    // Remove unwanted buff
                    call GroupEnumUnitsInRange(group, GetUnitX(.target), GetUnitY(.target), BUFF_SAFETY, null)
                    loop
                        set t = FirstOfGroup(group)
                        exitwhen t == null
                        call GroupRemoveUnit(group, t)
                        if GetUnitAbilityLevel(t, BUFF_ID) > 0 and not isAffected[GetUnitUserData(t)] then
                            call UnitRemoveAbility(t, BUFF_ID)
                        endif
                    endloop
                    return true
                debug else
                    debug call BJDebugMsg("Error occured: failed to attach buff")
                endif
            endif
            
            return false
        endmethod
        
        static method onInit takes nothing returns nothing
            set dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
            call UnitAddAbility(dummy, SPELL_ID)
        endmethod
    endstruct

    
    private struct data extends array
        implement FrostwaveData
        static constant real ANGLE_ADD = (bj_PI * 2)/data.SHARD_COUNT
    endstruct
        
    private keyword Frostwave
    
    private struct IceShard
        
        integer count
        
        real array angle[data.SHARD_COUNT]
        real array speed[data.SHARD_COUNT]
        real array x[data.SHARD_COUNT]
        real array y[data.SHARD_COUNT]
        
        boolean array bool[data.SHARD_COUNT]
        effect array sfx[data.SHARD_COUNT]
        unit array dummy[data.SHARD_COUNT]
        
        /* For FoG looping */
        static group tempGroup = CreateGroup()
        
        /* Function to compare two distances */
        static constant method inRadius takes real x, real y, real xt, real yt, real r returns boolean
            return (x - xt) * (x - xt) + (y - yt) * (y - yt) <= r * r
        endmethod
        
        static method move takes Frostwave i returns boolean
            
            local boolean b = false
            local integer j = 0
            local real a
            local real d
            local unit u
            
            loop
                exitwhen j > data.SHARD_COUNT - 1
                
                if i.missile.bool[j] then
                    set i.missile.x[j] = i.missile.x[j] + i.missile.speed[j] * Cos(i.missile.angle[j])
                    set i.missile.y[j] = i.missile.y[j] + i.missile.speed[j] * Sin(i.missile.angle[j])
                    
                    /* If has reached the destination */
                    if isInBound(i.missile.x[j], i.missile.y[j]) and not inRadius(i.missile.x[j], i.missile.y[j], i.tX, i.tY, i.missile.speed[j]) then
                        set a = getAngle(i.missile.x[j], i.missile.y[j], i.tX, i.tY)
                        
                        call SetUnitX(i.missile.dummy[j], i.missile.x[j])
                        call SetUnitY(i.missile.dummy[j], i.missile.y[j])
                        call SetUnitFacing(i.missile.dummy[j], i.missile.angle[j] * bj_RADTODEG)
                           
                        call GroupEnumUnitsInRange(tempGroup, i.missile.x[j], i.missile.y[j], i.angle, null)
                        loop
                            set u = FirstOfGroup(tempGroup)
                            exitwhen u == null
                            if UnitAlive(u) and not IsUnitInGroup(u, i.targets) and data.filter(u, i.owner) then
                                if IsUnitType(u, UNIT_TYPE_HERO) then
                                    set d = data.durationHero(i.level)
                                else
                                    set d = data.durationNormal(i.level)
                                endif
                                if Slow.apply(u, d) then
                                    call UnitDamageTarget(i.count, u, i.damage + i.damage, false, false, data.ATTACK_TYPE, data.DAMAGE_TYPE, data.WEAPON_TYPE)
                                    call DestroyEffect(AddSpecialEffectTarget(data.TARGET_SFX, u, data.TARGET_SFX_PT))
                                endif
                                call GroupAddUnit(i.targets, u)
                            endif
                            call GroupRemoveUnit(tempGroup, u)
                        endloop
                        
                        /*
                        *   Turn the missile slowly to face the target
                        *   point based on TURN_RATE
                        */
                        if data.TURN_RATE != 0 and Cos(i.missile.angle[j] - a) < Cos(data.TURN_RATE) then
                            if Sin(a - i.missile.angle[j]) >= 0 then
                                set i.missile.angle[j] = i.missile.angle[j] + data.TURN_RATE
                            else
                                set i.missile.angle[j] = i.missile.angle[j] - data.TURN_RATE
                            endif
                        else
                            set i.missile.angle[j] = a
                            /* Accelerate if has faced the target point correctly */
                            set i.missile.speed[j] = i.missile.speed[j] + data.ACCELERATION
                            if i.missile.speed[j] > data.SPEED_MAX then
                                set i.missile.speed[j] = data.SPEED_MAX
                            endif
                        endif
                    else
                        call SetUnitX(i.missile.dummy[j], i.tX)
                        call SetUnitY(i.missile.dummy[j], i.tY)
                        
                        call DestroyEffect(i.missile.sfx[j])
                        call UnitApplyTimedLife(i.missile.dummy[j], 'BTLF', SFX_DECAY_TIME)
                        
                        set i.missile.sfx[j] = null
                        set i.missile.bool[j] = false
                        set i.missile.count = i.missile.count - 1
                        
                        if i.missile.count < 0 then
                            call i.missile.destroy()
                            set b = true
                        endif
                    endif
                endif
                
                set j = j + 1
            endloop
            
            return b
        endmethod
        
        static method create takes real x, real y, real f returns thistype
            
            local thistype this = allocate()
            local integer i = 0
            
            /* Determine the lowest angle */
            set f = f - data.ANGLE_ADD * (data.SHARD_COUNT/2)
            set .count = data.SHARD_COUNT - 1
            
            loop
                exitwhen i > .count
                
                set .speed[i] = data.SPEED_MIN
                set .angle[i] = f
                set .bool[i] = true
                
                set .x[i] = x
                set .y[i] = y
                set .dummy[i] = CreateUnit(PASSIVE, DUMMY_ID, x, y, f * bj_RADTODEG)
                set .sfx[i] = AddSpecialEffectTarget(data.MISSILE_PATH, .dummy[i], "origin")
                
                static if not LIBRARY_AutoFly then
                    if UnitAddAbility(.dummy[i], 'Amrf') and UnitRemoveAbility(.dummy[i], 'Amrf') then
                    endif
                endif
                call SetUnitFlyHeight(.dummy[i], data.LAUNCH_Z, 0)
                
                set f = f + data.ANGLE_ADD
                set i = i + 1
            endloop
            
            return this
        endmethod
    endstruct
    
    private struct Frostwave extends array
        
        real tX
        real tY
        
        real angle
        real damage
        
        unit count
        group targets
        player owner
        
        integer level
        IceShard missile
            
        implement CTLExpire
            if IceShard.move(this) then
                call DestroyGroup(.targets)
                call destroy()
                set .count = null
                set .targets = null
            endif
        
        implement CTLEnd
        
        static method onCast takes nothing returns nothing
            
            local thistype this = create()
            local real a
            local real x
            local real y
            
            set .count = GetTriggerUnit()
            set .owner = GetTriggerPlayer()
            set .targets = CreateGroup()
            
            set x = GetUnitX(.count)
            set y = GetUnitY(.count)
            set .tX = GetSpellTargetX()
            set .tY = GetSpellTargetY()
            
            set .level = GetUnitAbilityLevel(.count, data.SPELL_ID)
            /* If target point is the same as cast point */
            if x == .tX and y == .tY then
                set a = GetUnitFacing(.count) * bj_DEGTORAD
            else
                set a = getAngle(x, y, .tX, .tY)
            endif
            
            set .missile = IceShard.create(x, y, a)
            set .tX = x + data.distance(.level) * Cos(a)
            set .tY = y + data.distance(.level) * Sin(a)
            
            set .damage = data.damage(.level)
            set .angle = data.aoe(.level)
            
        endmethod
    
        static if not LIBRARY_SpellEffectEvent then
            private static method check takes nothing returns boolean
                if GetSpellAbilityId() == data.SPELL_ID then
                    call thistype.onCast()
                endif
                return false
            endmethod
        endif
        
        static method onInit takes nothing returns nothing

            local trigger t
            
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(data.SPELL_ID, function thistype.onCast)
            else
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Condition(function thistype.check))
            endif
            
        endmethod
    endstruct
        
endscope


Credits:
- Nestharus
- Bribe
- Cokemonkey11
- Anitarf
- Vexorian
- Magtheridon96

If you find any bug, please report it to me. ;)
 
Last edited:

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
Thanks guys!

i agree good spells kinda reminds me of lich but good spells

That can be an originality issue for my hero in the contest. And this Hero has already got some clear reasons to get less scores in the judging, just like her story, etc. That's why I'm working on another Hero. But if I can't finish it in time, then I will submit this no matter what. :)
 
  • static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
    can just be this
    static constant weapontype WEAPON_TYPE = null
  • Why you need argument integer l in many static constant methods, inwhich you don't use it?
  • Your filter methods can be constant. As well function getDistance, getAngle, isInBound.
  • .03125 can be a constant.
  • Use SetUnitX/Y instead of SetUnitPosition for moving the dummies back in corner.
  • In apply methods static method apply you can return true in end, and then return falsewhen buff failed to attach. (micro optimization^^)
  • You might consider to use one dummy for all spells.
  • JASS:
    static method inRadius takes real x, real y, real xt, real yt, real r returns boolean
        return (x - xt) * (x - xt) + (y - yt) * (y - yt) <= r * r
    endmethod
    ^Can be constant method.
  • JASS:
    if not LIBRARY_AutoFly then
        if UnitAddAbility(.u[i].unit, 'Amrf') and UnitRemoveAbility(.u[i].unit, 'Amrf') then
        endif
    endif
    ^Change it to static if.
  • JASS:
    loop
       exitwhen c >= .d or not IsTerrainWalkable(.x, .y)
        set .x = .x + ACCURACY * Cos(.r)
        set .y = .y + ACCURACY * Sin(.r)
        set c = c + ACCURACY
    endloop
    I don't like you directly exit when you found a not walkable location. This way you can't even skip some trees if they are in between caster and target point, even target point is walkable.
    Maybe if distance is bigger than the MaxRange, just set distance to MaxRange and then there you go without anymore checks. With SetUnitPosition you will anyway find the closest walkable point afterwards. (So this affects the in-game behaviour for the teleportation)
    If you keep the loop, then store Cos/Sin(r) into local variables.
  • It might be very subjective, but for constants I don't really like short names like this for example:
    static constant real HP = bj_PI/2

The effects are fitting the theme good. It would be neat if units affected by Cold Addict would have a changed VertexColoring, to visualize the slowered movement/attack speed.
It was nice if imported values were highligted in the spell tooltips, else they are fine.
Add links of the resources that you use as requirement.
Code is written very clear and readable.

Very cool spellpack, I'm not very sure about a rating, but after some changes I will tend to 5 I guess. Have a nice day!:csmile:

Edit: "You must spread first reputation..." No worry. Will come soon. :)
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
  • static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
    can just be this
    static constant weapontype WEAPON_TYPE = null

    It's blizzard's troll, just keep it ^)^
  • Why you need argument integer l in many static constant methods, inwhich you don't use it?

    In case user wants different value for each level ^)^
  • Your filter methods can be constant. As well function getDistance, getAngle, isInBound.

    Still not sure but I think they are no difference at all.
  • .03125 can be a constant.

    It's CTL expire time and it's an exact value. I think it's better that way.
  • Use SetUnitX/Y instead of SetUnitPosition for moving the dummies back in corner.

    But it has less line :( Just like what BPower said, it's important just for missile system. ^)^
  • In apply methods static method apply you can return true in end, and then return falsewhen buff failed to attach. (micro optimization^^)

    ...
  • You might consider to use one dummy for all spells.

  • JASS:
    static method inRadius takes real x, real y, real xt, real yt, real r returns boolean
        return (x - xt) * (x - xt) + (y - yt) * (y - yt) <= r * r
    endmethod
    ^Can be constant method.

    ...
  • JASS:
    if not LIBRARY_AutoFly then
        if UnitAddAbility(.u[i].unit, 'Amrf') and UnitRemoveAbility(.u[i].unit, 'Amrf') then
        endif
    endif
    ^Change it to static if.

    ...
  • JASS:
    loop
       exitwhen c >= .d or not IsTerrainWalkable(.x, .y)
        set .x = .x + ACCURACY * Cos(.r)
        set .y = .y + ACCURACY * Sin(.r)
        set c = c + ACCURACY
    endloop
    I don't like you directly exit when you found a not walkable location. This way you can't even skip some trees if they are in between caster and target point, even target point is walkable.
    Maybe if distance is bigger than the MaxRange, just set distance to MaxRange and then there you go without anymore checks. With SetUnitPosition you will anyway find the closest walkable point afterwards. (So this affects the in-game behaviour for the teleportation)
    If you keep the loop, then store Cos/Sin(r) into local variables.

    It works as intended. The hero is unable to walk through obstacles.

    If you keep the loop, then store Cos/Sin(r) into local variables.
    I only use once. If you mean global, it means more variables declared => No.
  • It might be very subjective, but for constants I don't really like short names like this for example:
    static constant real HP = bj_PI/2

    HP = HALF_PI

The effects are fitting the theme good. It would be neat if units affected by Cold Addict would have a changed VertexColoring, to visualize the slowered movement/attack speed.
It was nice if imported values were highligted in the spell tooltips, else they are fine.
Add links of the resources that you use as requirement.
Code is written very clear and readable.

Very cool spellpack, I'm not very sure about a rating, but after some changes I will tend to 5 I guess. Have a nice day!:csmile:

Edit: "You must spread first reputation..." No worry. Will come soon. :)

Thanks :) Anyway, I think changing unit's vertex coloring is not safe in some cases. And good tooltips must be simple and only contains important information. No one will read blocks of texts. ^)^

Thanks for your review IcamenBo :) So much appreciated that you spend your times to give this review ^)^

P.S. ... means no comment or will be fixed right away!

@NesthaHolic

Updated.
 
Last edited:
JASS:
/* The higher, the safer. But just leave it */
static constant real        SAFETY          = 100.0

^Enum range called should not be called "SAFETY".

static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS

can be ->

static constant weapontype WEAPON_TYPE = null

Improve names. One should not take a wild guess what what a variable stands for. Example:
JASS:
unit u
real d /* duration */
 effect s
        
static unit c
static group g = CreateGroup()
static boolean array b
static thistype array dex

Why do you stop FrostPhase once there is an unpathable point?
Description: "Instantly vannishes to TargetPoint". So if TargetPoint is accessable for caster, then it should also go there, or not?
Also in my opinion the ability cast range (object editor) should equal the range in code.

JASS:
if isInBound(i.m.x[j], i.m.y[j]) then
    /* If has reached the destination */
    if inRadius(i.m.x[j], i.m.y[j], i.tX, i.tY, i.m.s[j]) then
        implement KillShard
    else
        ... some stuff
else
    implement KillShard
endif
->

JASS:
if isInBound(i.m.x[j], i.m.y[j]) and not(inRadius(i.m.x[j], i.m.y[j], i.tX, i.tY, i.m.s[j])) then
    ... some stuff
else
    implement KillShard
endif

FrozenFate also gets executed when there is no valid corpse in range.
It could be easily changed if you base the spell on the "Revive Dead" ultimate from Undead Spells. Just set the amount of raised units to "0" with using Shirt + Enter trick, and you got a nice template ability.

It would be a nice addition if ColdAddict would make the target unit blue, as indicator for slowness.
Just like it's done for default warcraft III freeze effects.

Make attachment points configurable.

All in all it's a very nice spell pack. Nice visuals, too. To get it approved I would like to know...

1 Your opinion to this:
Why do you stop FrostPhase once there is an unpathable point?
Description: "Instantly vannishes to TargetPoint". So if TargetPoint is accessable for caster, then it should also go there, or not?
2. Improve names.

Needs Fix
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
1 Your opinion to this:
I will update the tooltip instead. That's the only way to make it quite different to common blink spell.

2. Improve names.
What if I say I wont? I can fix my coding style in the future, but I would like to leave this as it is for now. Anyway, guessing what those letters stand for is not really hard, unit c => caster, integer dex => index, effect s => sfx, group g => group, unit t => target. Those make sense for me.
 

Kazeon

Hosted Project: EC
Level 34
Joined
Oct 12, 2011
Messages
3,449
Update.
- Removed Dummy library
- Now Frostwave spell comes in standalone version
- Big update on Frostwave spell code structures
- Etc.

Have been working on this update long time ago. But paused the process for a month or something, I forgot most things I've changed at the time. I worked on a new spell named Glaciar Dragon but now I've completely forgot about how was the concept, so... it's cancelled. Anyway... enjoy...
 
Top