1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  3. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  4. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  5. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  6. Shoot to thrill, play to kill. Sate your hunger with the 33rd Modeling Contest!
    Dismiss Notice
  7. Do you hear boss music? It's the 17th Mini Mapping Contest!
    Dismiss Notice
  8. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[JASS] Mystic Storm v1.1i

Submitted by Doomlord
This bundle is marked as approved. It works and satisfies the submission rules.
A remake of the spell Eye of the Storm from DotA with various new features, which distinguishes it from its DotA counterpart.

Note: You should set the field "Art - Projectile Launch - Z" field of the Storm Dummy to about -30.00 for better visual. It is not really a compulsory thing though.

MYSTIC STORM

The Hero calls upon a powerful storm of crackling energy, which strikes weakened enemies periodically with deadly bolts of lightning. The storm is charged with malevolent will, and will seek out only the most injured targets for its armor shattering blasts. The storm lasts for 30/40/50/60 seconds or until the caster perishes. The range and number of units attacked improves with level.

Level 1 - 60 damage every 1.1s. Reduce 1 armor per hit. Attack 1 unit.
Level 2 - 70 damage every 0.95s. Reduce 2 armor per hit. Attack 2 units.
Level 3 - 80 damage every 0.8s. Reduce 3 armor per hit. Attack 3 units.
Level 4 - 90 damage every 0.65s. Reduce 4 armor per hit. Attack 4 units.

Credit:
+ The IceFrog team for the spell idea (and probably the storm cloud model)
+ iAyanami for his awesome technique to get any value with +1 +10 and +100
+ rulerofiron99 for helping me fix the -armor technical bug. Thanks very much.
+ Dr Super Good for helping me fix the issue with the MS_LifeCheck function, optimizing some codes and writing the random cooldown snippet.
+ Geries for introducing me to the concept of code optimization.
+ Radamantus for pointing out some facts for this noob :D

New features:

Multiple new and customizable stats such as:

+ Damage intensification rate
+ Attack and damage type
+ Storm attack range
+ Can attack structures or not
+ Can reduce structures' armor or not when they are attacked
+ ...


Code:

Code (vJASS):
// CONFIGURATION

// Constants

// Main Spell raw code
constant function MS_GetSpellID takes nothing returns integer
    return 'A000'
endfunction

// Dummy raw code
constant function MS_DummyID takes nothing returns integer
    return 'h001'
endfunction

// Dummy Lightning Spell raw code
constant function MS_LightningID takes nothing returns integer
    return 'A001'
endfunction

// Armor (-1) ability raw code
constant function MS_Armor1ID takes nothing returns integer
    return 'A002'
endfunction

// Armor (-10) ability raw code
constant function MS_Armor10ID takes nothing returns integer
    return 'A003'
endfunction

// Armor (-100) ability raw code
constant function MS_Armor100ID takes nothing returns integer
    return 'A009'
endfunction

// Spell Book raw code
constant function MS_SpellBookID takes nothing returns integer
    return 'A007'
endfunction

// Dummy lightning base order string
constant function MS_LightningOrder takes nothing returns string
    return "fingerofdeath"
endfunction

// Booleans

// Can attack structures or not?
constant function MS_AttackStrucs takes nothing returns boolean
    return true
endfunction

// Can reduce structures' armor or not?
constant function MS_ReduceStrucArmor takes nothing returns boolean
    return true
endfunction

// True = target the unit with lowest HP percentage, false = target unit with lowest HP amount
constant function MS_HPPercentage takes nothing returns boolean
    return true
endfunction

// Enable random interval or not?
constant function MS_RandomInterval takes nothing returns boolean
    return false
endfunction

// Minimum value for random interval
constant function MS_RandomIntervalMin takes nothing returns real
    return 1.00
endfunction

// Maximum value for random interval
constant function MS_RandomIntervalMax takes nothing returns real
    return 2.00
endfunction

// Filter valid units

function MS_FilterUnit takes unit caster, unit u returns boolean
    local boolean b = IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitVisible(u, GetOwningPlayer(caster))
    if MS_AttackStrucs() then
        return b
    endif

    return b and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction

