• 🏆 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!

Shattering Frost - v2.0.1

shatteringfrost.png

Introduction

Back in the day while learning vJASS, I coded this for one of my maps and decided to upload it.

Spell Description
Active: Encases a unit in ice, dealing damage and disabling it. After a short duration the ice explodes, slowing the

target by 50% and sending shards of ice to all nearby enemies, dealing damage and stunning them for 3 seconds.

Level 1: 75 target damage; 0.5 second freeze; 50 shard damage
Level 2: 125 target damage; 0.75 second freeze; 75 shard damage
Level 3: 175 target damage; 1 second freeze; 100 shard damage

Cooldown: 15 seconds.
Mana cost: 125.

Screenshots

Target a unit:
0kh3.jpg


For a short period the target is frozen in ice:
ux75.jpg


The ice explodes, sending shards to nearby enemies:
aun0.jpg


Units hit by ice shards are damaged and stunned:
l024.jpg

Requirements

- JASS NewGen Pack
- BeanDummyCaster by me (a simple library to help with dummy casting)
- SpellEffectEvent by Bribe
- TimerUtils by Vexorian
- xemissile by Vexorian

Links for the above systems can be found in the spell's code (below)

The Code


JASS:
library ShatteringFrost /*
****************************************************************************************************
* 
*   S H A T T E R I N G    F R O S T
*
*       ~ version 2.0.1
*       ~ coded by Mr_Bean
*
****************************************************************************************************
*
*    */ requires /*
*       ¯¯¯¯¯¯¯¯
*           */ BeanDummyCaster,     /* (not uploaded; available in demo map)
*           */ SpellEffectEvent,    /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193
*           */ TimerUtils           /* http://www.wc3c.net/showthread.php?t=101322
*           */ xemissile            /* http://www.wc3c.net/showthread.php?t=101150
*
****************************************************************************************************
*
*       Installation
*       ¯¯¯¯¯¯¯¯¯¯¯¯
*   - Make sure you've got (and installed properly) the above systems.
*   - Copy the 4 custom abilities. Scroll down to the "RAW IDS" section and set their raw IDs.
*   - Copy the 2 custom buffs (or make your own):
*       > Set the buff fields of "Slow [Shattering Frost]" to the "Shattering Frost" buff
*       > Set the buff fields of "Pause [Shattering Frost]" to the "Shattering Frost [paused]" buff.
*   - Go through the configurables section below and change what you want to.
*
****************************************************************************************************
*
*       Adding Ability Levels
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   If you would like to add more levels to the ability, you will need to add the same amount of 
*   levels to the 3 dummy abilities (Pause, Stun and Slow). Configure the stun and slow values as
*   desired. For the Stun and Pause dummy abilities, make sure the for each level you added:
*       - Damage is set to 0.
*       - Cast range is set to 99999.
*       - Cooldown is set to 0.
*       - Mana cost is set to 0.
*       - Targets allowed is BLANK (don't worry, the code makes sure only valid targets are chosen).
*
*   For the Slow dummy ability, make sure the for each level you added:
*       - Cast range is set to 99999.
*       - Cooldown is set to 0.
*       - Mana cost is set to 0.
*       - Targets allowed is BLANK (don't worry, the code makes sure only valid targets are chosen).
*
****************************************************************************************************
*
*       Configuration
*       ¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
    globals
    
        //==================================================
        //=== RAW IDS
        private constant integer SPELL_ID       = 'A002'    // "Shattering Frost" ability.
        private constant integer PAUSE_ID       = 'A003'    // "Pause [Shattering Frost]" ability.
        private constant integer STUN_ID        = 'A000'    // "Stun [Shattering Frost]" ability.
        private constant integer SLOW_ID        = 'A001'    // "Slow [Shattering Frost]" ability.
        
        //==================================================
        //=== PAUSE CONFIGURATION
        private constant real    PAUSE_DELAY    = 1.0       // Delay between target effect creation and pause starting.
        
        // Effect created on target while paused:
        private constant string  TARGET_EFFECT  = "Abilities\\Spells\\Undead\\FreezingBreath\\FreezingBreathTargetArt.mdl"
        
        private constant string  TARGET_ATTACH  = "origin"
        
        //==================================================
        //=== EXPLOSION CONFIGURATION
        private constant real    EXPLODE_AOE    = 400.0     // All enemies within this range of the target get a frost shard.
        
        // Effect created at target when ice explodes:
        private constant string  EXPLODE_EFFECT = "Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl"
        
        //==================================================
        //=== ICE SHARD CONFIGURATION
        private constant real    SHARD_SCALE    = 1.0       // Model scale.
        private constant real    SHARD_SPEED    = 800.0     // Speed.
        private constant real    SHARD_ARC      = 0.2       // Arc.
        private constant real    SHARD_HEIGHT   = 50.0      // Height.
        
        // Model:
        private constant string  SHARD_ART      = "Abilities\\Spells\\Other\\FrostBolt\\FrostBoltMissile.mdl"
        
    endglobals
    
    //==================================================
    //=== PRIMARY TARGET DAMAGE PER LEVEL
    private function GetTargetDamage takes integer level returns real
        return (50.0 * level) + 25.0
    endfunction
    
    //==================================================
    //=== PRIMARY TARGET PAUSE PER LEVEL:
    private function GetPauseDuration takes integer level returns real
        return (0.25 * level) + 0.25
    endfunction
    
    //==================================================
    //=== SHARD DAMAGE PER LEVEL:
    private function GetShardDamage takes integer level returns real
        return 50.0 * level
    endfunction
    
    //==================================================
    //=== DAMAGE FUNCTION SETUP
    // This is if you want to change attack/damage types or if you use a custom
    // function for damage detection or something similar.
    private function DealDamage takes unit source, unit target, real amount returns nothing
        call UnitDamageTarget(source, target, amount, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
    endfunction
    
/***************************************************************************************************
*
*       END OF CONFIGURATION AND DOCUMENTATION
*
***************************************************************************************************/
    
    private struct IceShard extends xemissile
        unit    source
        integer level
        //-----
        
        /**
         * Damages the target and stuns it if it's still alive.
         */
        private method onHit takes nothing returns nothing
            call DealDamage(source, targetUnit, GetShardDamage(level))
            
            if (not IsUnitType(targetUnit, UNIT_TYPE_DEAD)) then
                call BeanDummyCaster_SetAbility(STUN_ID, level, "thunderbolt")
                call BeanDummyCaster_TargetOrder(targetUnit)
            endif
            
            set source = null
        endmethod
        
        /**
         * Creates a new instance and launches the missile.
         */
        static method create takes unit s, real x, real y, unit t, integer l returns thistype
            local thistype this = allocate(x, y, SHARD_HEIGHT, GetUnitX(t), GetUnitY(t), SHARD_HEIGHT)
            
            set source = s
            set level = l
            set targetUnit = t
            set fxpath = SHARD_ART
            set scale = SHARD_SCALE
            call launch(SHARD_SPEED, SHARD_ARC)
            
            return this
        endmethod
    
    endstruct
    
    private struct ShatteringFrost
        static group enumG = CreateGroup()
        static unit  temp
        //-----
        unit    caster 
        unit    target
        player  owner
        integer level
        effect  fx
        //-----
        
        /**
         * Cleans up and dellocates the instance.
         */
        private method destroy takes nothing returns nothing
            set caster = null
            set target = null
            set owner = null
            set fx = null
            call deallocate()
        endmethod
        
        /**
         * This is the filter applied to units that are in range of the target.
         */
        private method filterUnit takes unit u returns boolean
            return IsUnitEnemy(u, owner)                         /* Enemy of caster.
                */ and not IsUnitType(u, UNIT_TYPE_DEAD)         /* Alive
                */ and     GetUnitTypeId(u) != 0                 /* Alive
                */ and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) /* Not magic immune.
                */ and not IsUnitType(u, UNIT_TYPE_STRUCTURE)    /* Not a structure. */
        endmethod
        
        /**
         * Unpauses the target and sends ice shards to all enemies in range.
         * Also slows the target and destroys the effect.
         */
        private static method explode takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real x = GetUnitX(target)
            local real y = GetUnitY(target)
            
            call ReleaseTimer(GetExpiredTimer())
            call DestroyEffect(fx)
            call DestroyEffect(AddSpecialEffect(EXPLODE_EFFECT, x, y))
            call BeanDummyCaster_SetAbility(SLOW_ID, level, "slow")
            call BeanDummyCaster_TargetOrder(target)
            call GroupEnumUnitsInRange(enumG, x, y, EXPLODE_AOE, null)
            call GroupRemoveUnit(enumG, target)
            
            loop
                set temp = FirstOfGroup(enumG)
                exitwhen temp == null
                call GroupRemoveUnit(enumG, temp)
                if (filterUnit(temp)) then
                    call IceShard.create(caster, x, y, temp, level)
                endif
            endloop
            
            call destroy()
        endmethod
        
        /**
         * Pauses the target and deals damage. Waits until the pause is
         * over.
         */
        private static method pause takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            
            call BeanDummyCaster_SetAbility(PAUSE_ID, level, "thunderbolt")
            call BeanDummyCaster_TargetOrder(target)
            call DealDamage(caster, target, GetTargetDamage(level))
            call TimerStart(GetExpiredTimer(), GetPauseDuration(level), false, function thistype.explode)
        endmethod
        
        /**
         * Creates a new instance. Creates the effect on the target and
         * waits until PAUSE_DELAY expires.
         */
        private static method create takes nothing returns thistype
            local thistype this = allocate()
            
            set caster = GetTriggerUnit()
            set owner = GetTriggerPlayer()
            set level = GetUnitAbilityLevel(caster, SPELL_ID)
            set target = GetSpellTargetUnit()
            set fx = AddSpecialEffectTarget(TARGET_EFFECT, target, TARGET_ATTACH)
            call TimerStart(NewTimerEx(this), PAUSE_DELAY, false, function thistype.pause)
            
            return this
        endmethod
    
        /**
         * Registers the spell and preloads effects and abilities.
         */
        private static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(SPELL_ID, function thistype.create)
            call Preload(TARGET_EFFECT)
            call Preload(EXPLODE_EFFECT)
            call Preload(SHARD_ART)
            set temp = CreateUnit(Player(0), 'hpea', 0.0, 0.0, 0.0)
            call UnitAddAbility(temp, PAUSE_ID)
            call UnitAddAbility(temp, STUN_ID)
            call UnitAddAbility(temp, SLOW_ID)
            call RemoveUnit(temp)
        endmethod
    
    endstruct

