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

[JASS] Freezing Field v1.1d

A non-channeling ability based on Freezing Field in DotA. All rating and comments are welcome :D

FREEZING FIELD

Calls down an icy hailstorm of destruction. Each ice shard explodes upon impact and deals damage to enemies in the surrounding area. Summons 20 explosions per second for 5 seconds. Explosion AoE and damage improve with level.

Level 1 - 120 damage per explosion.
Level 2 - 140 damage per explosion.
Level 3 - 160 damage per explosion.
Level 4 - 180 damage per explosion.

Credit:
+ The IceFrog team for the spell idea (and possibly the Freezing Field model)
+ Evading Self for the SnowierBlizzardTarget model.


+ udg_FF_Hashtable: Spell Hashtable
+ udg_FF_Group: Global group for faster iteration rate.


JASS:
// CONFIGURATION

// Constants

// Main spell raw code
constant function FF_GetSpellID takes nothing returns integer
    return 'A004'
endfunction

// Field Effect
constant function FF_FieldFx takes nothing returns string
    return "war3mapimported\\FreezingField.mdx" 
endfunction

// Explosion Effect
constant function FF_ExpFx takes nothing returns string
    return "war3mapimported\\SnowyBlizzardTarget.mdx"
endfunction

// Explosion Effect delay
constant function FF_ExpDelay takes nothing returns real
    return 0.8
endfunction

// Explosion Interval
constant function FF_Interval takes nothing returns real
    return 0.05
endfunction

// Booleans

function FF_FilterUnit takes player casterowner, unit u returns boolean
    return IsUnitEnemy(u, casterowner) and not IsUnitType (u, UNIT_TYPE_DEAD) and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

// Get spell level

function FF_SpellLvl takes unit caster returns integer
    return GetUnitAbilityLevel (caster, FF_GetSpellID()) // Get the spell level of the caster
endfunction

// Level dependable values

function FF_Duration takes integer level returns real
    return 5.
endfunction

function FF_ExpDmg takes integer level returns real
    return 100. + level*20.
endfunction

function FF_ExpAoE takes integer level returns real
    return 100. + level*20.
endfunction

function FF_FieldAoe takes integer level returns real
    return 400. + level*50.
endfunction

// Attack - Damage - Weapon type

function FF_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_NORMAL
endfunction

function FF_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_MAGIC
endfunction

function FF_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

// END CONFIGURATION

// SPELL FUNCTIONS

// This is the main function of the spell