// Get Abilities Level

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

// Get the armor (-1) level of the target
function MS_Armor1Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor1ID())
endfunction

// Get the armor (-10) level of the target
function MS_Armor10Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor10ID())
endfunction

// Get the armor (-100) level of the target
function MS_Armor100Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor100ID())
endfunction

// Level dependable values

// Spell Duration
function MS_Duration takes integer level returns real
    return 20 + level*10.
endfunction

// Damage per strike
function MS_Damage takes integer level returns real
    return 50 + level*10.
endfunction

// Armor reduced per strike
function MS_ArmorReduced takes integer level returns integer
    return level
endfunction

// Max attack range of the storm
function MS_Range takes integer level returns real
    return 400 + level*100.
endfunction

// Damage intensification rate i.e the increment of damage percentage with each successive attack
function MS_DmgPlusRate takes integer level returns real
    return 0.0
endfunction

// Cooldown between each strike
function MS_Cooldown takes integer level returns real
    return 1.25 - level*0.15
endfunction

// Max amount of units attacked per strike
function MS_MaxStrike takes integer level returns integer
    return level
endfunction

// Attack - Damage - Weapon type

function MS_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_HERO
endfunction

function MS_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_NORMAL
endfunction

function MS_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

// END CONFIGURATION

// Preload

function MS_Preload takes nothing returns nothing
    local unit dummy
    local integer i = 0

    set dummy  = CreateUnit(Player(0), MS_DummyID(), 0, 0, 0.00)
    call UnitAddAbility (dummy, MS_GetSpellID())
    call UnitAddAbility (dummy, MS_Armor10ID())
    call UnitAddAbility (dummy, MS_Armor10ID())
    call UnitAddAbility (dummy, MS_Armor100ID())
    call UnitAddAbility (dummy, MS_SpellBookID())

    loop
        exitwhen i > 15
        call SetPlayerAbilityAvailable(Player(i), MS_SpellBookID(), false) // Disable the spell book
        set i = i + 1
    endloop

    call RemoveUnit (dummy)
    set dummy = null
endfunction

// End preload

// SPELL FUNCTIONS

// Modify the armor reduction value of the affected unit

function MS_ModifyArmor takes unit target, integer armorreduction returns nothing
    local integer array armor
    local integer level1 = MS_Armor1Lvl(target)
    local integer level2 = MS_Armor10Lvl(target)
    local integer level3 = MS_Armor100Lvl(target)
    local real armorreduce = level1+level2*10+level3*100
    local real armorcalc
    local integer tempdivider
    local integer tempint
    local integer i

    set armor[0] = MS_Armor100ID()
    set armor[1] = MS_Armor10ID()
    set armor[2] = MS_Armor1ID()
    if armorreduce == 0 then
         call UnitAddAbility(target, MS_SpellBookID()) // Add the spell book
         set armorcalc = armorreduce + armorreduction // Set the amount of armor to be modified
         if armorcalc == 0 then
             call UnitRemoveAbility (target, MS_SpellBookID())
         endif
         set i = 0
         set tempint = 0
         set tempdivider = 100
         loop
         exitwhen i == 3
              set tempint = R2I(armorcalc/tempdivider)
              call SetUnitAbilityLevel(target, armor[i], tempint+1)
              set armorcalc = armorcalc - I2R(tempint*tempdivider)
              set tempdivider = tempdivider/10
              set i = i + 1
         endloop
    else
         set armorreduce = level1+level2*10+level3*100-111 // Calculate the current armor reduction rate
         set armorcalc = armorreduce + armorreduction
         if armorcalc == 0 then
             call UnitRemoveAbility (target, MS_SpellBookID()) // Remove the spell book if the unit's armor reduction reaches 0
         endif
         set i = 0
         set tempint = 0
         set tempdivider = 100
         loop
         exitwhen i == 3
              set tempint = R2I(armorcalc/tempdivider)
              call SetUnitAbilityLevel(target, armor[i], tempint+1)
              set armorcalc = armorcalc - I2R(tempint*tempdivider)
              set tempdivider = tempdivider/10
              set i = i + 1
         endloop
    endif