endlibrary

Changelog

v2.0.1
- Removed an unused local variable.
- Fixed the unit filter to check properly for dead units.
- The original target is now stunned instead of paused (because pausing a unit pauses its buffs).

v2.0
- Completely rewrote everything from scratch. It made me want to cry when I saw how badly I used to code.

v1.1.0
- Further improved and optimized the code.
- You can now change the delay between the casting of the spell and the stun. It is initially set to 0.5/0.4/0.3 seconds.
- You can now change the range of the stun per level. It is initially set to 200/250/300.

1.0.1
- Fixed a few issues with the code.
- Fixed a few things with the dummy caster.
- In the demo map, if you kill a ghoul, you will now get gold.
- You can now configure the delay between when the spell is cast and when the units are stunned.

1.0.0
- Initial release.

Credits

For Code Improvements:
- Maker
- OxygenD
- Ruke
- Bribe

Keywords:
Mr_Bean, Frost, Shatter, Ice, Shard
Contents

Shattering Frost v2.0.1 (Map)

Reviews
16 Dec 2011 Bribe: The changes look much better. Rating changed to 4/5 (Recommended).

Moderator

M

Moderator

16 Dec 2011
Bribe: The changes look much better. Rating changed to 4/5 (Recommended).
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update:
- Fixed a few issues with the code.
- Fixed a few things with the dummy caster.
- In the demo map, if you kill a ghoul, you will now get gold.
- You can now configure the delay between when the spell is cast and when the units are stunned.