function FF_TimerLoop takes nothing returns nothing
    local integer ID
    local integer ID2
    local integer listmax = LoadInteger(udg_FF_Hashtable, -1, 0 )
    local unit caster
    local integer i = 0
    local unit u
    local real x
    local real y
    local real x1
    local real y1
    local integer level
    local real t
    local integer j
    local integer modifier
    local real r

    // Now we start looping through all the spell instances
    loop
        exitwhen i >= listmax

        // First we load the saved values of a spell instance
        set i = i + 1
        set ID = LoadInteger(udg_FF_Hashtable, -1, i)
        set caster = LoadUnitHandle(udg_FF_Hashtable, ID, 1)
        set x = LoadReal(udg_FF_Hashtable, ID, 2)
        set y = LoadReal(udg_FF_Hashtable, ID, 3)
        set level = FF_SpellLvl(caster)
        set t = LoadReal(udg_FF_Hashtable, ID, 5)
        set j = LoadInteger(udg_FF_Hashtable, ID, 6)

        if t >= FF_Duration(level) then
            call DestroyEffect(LoadEffectHandle(udg_FF_Hashtable, ID, 4))

            if listmax != i then
                call SaveInteger(udg_FF_Hashtable, LoadInteger(udg_FF_Hashtable, -1, listmax), 0, i)
                call SaveInteger(udg_FF_Hashtable, -1, i, LoadInteger(udg_FF_Hashtable, -1, listmax))
                call RemoveSavedInteger(udg_FF_Hashtable, -1, listmax)
                set i = i - 1
            endif

            set listmax = listmax - 1
            call SaveInteger(udg_FF_Hashtable, -1, 0, listmax)

            if LoadInteger(udg_FF_Hashtable, 0, 0) + 1 == LoadInteger(udg_FF_Hashtable, 0, -1) then
                call FlushChildHashtable(udg_FF_Hashtable, 0)
                call PauseTimer(LoadTimerHandle(udg_FF_Hashtable, -2, 0))
            else
                call SaveInteger (udg_FF_Hashtable, 0, 0, LoadInteger(udg_FF_Hashtable, 0, 0) + 1)
                call SaveInteger(udg_FF_Hashtable, 0, LoadInteger(udg_FF_Hashtable, 0, 0), ID)
            endif

            call FlushChildHashtable(udg_FF_Hashtable, ID)

        else
 
            if LoadBoolean(udg_FF_Hashtable, ID, 7) then
                set r = LoadReal(udg_FF_Hashtable, ID, 10)

                if r >= FF_ExpDelay() then
                    call GroupEnumUnitsInRange(udg_FF_Group, LoadReal(udg_FF_Hashtable, ID, 8), LoadReal(udg_FF_Hashtable, ID, 9), FF_ExpAoE(level), null)

                    loop
                        set u = FirstOfGroup(udg_FF_Group)
                        exitwhen u == null

                        if FF_FilterUnit (GetOwningPlayer(caster), u) and IsUnitVisible(u, GetOwningPlayer(caster)) then
                            call UnitDamageTarget(caster, u, FF_ExpDmg(level), false, false, FF_AttackType(), FF_DamageType(), FF_WeaponType())
                        endif

                        call GroupRemoveUnit(udg_FF_Group, u)
                    endloop

                    if listmax != i then
                        call SaveInteger (udg_FF_Hashtable, LoadInteger (udg_FF_Hashtable, -1, listmax), 0, i)
                        call SaveInteger (udg_FF_Hashtable, -1, i, LoadInteger (udg_FF_Hashtable,-1, listmax))
                        call RemoveSavedInteger (udg_FF_Hashtable, -1, listmax)
                        set i = i - 1
                    endif

                    set listmax = listmax - 1
                    call SaveInteger (udg_FF_Hashtable, -1, 0, listmax )

                    if LoadInteger (udg_FF_Hashtable, 0, 0) + 1 == LoadInteger (udg_FF_Hashtable, 0, -1) then
                        call FlushChildHashtable (udg_FF_Hashtable, 0) 
                        call PauseTimer (LoadTimerHandle (udg_FF_Hashtable, -2, 0))
                    else
                        call SaveInteger (udg_FF_Hashtable, 0, 0, LoadInteger(udg_FF_Hashtable, 0, 0) + 1)
                        call SaveInteger (udg_FF_Hashtable, 0, LoadInteger(udg_FF_Hashtable, 0, 0), ID)
                    endif

                    call FlushChildHashtable (udg_FF_Hashtable, ID)
                else
                    call SaveReal(udg_FF_Hashtable, ID, 10, r + FF_Interval())
                endif

            else

                set modifier = j - (j/4)*4
                set x1 = x + GetRandomReal (135, FF_FieldAoe(FF_SpellLvl(caster)))*Cos(GetRandomReal(-90, 0) + modifier*90.)
                set y1 = y + GetRandomReal (135, FF_FieldAoe(FF_SpellLvl(caster)))*Sin(GetRandomReal(-90, 0) + modifier*90.)
                call DestroyEffect(AddSpecialEffect (FF_ExpFx(), x1, y1))
                set ID2 = LoadInteger (udg_FF_Hashtable, 0, 0)

                if ID2 > 0 then
                    call SaveInteger (udg_FF_Hashtable, 0, 0, ID2 - 1)
                    set ID2 = LoadInteger (udg_FF_Hashtable, 0, ID2)
                else
                    set ID2 = LoadInteger (udg_FF_Hashtable, 0, -1) + 1
                    call SaveInteger (udg_FF_Hashtable, 0,-1, ID2)
                endif

                call SaveUnitHandle (udg_FF_Hashtable, ID2, 1, caster)
                call SaveReal (udg_FF_Hashtable, ID2, 8, x1)
                call SaveReal (udg_FF_Hashtable, ID2, 9, y1)
                call SaveReal(udg_FF_Hashtable, ID2, 10, 0)
                call SaveBoolean (udg_FF_Hashtable, ID2, 7, true)
                set listmax = listmax + 1
                call SaveInteger (udg_FF_Hashtable, -1, 0, listmax )
                call SaveInteger (udg_FF_Hashtable, -1, listmax, ID2)
                call SaveInteger (udg_FF_Hashtable, ID2, 0, listmax)

                call SaveReal(udg_FF_Hashtable, ID, 5, t + FF_Interval())
                call SaveInteger(udg_FF_Hashtable, ID, 6, j + 1)
            endif
        endif

        set caster = null
    endloop