endfunction

// Damage the target and initiate the armor reduction

function MS_DamageTarget takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer timerID = GetHandleId(t)
    local unit caster = LoadUnitHandle(udg_MS_Hashtable, timerID, 0)
    local unit target = LoadUnitHandle(udg_MS_Hashtable, timerID, 1)
    local integer id = LoadInteger (udg_MS_Hashtable, timerID, 2)
    local group g = LoadGroupHandle(udg_MS_Hashtable, id, 3)
    local real dmg = LoadReal (udg_MS_Hashtable, timerID, 3)
    local integer unitId = GetHandleId(target)
    local integer count = LoadInteger (udg_MS_Hashtable, unitId, id)
    local integer i = MS_SpellLvl(caster)

    // Damage the target and check how many armor should be reduced
    call UnitDamageTarget(caster, target, dmg, false, false, MS_AttackType(), MS_DamageType(), MS_WeaponType())

    if MS_ReduceStrucArmor() and IsUnitType(target, UNIT_TYPE_STRUCTURE) then
        call MS_ModifyArmor (target, MS_ArmorReduced(i))
        set count = count + MS_ArmorReduced(i)
        call SaveInteger (udg_MS_Hashtable, unitId, id, count)
    else

        if not IsUnitType (target, UNIT_TYPE_STRUCTURE) then
            call MS_ModifyArmor (target, MS_ArmorReduced(i))
            set count = count + MS_ArmorReduced(i)
            call SaveInteger (udg_MS_Hashtable, unitId, id, count)
        endif

    endif

    if IsUnitType (target, UNIT_TYPE_DEAD) then
        call UnitRemoveAbility (target, MS_SpellBookID())
    endif

    if g != null then
        call GroupAddUnit (g, target)
    else
        set g = CreateGroup()
        call GroupAddUnit (g, target)
    endif

    // Increase the damage for the next hit
    set dmg = dmg*(1.00 + MS_DmgPlusRate(i)/100)
    call SaveGroupHandle(udg_MS_Hashtable, id, 3, g)
    call SaveReal (udg_MS_Hashtable, id, 4, dmg)

    // Clean Up
    call FlushChildHashtable(udg_MS_Hashtable, timerID)
    call PauseTimer(t)
    call DestroyTimer(t)

    set caster = null
    set target = null
    set g = null
    set t = null
endfunction

// Function to loop through units and choose the one with the lowest HP percentage or amount

function MS_LifeCheck takes unit caster, unit stormfx, integer id, real dmg returns nothing
    local real lifecheck
    local group g = CreateGroup()
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local unit target
    local timer t
    local unit u
    local integer timerID
    local real r

    // Group units for valid units
    call GroupEnumUnitsInRange(g, x, y, MS_Range(MS_SpellLvl(caster)), null)

    loop
        set target = FirstOfGroup(g)
        exitwhen target == null or MS_FilterUnit(caster, target) and not IsUnitInGroup (target, LoadGroupHandle(udg_MS_Hashtable, id, 5))
        call GroupRemoveUnit(g, target)
    endloop

    // Loops through the chosen units for the target
    if target != null then
        set lifecheck = GetUnitState(target, UNIT_STATE_LIFE)

        if MS_HPPercentage() then
            set lifecheck = lifecheck/GetUnitState(target, UNIT_STATE_MAX_LIFE)
        endif

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

            if MS_FilterUnit(caster, u) and not IsUnitInGroup (u, LoadGroupHandle(udg_MS_Hashtable, id, 5)) then
                set r = GetUnitState(u, UNIT_STATE_LIFE)

                if MS_HPPercentage() then
                    set r = r/GetUnitState(u, UNIT_STATE_MAX_LIFE)
                endif

                if r<lifecheck then
                    set lifecheck = r
                    set target = u
                endif

            endif

            call GroupRemoveUnit(g, u)
        endloop

        set t = CreateTimer ()
        set timerID = GetHandleId(t)
        call SaveUnitHandle(udg_MS_Hashtable, timerID, 0, caster)
        call SaveUnitHandle(udg_MS_Hashtable, timerID, 1, target)
        call SaveInteger(udg_MS_Hashtable, timerID, 2, id)
        call SaveReal(udg_MS_Hashtable, timerID, 3, dmg)
        call TimerStart (t, 0.3, false, function MS_DamageTarget)
        call GroupAddUnit (LoadGroupHandle(udg_MS_Hashtable, id, 5), target)
        call IssueTargetOrder(stormfx, MS_LightningOrder(), target)

        set target  = null
        set t = null
    endif

    call DestroyGroup (g)
    set g = null