The player did not get credit because I didn't turn the Gives Bounty flag on. I have changed that now :)
 
Last edited:
Level 17
Joined
Feb 11, 2011
Messages
1,860
Surely if there is a kill count they would count all the kills made by the player, not just by the hero? Like if the hero can summon an Infernal, for example. The Infernal can kill units, which would ideally add to the kill count.
 
Level 6
Joined
Dec 10, 2010
Messages
119
JASS:
    private function Timer_Actions takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data D = Data(GetTimerData(t))
        local real x = GetUnitX(D.target)
        local real y = GetUnitY(D.target)
        local unit u
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, RANGE, Condition(function Group_Conditions))
        loop
            set u = FirstOfGroup(bj_lastCreatedGroup)
            exitwhen u == null
            if (u != D.target) then
                set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(D.caster), DUMMY_ID, x, y, 0)
                call UnitAddAbility(bj_lastCreatedUnit, DUMMY_SPELL_ID)
                call SetUnitAbilityLevel(bj_lastCreatedUnit, DUMMY_SPELL_ID, D.level)
                call IssueTargetOrder(bj_lastCreatedUnit, "thunderbolt", u)
                call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 5)
            endif
            call GroupRemoveUnit(bj_lastCreatedGroup, u)
        endloop
        call ReleaseTimer(t)
        set t = null
        call D.destroy()
    endfunction