endfunction

function Trig_FF_Conditions takes nothing returns boolean
    local unit caster
    local integer ID
    local integer listmax
    local real x
    local real y

    if GetSpellAbilityId() == FF_GetSpellID() then
        set caster = GetTriggerUnit()
        set x = GetSpellTargetX()
        set y = GetSpellTargetY()
        set ID = LoadInteger(udg_FF_Hashtable, 0, 0)

        // Now we start indexing the spell
        if ID>0 then
            call SaveInteger(udg_FF_Hashtable, 0, 0, ID - 1)
            set ID = LoadInteger (udg_FF_Hashtable, 0, ID)
        else
            set ID = LoadInteger(udg_FF_Hashtable, 0, -1) + 1
            call SaveInteger(udg_FF_Hashtable,0 ,-1, ID)
            if ID == 1 then
                call TimerStart (LoadTimerHandle(udg_FF_Hashtable, -2, 0), FF_Interval(), true, function FF_TimerLoop)
            endif
        endif

        // Save the values related to this spell instance
        call SaveUnitHandle(udg_FF_Hashtable, ID, 1, caster)
        call SaveReal(udg_FF_Hashtable, ID, 2, x)
        call SaveReal(udg_FF_Hashtable, ID, 3, y)
        call SaveEffectHandle(udg_FF_Hashtable, ID, 4, AddSpecialEffect (FF_FieldFx(), x, y))
        call SaveReal(udg_FF_Hashtable, ID, 5, 0)
        call SaveInteger(udg_FF_Hashtable, ID, 6, 0)

        set listmax = LoadInteger (udg_FF_Hashtable, -1, 0) + 1
        call SaveInteger (udg_FF_Hashtable, -1, 0, listmax )
        call SaveInteger (udg_FF_Hashtable, -1, listmax, ID)
        call SaveInteger (udg_FF_Hashtable, ID, 0, listmax)

        set caster = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Freezing_Field takes nothing returns nothing
    local trigger FF = CreateTrigger()
    set udg_FF_Hashtable = InitHashtable ()
    call SaveTimerHandle (udg_FF_Hashtable, -2, 0, CreateTimer())
    call TriggerRegisterAnyUnitEventBJ(FF, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(FF, Condition( function Trig_FF_Conditions))
    set FF = null
endfunction


+ v1.0: Initial release
+ v1.0a: Updated the code.
+ v1.1: Revamped the code completely.
+ v1.1a: Tiny bits of optimization.
+ v1.1b: Added importing instruction.
+ v1.1c: Fixed an issue with the timer and indexing method.
+ v1.1d: Addressed two tiny issues mentioned in Maker's review.


Keywords:
freezing field, ice, frost, dota, spell, jass
Contents

Freezing Field v1.1d (Map)

Reviews
Freezing Field v1.1c - Reviewed by Maker - 15th Feb 2013 Approved The spell works and causes no fps drop even with multiple stacked casts. This is a bit like Blizzard except it is not a channeling spell. GetWidgetLife(u) > 0.405 -> not...

Moderator

M

Moderator


Freezing Field v1.1c - Reviewed by Maker - 15th Feb 2013
Approved

The spell works and causes no fps drop even with multiple stacked casts.
This is a bit like Blizzard except it is not a channeling spell.

GetWidgetLife(u) > 0.405 -> not IsUnitType(u, UNIT_TYPE_DEAD)
DAMAGE_TYPE_FIRE - > DAMAGE_TYPE_MAGIC

Fire and magic work just the same way,
but magic is the base type, I recommend using that.

[td]
[/td]


Freezing Field v1.1a, 13th Feb 2012

Reviewed by Maker

Required changes
  • The timer is never paused and the hastable is not flushed
  • Add importing instructions
16:57, 15th Jan 2013
Magtheridon96:

Here's a brief review:

  • JASS:
    if modifier == 1 then
        set angle = GetRandomReal (0, 90)
    elseif modifier == 2 then
        set angle = GetRandomReal (90, 180)
    elseif modifier == 3 then
        set angle = GetRandomReal (180, 270)
    else
        set angle = GetRandomReal (270, 360)
    endif

    ->

    set angle = GetRandomReal(-90, 0) + modifier * 90.
  • You're forgetting to null freezefx in the periodic function.
  • Instead of triggers, I'd recommend using timers. Doing things by evluation count and timed triggers is a really bad method in terms of speed because if you have trigger evaluations piling up, you will face freezes at times, limiting the features of any given map :/
    DotA does this and this is why knocking back 10 - 15 units at the same time causes the game to freeze terribly. This is also the reason why spell casting can cause freezes on slower computers. It causes hundreds of triggers to evaluate and some of them to execute.
  • What you should do is start a timer and save its handle id in the hashtable, then use that handle id to store other data. When a timer expires, get its handle id, and use that to retrieve spell data from the hashtable. Execution counts would also be retrieved from the hashtable.
  • Your indentation is messed up in the periodic function directly after the first else line. The "if modifier == 1 then" line is not in the same tab level as its corresponding "endif" line. It's in the same tab level as the "endif" of the first if statement.
  • The END_CAST event would be handled by a different function inside your code when you're going for the timer approach.
  • You can avoid setting the caster, x, and y locals in your Cast function outside the if block. They aren't needed at all if you're not going to branch into the if block's code.
  • Store the handle Id of the the timers you're creating inside the periodic function into a local integer.
  • I'd like to warn you that your periodic function runs the following code:
    JASS:
    set x1 = x + offset*Cos(angle)
    set y1 = y + offset*Sin(angle)
    set t = CreateTimer()
    call SaveUnitHandle( udg_FF_Hashtable, GetHandleId(t), 0, caster )
    call SaveReal (udg_FF_Hashtable, GetHandleId(t), 1, x1 )
    call SaveReal (udg_FF_Hashtable, GetHandleId(t), 2, y1 )
    call TimerStart (t, FF_ExpDelay(), false, function FF_DamageUnit)
    set caster = null
    call DestroyEffect (AddSpecialEffect (FF_ExpFx(), x1, y1))
    set t = null
    even during the END_CAST event :V
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
OMG!!! Another storm-related spell from YOU!!!!

oh,forgot the review.

Review #1
1)when damaging target,you must turn to 2 booleans to false
2) if(GetSpellAbilityId()=='A004')then
ahaha missed this one.You cached it but didnt use it.
3)you forgot to null unit u in the function FF_Periodic