endfunction

// Periodical trigger for moving the dummy and initialize stuffs when the spell ends

function MS_Periodic takes nothing returns nothing
    local trigger trig = GetTriggeringTrigger()
    local integer trigID = GetHandleId (trig)
    local unit caster = LoadUnitHandle(udg_MS_Hashtable, trigID, 1)
    local unit stormfx = LoadUnitHandle(udg_MS_Hashtable, trigID, 2)
    local integer evalcount = GetTriggerEvalCount(trig)
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local group g
    local real dmg = LoadReal (udg_MS_Hashtable, trigID, 4)
    local timer t
    local integer count
    local unit u
    local integer unitid
    local integer i = 0

    // Check if the caster is dead or the spell duration expired to remove the dummy
    if(GetTriggerEventId()==EVENT_UNIT_DEATH)then

        if(GetDyingUnit() == caster)then
            call KillUnit (stormfx) // We kill the dummy if the caster is dead
        endif

        call DestroyGroup (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))
        call KillUnit (stormfx)

        // Remove the armor reduction induced by the spell instance
        set g = LoadGroupHandle(udg_MS_Hashtable, trigID, 3)

        loop
            set u = FirstOfGroup (g)
            exitwhen u == null
            set unitid = GetHandleId (u)
            set count = LoadInteger (udg_MS_Hashtable, unitid, trigID)
            call MS_ModifyArmor (u, (-count))
            call GroupRemoveUnit(g, u)
        endloop

        call DestroyGroup (g)
        call FlushChildHashtable(udg_MS_Hashtable, trigID)
        set g = null
        call DisableTrigger (trig)
        call TriggerRemoveAction (trig, LoadTriggerActionHandle(udg_MS_Hashtable, trigID, 0))
        set trig = null
        call DestroyTrigger (GetTriggeringTrigger())

    else
        call GroupClear (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))

        if evalcount == 1 or evalcount >= LoadInteger(udg_MS_Hashtable, trigID, 6) then

            loop
                exitwhen i == MS_MaxStrike(MS_SpellLvl(caster))
                call MS_LifeCheck(caster, stormfx, trigID, dmg)
                set i = i + 1
            endloop

            if MS_RandomInterval() then
                call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
            else
                call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + R2I(MS_Cooldown(MS_SpellLvl(caster))/0.03125))
            endif
        endif

        call SetUnitPosition (stormfx, x, y)
    endif

    set caster = null
    set stormfx = null
    set trig = null
endfunction

// Initiate function