Dont use bj_lastCreatedUnit and bj_lastCreatedGroup its just globals variables. In your variant you can use just local group GroupName. Create this local group and change all bj_lastCreatedGroup to GroupName. Do the same with the unit.
 
Level 10
Joined
Sep 19, 2011
Messages
527
JASS:
library ShatteringFrost requires TimerUtils
    globals
        // Important Configurables:
        private constant integer SPELL_ID = 'A000'          // Change A000 to the raw code of the "Shattering Frost" spell.
        private constant integer DUMMY_SPELL_ID = 'A001'    // Change A001 to the raw code of "Shattering Frost [dummy]" spell.
        private constant integer DUMMY_ID = 'h001'          // Change h001 to the raw code of your dummy unit.
        
        // Other Configurables:
        private constant real RANGE = 250                   // Area of effect (how close units have to be to the target to get stunned).
        private constant real SHARD_DELAY = 0.5             // The delay between the ice blast and when the units get stunned. I wouldn't make it higher than 0.5 seconds.
    endglobals
    
    private function Group_Conditions takes unit target, unit caster returns boolean
        return (IsUnitEnemy(target, GetOwningPlayer(caster))) and (IsUnitType(target, UNIT_TYPE_DEAD) == false)
    endfunction
    
    // End Of Configurables.
    // Don't change anything past this point!
    
    private struct Data
        unit caster
        unit target
        integer level
        
        method destroy takes nothing returns nothing
            set this.caster = null
            set this.target = null
            call this.deallocate()
        endmethod
        
        static method Timer_Actions takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype D = GetTimerData(t)
            local real x = GetUnitX(D.target)
            local real y = GetUnitY(D.target)
            local unit u
            local unit dummy
            
            call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, RANGE, null)
            
            loop
                set u = FirstOfGroup(bj_lastCreatedGroup)
                exitwhen u == null
                
                if (u != D.target and Group_Conditions(u, D.caster)) then
                    set dummy = CreateUnit(GetOwningPlayer(D.caster), DUMMY_ID, x, y, 0)
                    
                    call UnitAddAbility(dummy, DUMMY_SPELL_ID)
                    call SetUnitAbilityLevel(dummy, DUMMY_SPELL_ID, D.level)
                    call IssueTargetOrder(dummy, "thunderbolt", u)
                    call UnitApplyTimedLife(dummy, 'BTLF', 5)
                    
                    set dummy = null
                endif
                
                call GroupRemoveUnit(bj_lastCreatedGroup, u)
            endloop
            
            call ReleaseTimer(t)
            set t = null
            call D.destroy()
        endmethod

        static method create takes nothing returns thistype
            local timer t = NewTimer()
            local thistype D = thistype.allocate() 
            
            set D.caster = GetTriggerUnit()
            set D.target = GetSpellTargetUnit()
            set D.level = GetUnitAbilityLevel(D.caster, SPELL_ID)
            
            call SetTimerData(t, D)
            call TimerStart(t, SHARD_DELAY, true, function thistype.Timer_Actions)
            set t = null
            
            return D
        endmethod
        
        static method Conditions takes nothing returns boolean
            if (GetSpellAbilityId() == SPELL_ID) then
                call thistype.create()
            endif
            
            return false
        endmethod
        
        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.Conditions))
        endmethod
    endstruct    
endlibrary

  • Use library instead scope (then, make it requires TimerUtils or the libraries that you will use later :)).
  • The "Actions" function is unnesesary, you can make all of these actions into the create method.
  • Use the "destroy" method to null your variables (only who really needs null) and then, use deallocate (if you're using allocate).
  • Use "thistype" keyword instead the name of the struct (is the same, but better e_e).
  • Don't use "bj_lastCreatedUnit" like this.
  • When you enumerate units into a group, don't put a filter condition, make the filter in the loop :).

call TimerStart(t, SHARD_DELAY, true, function Timer_Actions)

Why true? the actions only executes one time.

local Data D = Data(GetTimerData(t))

->

local Data D = GetTimerData(t)

Null t when you create the trigger

You will never (in this case) destroy the trigger, so, what's the point?

---

Nice spell :).

Greetings :D
 
