• 🏆 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] Frost Shard v1.1d

Another spell with multiple customizable stats. Well actually it is a spell made for The Chosen Ones campaign but I decided to upload it here anyway. Enjoy :D

I also made another version that will trigger the spread if the target unit has a specific buff so if any of you want that version just contact me.

Now uses Dynamic Indexing for dramatically improved performance.

FROST SHARD

Hurl an enchanted frost shard towards the target that damages it. If the target succumbs to the damage or its current HP falls below a certain rate smaller shards of ice will be released from its body to damage nearby enemies for a portion of the original target's. Area of Effect and projectile speed improves with level.

Level 1 - 150 main target damage. 75 secondary target damage. 50% HP trigger.
Level 2 - 200 main target damage. 120 secondary target damage. 60% HP trigger.
Level 3 - 250 main target damage. 175 secondary target damage. 70% HP trigger.
Level 4 - 300 main target damage. 240 secondary target damage. 80% HP trigger.

Credit:
+ Geries, whose help proves invaluable to this spell. From teaching me code optimization, dynamic indexing and also patiently guiding me, thank you for everything.
+ Maker for teaching me how to move units in JASS
+ Almia for reviewing the spell and showing me how to improve the code
+ Magtheridon96 for the nulling issue help.


+ udg_FS_Hashtable: Spell Hashtable



JASS:
// CONFIGURATION

// Constants

// Main spell raw code
constant function FS_GetSpellID takes nothing returns integer
    return 'A005'
endfunction

// Main projectile dummy raw code
constant function FS_DummyID takes nothing returns integer
    return 'h002'
endfunction

// Secondary projectile dummy raw code
constant function FS_Dummy2ID takes nothing returns integer
    return 'h003'
endfunction

// Main projectile effect
constant function FS_Sfx takes nothing returns string
    return "Abilities\\Weapons\\LichMissile\\LichMissile.mdl"
endfunction

// Secondary projectile effect
constant function FS_Sfx1 takes nothing returns string
    return "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
endfunction

// Attachment point
constant function FS_AttachPoint takes nothing returns string
    return "origin"
endfunction

// Movement interval
constant function FS_Interval takes nothing returns real
    return 0.03125
endfunction

// Booleans

// True = check if a unit's HP falls below a certain percentage
// False = check if a unit's HP falls below a certain amount
constant function FS_HPPercBool takes nothing returns boolean
    return true
endfunction

// Filter valid units

function FS_FilterUnit takes unit caster, unit u returns boolean
    return IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction

// Get Abilities Level

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

// Level dependable values

// Main target damage
function FS_Damage takes integer level returns real
    return 100 + level*50.
endfunction

// The damage rate (%) for secondary targets
function FS_DamageRate takes integer level returns real
    return (40 + level*10.)/100
endfunction

// Area of Effect
function FS_AoE takes integer level returns real
    return 400 + level*100.
endfunction

function FS_HPCheck takes integer level returns real
    if FS_HPPercBool() then
        // If the target's HP Percentage falls below this rate the spread will be triggered
        return 40. + level*10.
    endif

    // If the target's HP falls below this rate the spread will be triggered
    return 400. + level*100.
endfunction

// Projectile Speed

// The speed of the two type of projectiles
function FS_Speed takes integer level returns real
    return (800. + level*100.)/(1/FS_Interval())
endfunction

// Attack - Damage - Weapon type

// Attack type
function FS_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_NORMAL
endfunction

// Damage type
function FS_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_FIRE
endfunction

// Weapon type
function FS_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

// END CONFIGURATION

// SPELL FUNCTIONS

// The loop where we index, deindex, and recycle everything