function Trig_MS_Conditions takes nothing returns boolean
    local trigger trig
    local unit caster
    local unit stormfx
    local real dmg
    local integer trigID

    if GetSpellAbilityId() == MS_GetSpellID() then
        set caster = GetTriggerUnit()
        set trig = CreateTrigger()
        set trigID = GetHandleId (trig)
        set stormfx = CreateUnit(GetTriggerPlayer(), MS_DummyID(), GetUnitX(caster), GetUnitY(caster), bj_UNIT_FACING)
        call UnitAddAbility(stormfx, MS_LightningID())
        call UnitApplyTimedLife (stormfx, 'BTLF', MS_Duration(MS_SpellLvl(caster)))
        call TriggerRegisterTimerEvent(trig, 0.03125, true)
        call TriggerRegisterUnitEvent(trig, caster, EVENT_UNIT_DEATH)
        call TriggerRegisterUnitEvent(trig, stormfx, EVENT_UNIT_DEATH)
        call SaveTriggerActionHandle(udg_MS_Hashtable , trigID, 0, TriggerAddAction( trig, function MS_Periodic ))
        call SaveUnitHandle(udg_MS_Hashtable, trigID, 1, caster)
        call SaveUnitHandle(udg_MS_Hashtable, trigID, 2, stormfx)
        call SaveReal(udg_MS_Hashtable, trigID, 4, MS_Damage(MS_SpellLvl(caster)))
        call SaveGroupHandle(udg_MS_Hashtable, trigID, 5, CreateGroup())
        call SaveInteger(udg_MS_Hashtable, trigID, 6, GetTriggerEvalCount(trig) + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
        set trig = null
        set stormfx = null
    endif
 
    set caster = null
    return false
endfunction

//===========================================================================
function InitTrig_Mystic_Storm takes nothing returns nothing
    local trigger MS = CreateTrigger()
    set udg_MS_Hashtable = InitHashtable ()
    call MS_Preload()
    call TriggerRegisterAnyUnitEventBJ(MS, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( MS, Condition( function Trig_MS_Conditions ) )
    set MS = null
endfunction


Notes:

+ The spell can only reduce a unit's armor up to 999.
+ Multiple instances of the spell resolves the armor debuff independently. However once a spell instance ends it means the armor debuff will be removed. This is a bit of a technical thing and I can't find any solution as of right now. If someone can help then I will be really grateful. Fixed, credit to rulerofiron99.


Changelog:

+ v1.0: Initial release
+ v1.0a: Added minor stuffs here and there to assist in importing. Function prefixes also added.
+ v1.1: Updated the code to follow JESP standard plus multiple adjustments (see code for more details)
+ v1.1a: Updated the code further including fixing some minor bugs.
+ v1.1b: Fixed some more bugs, especially the technical bug mentioned in the notes above (thanks rulerofiron99). In addition the dummy model now can play the death animation but is still removed upon dying.
+ v1.1c: Fixed another bug (first strike doesn't reduce armor). Thanks to Aeroblyctos for pointing that out.
+ v1.1d: Fixed one more bug that sometimes causes the storm to not pick the unit with the lowest HP percentage/amount plus optimizing the code somewhat.
+ ==Null fix==: Fix some issues with nulling.
+ v1.1e: Updated the code following Maker's helpful review.
+ v1.1f: Fixed a tiny little thing. Sorry I didn't notice it sooner :p
+ v1.1g: Cleaned the code and optimized some minor things.
+ v1.1h: Added configuration for multiple strikes simultaneously. Also code improvement :)
+ v1.1i: Added new feature: random cooldown. (tks DSG for his help)


Keywords:
dota, eye of the storm, lightning, storm, jass
Contents

Mystic Storm v1.1i (Map)

Reviews
Moderator
A fine spell that performs ok. Suggested changes: Use only one dummy unit type Merge the preload trigger with the spell trigger You're using a variable named bool. Bool is a keyword, use some other name IsUnitEnemy(u, GetOwningPlayer(caster))...
  1. doomhammer99

    doomhammer99

    Joined:
    Dec 5, 2011
    Messages:
    319
    Resources:
    5
    Models:
    1
    Spells:
    3
    Tutorials:
    1
    Resources:
    5
    Congratulations Brother
     
  2. Furiion52

    Furiion52

    Joined:
    Dec 9, 2012
    Messages:
    113
    Resources:
    0
    Resources:
    0
    Well I reckon its brilliant :D The spell itself is very op which I don't mind :) The cloud effect is quite nice, perhaps slightly less flashes would be more realistic. And the effect and the end of the cloud is wow. It's like a star collapsing its soo good. You should probably make it slightly less op by reducing the amount of time it stays.
    Very, very good spell.
    4.9/5
     
  3. owwweeennn

    owwweeennn

    Joined:
    Jan 21, 2013
    Messages:
    33
    Resources:
    0
    Resources:
    0
    this is...
    RAZOR's SPELL!!