that's all i can see :D
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
OMG!!! Another storm-related spell from YOU!!!!

oh,forgot the review.

Review #1
1)when damaging target,you must turn to 2 booleans to false
2) if(GetSpellAbilityId()=='A004')then
ahaha missed this one.You cached it but didnt use it.
3)you forgot to null unit u in the function FF_Periodic

that's all i can see :D

Well okay I will make sure to change the theme next time :D

Now about the review

1&2: Code updated.
3: According to Mag, when you use exitwhen u == null it means the variable is already nulled when the loop finished, thus another nulling is unnecessary.
 
Level 15
Joined
Jul 6, 2009
Messages
889
The snow shard model is by Evading Self (Source: http://www.wc3c.net/showthread.php?t=87344). I'm not sure about the actual field effect though.

There is no need to assign the triggering unit and targeted coordinates on local variable declaration. That part of code will be fired for every spell that is cast in the map as it runs on the spell effect even. Therefore you should do the assignments within the if block checking whether the spell id was that of Freezing Field's. Alternatively you could just have a separate function with everything else done in it.

Well, there are many more things with your code that you could improve, but I'll let someone else point them out. Mainly it is how you do things.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
The snow shard model is by Evading Self (Source: http://www.wc3c.net/showthread.php?t=87344). I'm not sure about the actual field effect though.

There is no need to assign the triggering unit and targeted coordinates on local variable declaration. That part of code will be fired for every spell that is cast in the map as it runs on the spell effect even. Therefore you should do the assignments within the if block checking whether the spell id was that of Freezing Field's. Alternatively you could just have a separate function with everything else done in it.

Well, there are many more things with your code that you could improve, but I'll let someone else point them out. Mainly it is how you do things.

Thanks very much for taking your time reviewing this spell of mine. And yes there are still many things that can be improved. It would be great if you could point them out for me also but it is entirely your choice.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
nice spell is it possible to get a non-dotaish? version that scales with stats for more damage? also the name kinda gave the impression that it would freeze on impact not explode.

Yeh. You just need to modify it a little bit (if you know JASS that is). Well about the name it is a homage to the original ability this is based on, and let's not forget the fact that the spell in DotA only slows, not freeze :p
 
Top