function FS_TimerLoop takes nothing returns nothing
    local integer ID
    local integer listmax = LoadInteger(udg_FS_Hashtable, -1, 0 )
    local unit caster
    local unit target
    local unit dummy
    local integer i = 0
    local real x1
    local real y1
    local real x2
    local real y2
    local unit u
    local real r
    local real angle
    local integer i1
    local boolean b
    local group g = CreateGroup()
    local integer ID2

    // 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_FS_Hashtable, -1, i)
        set caster = LoadUnitHandle (udg_FS_Hashtable, ID, 1)
        set target = LoadUnitHandle (udg_FS_Hashtable, ID, 2)
        set dummy = LoadUnitHandle (udg_FS_Hashtable, ID, 3)
        set x1 = GetUnitX (dummy)
        set y1 = GetUnitY (dummy)
        set x2 = GetUnitX (target)
        set y2 = GetUnitY (target)
        set i1 = FS_SpellLvl (caster)
        set b = LoadBoolean (udg_FS_Hashtable, ID, 4)

        if (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) < 900 then
            call KillUnit (dummy)

            // We will check if this projectile is the main one or the secondary one through a saved boolean in order to deal the correct damage

            if not b then
                call DestroyEffect (AddSpecialEffectTarget(FS_Sfx(), target, FS_AttachPoint()))
                call UnitDamageTarget(caster, target, FS_Damage(i1), false, false, FS_AttackType(), FS_DamageType(), FS_WeaponType())
            else
                call DestroyEffect (AddSpecialEffectTarget(FS_Sfx1(), target, FS_AttachPoint()))
                call UnitDamageTarget(caster, target, FS_Damage(i1)*FS_DamageRate(i1), false, false, FS_AttackType(), FS_DamageType(), FS_WeaponType())
            endif

            // Now we check if the projectile that just collided is the main one or not

            if not LoadBoolean (udg_FS_Hashtable, ID, 4) then

                // Branching here: Depends on the spread condition, we will check if the target is fit for spreading

                if FS_HPPercBool() then

                    // Here we go with the HP percentage

                    set r = (GetUnitState (target, UNIT_STATE_LIFE)/GetUnitState (target, UNIT_STATE_MAX_LIFE))*100

                    if r < FS_HPCheck(i1) then
                        call GroupEnumUnitsInRange(g, x2, y2, FS_AoE(i1), null)

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

                            if FS_FilterUnit (caster, u) and IsUnitVisible(u, GetOwningPlayer(caster)) and u != target then

                                // Index and save secondary projectiles

                                set ID2 = LoadInteger (udg_FS_Hashtable, 0, 0)

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

                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 1, caster)
                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 2, u)
                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 3, CreateUnit(GetOwningPlayer(caster), FS_Dummy2ID(), x2, y2, Atan2(GetUnitY(u)-y2, GetUnitX(u)-x2)*bj_RADTODEG))
                                call SaveBoolean (udg_FS_Hashtable, ID2, 4, true)
                                set listmax = listmax + 1
                                call SaveInteger (udg_FS_Hashtable , -1, 0, listmax)
                                call SaveInteger (udg_FS_Hashtable, -1, listmax, ID2)
                                call SaveInteger (udg_FS_Hashtable, ID2, 0, listmax)
                            endif

                            call GroupRemoveUnit(g, u)
                        endloop
                    endif
                else

                    // Here we go with the HP amount

                    set r = GetUnitState (target, UNIT_STATE_LIFE)

                    if r < FS_HPCheck(i1) then
                        call GroupEnumUnitsInRange(g, x2, y2, FS_AoE(i1), null)
                        loop
                            set u = FirstOfGroup(g)
                            exitwhen u == null

                            if FS_FilterUnit (caster, u) and IsUnitVisible(u, GetOwningPlayer(caster)) and u != target then

                                // Index and save secondary projectiles, same as above

                                set ID2 = LoadInteger (udg_FS_Hashtable, 0, 0)

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

                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 1, caster)
                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 2, u)
                                call SaveUnitHandle (udg_FS_Hashtable, ID2, 3, CreateUnit(GetOwningPlayer(caster), FS_Dummy2ID(), x2, y2, Atan2(GetUnitY(u)-y2, GetUnitX(u)-x2)*bj_RADTODEG))
                                call SaveBoolean (udg_FS_Hashtable, ID2, 4, true)
                                set listmax = listmax + 1
                                call SaveInteger (udg_FS_Hashtable, -1, 0, listmax)
                                call SaveInteger (udg_FS_Hashtable, -1, listmax, ID2)
                                call SaveInteger (udg_FS_Hashtable, ID2, 0, listmax)
                            endif

                            call GroupRemoveUnit(g, u)
                        endloop
                    endif
                endif
            endif

            // We are now recycling, clearing the used spell instance's ID and flushing

            if listmax != i then

                call SaveInteger (udg_FS_Hashtable, LoadInteger (udg_FS_Hashtable, -1, listmax), 0, i)
                call SaveInteger (udg_FS_Hashtable, -1, i, LoadInteger (udg_FS_Hashtable, -1, listmax))
                call RemoveSavedInteger (udg_FS_Hashtable, -1, listmax)
                set i = i - 1
            endif

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

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

        else

            if IsUnitType(target, UNIT_TYPE_DEAD) then

            // We are now recycling because the target is dead, also clearing the used spell instance's ID and flushing

                call KillUnit (dummy)

                if not b then
                    call DestroyEffect(AddSpecialEffect (FS_Sfx(), x1, y1))
                else
                    call DestroyEffect(AddSpecialEffect (FS_Sfx1(), x1, y1))
                endif

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

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

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

                call FlushChildHashtable (udg_FS_Hashtable, ID)
            else

                // Move all the projectiles!
                set angle = Atan2(y2-y1, x2-x1)
                call SetUnitX (dummy, x1 + (FS_Speed(i1))*Cos(angle))
                call SetUnitY (dummy, y1 + (FS_Speed(i1))*Sin(angle))
                call SetUnitFacing (dummy, angle*bj_RADTODEG)
            endif
        endif
    set caster = null
    set target = null
    set dummy = null
    endloop

    call DestroyGroup(g)
    set g = null