Last edited:
Dont use bj_lastCreatedUnit and bj_lastCreatedGroup its just globals variables. In your variant you can use just local group GroupName. Create this local group and change all bj_lastCreatedGroup to GroupName. Do the same with the unit.

It's actually the opposite. I encourage using the global group instead of creating and destroying a group in the same thread. It's cleaner and more efficient ;)

There's no need to use bj_lastCreatedUnit though.
Creating a local variable for that is faster (Locals are theoretically faster than globals and they don't get slower with more characters as much as globals do).

If u is a global and v is a local, u is faster and v is slower.
If uu is a global and vv is a local, vv is faster and uu is slower.

You can imagine the following formulas:
This is only an example.

globalLookup = 3x + 5
localLookup = x + 8

x is the number of characters.

With 1 character, globalLookup = 8 and localLookup = 9 (globalLookup is faster)
With any other number of characters, the localLookup would be faster.

This is purely an example that shows a special case when globals are faster than locals.

This still doesn't give you a reason to use dynamic group handles in any function because function calls are DRASTICALLY slow relative to any variable lookup ;)

But, I wouldn't recommend using one-character long names for global variables, so locals are technically always going to be faster than globals.

----------
TL;DR?

Use a local unit; Keep the bj_lastCreatedGroup.
 
Last edited:
Level 17
Joined
Feb 11, 2011
Messages
1,860
Thanks guys!

Update:
- Replaced bj_lastCreatedUnit with a local variable.
- Used Ruke's suggestions.
- You can now change the delay between the casting of the spell and the stun. It is initially set to 0.5/0.4/0.3 seconds (suggested by Bribe).
- You can now change the range of the stun per level. It is initially set to 200/250/300 (suggested by Bribe).
 
Last edited:
Level 29
Joined
Mar 10, 2009
Messages
5,016
Do you have a problem with the destroy method? xD

As did you say in your spell, if it's not a requirement (I have questions about this) but it's recomended, why shouldn't we do it?

I posted in the other thread...
Mckill2009 said:
it may leak but very minimal, I've created a test with nulling a unit and not...

here's the result: creates unit every 0.03/sec, units created 1500...

Ram 101,432 = nulls the unit with your method

Ram 101,498 = w/o nulling them but with .destroy()

maybe 1500 isnt that much but I believe even if 10000, results is not a big deal...

EDIT: SOrry for double posting...

EDIT2: New test 3000 units...

Ram 134396 = no nulling
Ram 134664 = nulls with your method
 
Level 37
Joined
Mar 6, 2006
Messages
9,240
-Method onHit has unused local unit dummy variable
-I think dead units can have over 0.405 life. Use not(IsUnitType(unit, UNIT_TYPE_DEAD) or GetUnitTypeId(unit) == 0)
-Increase follow through time so the caster completes the cast animation
-Pausing a unit should be avoided, it pauses buffs
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update - v2.0.1:
- Removed an unused local variable.
- Fixed the unit filter to check properly for dead units.
- The original target is now stunned instead of paused (because pausing a unit pauses its buffs).

Thanks to Maker for the above suggestions.

EDIT: Added screenshots to illustrate what happens when you cast the spell.
 
Level 2
Joined
Feb 26, 2014
Messages
6
I get an error while trying to play the map with this spell in it.

It says : Fatal error! Exception 0xC00000000005 access violation at 00:23000000004 the instruction 0x00000000004 referenced memory at 0x0000000004 could not be writen, click ok to terminate the application, wtf ?
 
Level 16
Joined
May 2, 2011
Messages
1,345
the Spell is quite cool.

May I suggestion one thing though? perhaps you could configure maximum number of ice shards that will stun enemies around target?

as you know, many of AOE spells have maximum damage. if so many units are surrounding the target, they will all be hit by shards and get stunned/damaged, with no limit to the maximum number of targets. I would say 4/5 targets max is kinda balanced.

also, if more than this limit is around the target unit, then Random 5 must be chosen.

dont you agree? :)
 
Level 38
Joined
Feb 27, 2007
Messages
4,951
All you have to do is modify private static method explode to create a shard for only N units instead of all the units.
JASS:
private static ...
  local integer i = 0
  //...
  loop
    exitwhen temp == null or i >= 5 //add the second half of this line
    //...
    if ...
      set i = i+1
      //...
    endif
  endloop
//...
endfunction
FirstOfGroup is functionally random afaik but if you really care you can replace it with GroupPickRandomUnit instead.
 
Top