endfunction

// Initiate function

function Trig_FS_Conditions takes nothing returns boolean
    local unit caster
    local unit target
    local integer ID
    local integer listmax
    local real x1
    local real y1
    local real x2
    local real y2

    if GetSpellAbilityId() == FS_GetSpellID() then
        set caster = GetTriggerUnit()
        set target = GetSpellTargetUnit() 
        set x1 = GetUnitX (caster)
        set y1 = GetUnitY (caster)
        set x2 = GetUnitX (target)
        set y2 = GetUnitY (target)
        set ID = LoadInteger (udg_FS_Hashtable, 0, 0)

        // Now we start indexing the main projectiles

        if ID>0 then
            call SaveInteger (udg_FS_Hashtable, 0, 0, ID - 1)
            set ID = LoadInteger (udg_FS_Hashtable, 0, ID)
        else
            set ID = LoadInteger (udg_FS_Hashtable, 0, -1) + 1
            call SaveInteger (udg_FS_Hashtable, 0, -1, ID)

            if ID == 1 then
                call TimerStart (LoadTimerHandle (udg_FS_Hashtable, -2, 0), FS_Interval(), true, function FS_TimerLoop)
            endif

        endif

        // Save the values related to this spell instance
        call SaveUnitHandle (udg_FS_Hashtable, ID, 1, caster)
        call SaveUnitHandle (udg_FS_Hashtable, ID, 2, target)
        call SaveUnitHandle (udg_FS_Hashtable, ID, 3, CreateUnit(GetTriggerPlayer(), FS_DummyID(), x1, y1, Atan2(y2-y1, x2-x1)*bj_RADTODEG))
        set listmax = LoadInteger (udg_FS_Hashtable, -1, 0) + 1
        call SaveInteger(udg_FS_Hashtable, -1, 0, listmax)
        call SaveInteger (udg_FS_Hashtable, -1, listmax, ID)
        call SaveInteger (udg_FS_Hashtable, ID, 0, listmax)
        set caster = null
        set target = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Frost_Shard takes nothing returns nothing
    local trigger FS = CreateTrigger()
    set udg_FS_Hashtable = InitHashtable ()
    call SaveTimerHandle (udg_FS_Hashtable, -2, 0, CreateTimer())
    call TriggerRegisterAnyUnitEventBJ(FS, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(FS, Condition( function Trig_FS_Conditions ) )
    set FS = null
endfunction


+ v1.0: Initial release
+ v1.0a: Updated the code.
+ v1.0b: Updated the code again after received some suggestions from Geries. Not completed at the moment. Done.
+ ==Null fix== : Fix some nulling issues.
+ v1.1: Spell now uses dynamic indexing. All credit shall go to Geries. I myself deserve nothing.
+ v1.1a: Fixed some leaks, bugs and added more comments.
+ ==Small patch==: Fix according to Mag's review.
+ v1.1b: Fixed the dummy removal and improved the visual aspect.
+ v1.1c: Cleaned the code a bit.
+ v1.1d: Fixed an issue with the timer and indexing method.


Keywords:
frost, ice, spread, spell, jass
Contents

Frost Shard v1.1d (Map)

Reviews
04:17, 1st Jan 2013 Magtheridon96: Approved. Okay, well done. If you're ever up for it, you can totally convert this thing to use arrays since your indices will fit in the range [0 ... 8191] ;) Other than that, this is a job well done.

Moderator

M

Moderator

04:17, 1st Jan 2013
Magtheridon96: Approved.
Okay, well done.

If you're ever up for it, you can totally convert this thing to use arrays since your indices will fit in the range [0 ... 8191] ;)

Other than that, this is a job well done.
 
Review
1)Because of Mag's review,make all of real functions takes integer level
2)Speed and modifier should be inside real function
3)
if SquareRoot( (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1) ) < 30 then
->
if (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1) < 900 then
This way it will be faster in calculating
4)[jass=]set fx = AddSpecialEffectTarget(FS_Sfx1(), target, "origin")
call DestroyEffect (fx)[/code]
->[/icode]call DestroyEffect(AddSpecialEffectTarget(FS_Sfx1(), target, "origin"))[//jass]
5)^Attachment should be configurable
6)local timer t1 = CreateTimer()
you didnt null this
7)Dont use SetUnitPosition,they are slower.Use SetUnitX/Y
8)Sin(angle) & Cos(angle) should be radians
9)0.03125 cache this into a function
10) set r = GetUnitState (target, UNIT_STATE_LIFE)/GetUnitState (target, UNIT_STATE_MAX_LIFE)
you can cache this
11)There are some lines that have misindention,fix it.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Review
1)Because of Mag's review,make all of real functions takes integer level
2)Speed and modifier should be inside real function
3)
if SquareRoot( (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1) ) < 30 then
->
if (x2-x1) * (x2-x1) + (y2-y1) * (y2-y1) < 900 then
This way it will be faster in calculating
4)[jass=]set fx = AddSpecialEffectTarget(FS_Sfx1(), target, "origin")
call DestroyEffect (fx)[/code]
->[/icode]call DestroyEffect(AddSpecialEffectTarget(FS_Sfx1(), target, "origin"))[//jass]
5)^Attachment should be configurable
6)local timer t1 = CreateTimer()
you didnt null this
7)Dont use SetUnitPosition,they are slower.Use SetUnitX/Y
8)Sin(angle) & Cos(angle) should be radians
9)0.03125 cache this into a function
10) set r = GetUnitState (target, UNIT_STATE_LIFE)/GetUnitState (target, UNIT_STATE_MAX_LIFE)
you can cache this
11)There are some lines that have misindention,fix it.

1/, 2/ Can you elaborate a bit? I don't really understand
3/, 4/, 5/, 6/, 7/, 8/, 9/, 11/ Fixing now
10/ Huh?
 
  • JASS:
    set t = CreateTimer ()
    set timerID = GetHandleId (t)
    call SaveUnitHandle(FS_HashtableVar(), timerID, 0, caster)
    call SaveUnitHandle(FS_HashtableVar(), timerID, 1, target)
    call SaveUnitHandle (FS_HashtableVar(), timerID, 2, CreateUnit(GetTriggerPlayer(), FS_DummyID(), GetUnitX(caster), GetUnitY(caster), Atan2(GetUnitY(target)-GetUnitY(caster), GetUnitX(target)-GetUnitX(caster))*bj_RADTODEG))
    call TimerStart( t , FS_Interval() , true , function FS_Periodic )

    After this block of code, t should be nulled.
  • You don't need to inline the TriggerRegisterAnyUnitEventBJ function in the Initialization function. It's not worth it :/
  • I'm going to show you how to correctly null and remove leaks.


    If you have a block of code like this:
    JASS:
    local handle h = something()
    if somethingElse() then
        call DoNothing()
    elseif somethingElse2() then
        if somethingElse3() then
            call DoNothing()
        else
            call DoNothing()
        endif
    endif
    You will notice that I have a ton of branches in here.
    I want to null the handle h. What should I do?
    Well, some people would do this:
    JASS:
    local handle h = something()
    if somethingElse() then
        call DoNothing()
        set h = null
    elseif somethingElse2() then
        if somethingElse3() then
            call DoNothing()
            set h = null
        else
            call DoNothing()
            set h = null
        endif
    endif
    This is bad. You should null a handle or anything that extends a handle like a unit or a destructable in the same block in which you created/set it. The correct way is this:
    JASS:
    local handle h = something()
    if somethingElse() then
        call DoNothing()
    elseif somethingElse2() then
        if somethingElse3() then
            call DoNothing()
        else
            call DoNothing()
        endif
    endif
    set h = null

    Here's another example to clear it up:


    JASS:
    local timer t
    if something() then
        set t = GetExpiredTimer()
    endif
    set t = null
    JASS:
    local timer t = GetExpiredTimer()
    if something() then
        set t = null
    endif
    JASS:
    local timer t = GetExpiredTimer()
    if something() then
        // ..
    endif
    set t = null
    JASS:
    local timer t
    if something() then
        set t = GetExpiredTimer()
        // ...
        set t = null
    endif

    Hopefully, you'll be able to apply this information to rethink how you're nulling all your handles in the main function ;)
  • WEAPON_TYPE_WHOKNOWS is equal to null, but that's not important.
  • You can get rid of the I2R calls inside the configurable functions if you add in a period to the end of any of the numbers in the expressions. (For example, 200 -> 200.)
 
Level 5
Joined
Dec 9, 2012
Messages
125
This is really good! The initial sfx is nice and the frost shards after are great! The frost shards though look like a mass attack of white stuff, might be more suited if there were less... It is a really good spell ;)
4.5/5
 
Top