• 🏆 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!
  • ✅ HD Level Design Contest #1 POLL is now OPEN! Check out the stunning visuals of the final entries. 🔗Click here to cast your vote!

Myrmidon Spellpack v1.1

2 spells that I made based on a hero from Heroes of Newrth. Here's a link for a more detailed information on the hero.

Overview
  • Aqua Field [Active]
  • Subzero Snap [Active]


Details
- The spells are vJASS
- They should be leak-less and lag-less
- It is MUI, meaning can be cast many times at the same instance
- A watery and glacial theme


Implementation
JASS:
//==============================================================================
//                        MYRMIDON SPELLPACK v1.1
//                              By Ayanami
//==============================================================================
//==============================================================================
//                            REQUIREMENTS
//==============================================================================
//
// - vJASS Compiler (NewGen)
// - AIDS
// - Damage
//   - Event
// - GroupUtils
// - GTrigger
// - Timer32
//
//==============================================================================

//==============================================================================
//                           IMPLEMENTATION
//==============================================================================
//
// 1) Copy the whole "Required Systems" Trigger folder & paste in map
// 2) Save the map, it will take a bit longer than usual
// 3) After saving, close the map and re-open it
// 4) Delete or disable the trigger "Objects"
// 5) The required dummy units will have been automatically created for you
// 6) Copy all 4 abilities under "Night Elf" & paste in map
// 7) Copy all 2 buffs under "Night Elf" & paste in map
// 8) Ensure that the following abilities have their buff set properly:
//      Aqua Field (Stun) - Aqua Field
//      Subzero Snap (Freeze) - Subzero Snap
// 9) Copy the whole "Myrmidon" Trigger folder
// 10) Go through all the spell Configurations
//
//==============================================================================


Spell Codes



Forms a watery path in a straight line for 1000 distance. After 1 second, the watery path explodes, causing any unit caught within the path's radius to be stunned and take 125 magic damage per second. The damage duration is equal to the stun duration.

Level 1 - 1.25 seconds.
Level 2 - 1.5 seconds.
Level 3 - 1.75 seconds.
Level 4 - 2.0 seconds.

Cast Range: 1000
Target Type: Point
Cooldown: 10 seconds

JASS:
library AquaField uses AIDS, GT, GroupUtils, T32 optional Damage

constant native UnitAlive takes unit id returns boolean

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABAF' // raw code of ability "Aqua Field"
    private constant integer DUMABILID = 'AAF0' // raw code of ability "Aqua Field (Stun)"
    private constant integer BUFFID = 'BAF0' // raw code of buff "Aqua Field"
    private constant integer DUMMYID = 'duAF' // raw code of unit "Aqua Field Dummy"
    private constant integer CASTERID = 'cAST' // raw code of unit "Caster Dummy"
    
    private constant real PERIOD = 0.1 // interval in delay time is updated
    private constant real DINTERVAL = 50.0 // every distance interval where an effect will be created
                                           // smaller value is more accurate, but might cause framerate to drop
    private constant string FX = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // effect used for explosion
    
    private constant boolean USE_DAMAGE = true // true if you want to this spell to use the Damage library
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage dealt
    private constant damagetype DMG = DAMAGE_TYPE_NORMAL // damage type of damage dealt
endglobals

// filter to determine which target types are allowed
private constant function TargetFilter takes unit u, unit target returns boolean
    return /*
    */ UnitAlive(target) /* // target is alive
    */ and IsUnitEnemy(target, GetOwningPlayer(u)) /* // target is an enemy
    */ and not IsUnitType(target, UNIT_TYPE_STRUCTURE) /* // target is not a structure
    */ and not IsUnitType(target, UNIT_TYPE_MECHANICAL) /* // taget is not mechanical
    */
endfunction

// radius of each field
private constant function GetArea takes integer level returns real
    return 200.
endfunction

// damage dealt per second
private constant function GetDamage takes integer level returns real
    return 125.
endfunction

// delay time before the field explodes
private constant function GetDelay takes integer level returns real
    return 1.
endfunction

// distance of the field
private constant function GetDistance takes integer level returns real
    return 1000.
endfunction

// duration of stun
private constant function GetDuration takes integer level returns real
    return level * 0.25 + 1.
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

private struct Data
    thistype next
    thistype prev
    unit caster
    group dummyg
    group g
    real area
    real damage
    real delay
    real dur
    static thistype tempData
    static integer array store
    static unit castDummy
    static integer groupCount
    static timer delayTimer = CreateTimer()
    static integer delayCount = 0
    
    private static method flushEnumFunc takes nothing returns nothing
        local unit u = GetEnumUnit()
        
        call UnitRemoveAbility(u, BUFFID)
        set store[GetUnitId(u)] = 0
        
        set u = null
    endmethod
    
    private static method stunEnumFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
    
        if UnitAlive(u) and GetUnitAbilityLevel(u, BUFFID) > 0 then
            static if USE_DAMAGE then
                call UnitDamageTargetEx(this.caster, u, this.damage, true, false, ATK, DMG, null)
            else
                call UnitDamageTarget(this.caster, u, this.damage, true, false, ATK, DMG, null)
            endif
            set groupCount = groupCount + 1
        endif
        
        set u = null
    endmethod
    
    private method periodic takes nothing returns nothing
        if this.dur <= 0 then
            call ForGroup(this.g, function thistype.flushEnumFunc)
            call ReleaseGroup(this.dummyg)
            call ReleaseGroup(this.g)
            
            call this.stopPeriodic()
            call this.deallocate()
        else
            set tempData = this
            set groupCount = 0
            call ForGroup(this.g, function thistype.stunEnumFunc)
            if groupCount == 0 then
                set this.dur = 0
            else
                set this.dur = this.dur - T32_PERIOD
            endif
        endif
    endmethod
    implement T32x
    
    private static method filterFunc takes nothing returns boolean
        local thistype this = tempData
        local unit u = GetFilterUnit()
        local integer id
        
        if not IsUnitInGroup(u, this.g) and TargetFilter(this.caster, u) then
            set id = GetUnitId(u)
            if store[id] > 0 then
                call GroupRemoveUnit(thistype(store[id]).g, u)
            endif
            
            set store[id] = this
            call IssueTargetOrder(castDummy, "firebolt", u)
            call GroupAddUnit(this.g, u)
        endif
        
        set u = null
        return false
    endmethod
    
    private static method enumFunc takes nothing returns nothing
        local thistype this = tempData
        local unit u = GetEnumUnit()
        local real x = GetUnitX(u)
        local real y = GetUnitY(u)
        
        call DestroyEffect(AddSpecialEffect(FX, x, y))
        call GroupEnumUnitsInArea(ENUM_GROUP, x, y, this.area, Filter(function thistype.filterFunc))
        call GroupRemoveUnit(this.dummyg, u)
        call RemoveUnit(u)
        
        set u = null
    endmethod
    
    private static method delayIterate takes nothing returns nothing
        local thistype this = thistype(0)
        
        loop
            set this = this.next
            exitwhen this == 0
            
            if this.delay <= 0 then
                set this.next.prev = this.prev
                set this.prev.next = this.next
                
                set delayCount = delayCount - 1
                if delayCount == 0 then
                    call PauseTimer(delayTimer)
                endif
                
                set tempData = this
                call ForGroup(this.dummyg, function thistype.enumFunc)
                call this.startPeriodic()
            else
                set this.delay = this.delay - PERIOD
            endif
        endloop
    endmethod
    
    private static method onCast takes nothing returns boolean
        local thistype this = thistype.allocate()
        local player p
        local integer level
        local integer i
        local real a
        local real x
        local real y
        
        set thistype(0).next.prev = this
        set this.next = thistype(0).next
        set thistype(0).next = this
        set this.prev = thistype(0)
        
        if delayCount == 0 then
            call TimerStart(delayTimer, PERIOD, true, function thistype.delayIterate)
        endif
        set delayCount = delayCount + 1
        
        set this.caster = GetTriggerUnit()
        set this.dummyg = NewGroup()
        set this.g = NewGroup()
        set level = GetUnitAbilityLevel(this.caster, ABILID)
        set this.area = GetArea(level)
        set this.damage = GetDamage(level) * T32_PERIOD
        set this.delay = GetDelay(level)
        set this.dur = GetDuration(level)
        
        set p = GetOwningPlayer(this.caster)
        set x = GetUnitX(this.caster)
        set y = GetUnitY(this.caster)
        set a = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
        set i = R2I(GetDistance(level) / DINTERVAL)
        loop
            exitwhen i == 0
            call GroupAddUnit(this.dummyg, CreateUnit(p, DUMMYID, x + DINTERVAL * i * Cos(a), y + DINTERVAL * i * Sin(a), 0))
            set i = i - 1
        endloop
        
        set p = null
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call GT_RegisterStartsEffectEvent(t, ABILID)
        call TriggerAddCondition(t, Condition(function thistype.onCast))
        set t = null
        
        set castDummy = CreateUnit(Player(13), CASTERID, 0, 0, 0)
        call UnitAddAbility(castDummy, DUMABILID)
    endmethod
endstruct

endlibrary




Launches a powerful glacial force that travels towards the target. If the target is moving during the impact, the glacial force instantly snaps violently, stunning the target for a short period of time.

Level 1 - 100 magic damage. 1.0 second stun.
Level 2 - 150 magic damage. 1.5 seconds stun.
Level 3 - 200 magic damage. 2.0 seconds stun.
Level 4 - 250 magic damage. 2.5 seconds stun.

Cast Range: 1000
Target Type: Unit
Cooldown: 10 seconds

JASS:
library SubzeroSnap uses GroupUtils, GT, T32 optional Damage

constant native UnitAlive takes unit id returns boolean

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABILID = 'ABSS' // raw code of ability "Subzero Snap"
    private constant integer DUMABILID = 'ASS0' // raw code of ability "Subzero Snap (Freeze)"
    private constant integer BUFFID = 'BSS0' // raw code of buff "Subzero Snap"
    private constant integer DUMMYID = 'duSS' // raw code of dummy "Subzero Snap Dummy"
    private constant integer CASTERID = 'cAST' // raw code of dummy "Caster Dummy"
    
    private constant real SPEED = 400.0 // distance travelled by projectile per second
    private constant real HEIGHT = 200.0 // maximum height of the projectile
    private constant real HEIGHTDUR = 1.0 // time taken for 1 curve cycle
    private constant real COLLISION = 50.0 // collision radius for the spell
    
    private constant real PERIOD = 0.10 // frequency for stun timer
                                           // smaller value is more accurate, but might cause framerate to drop
    
    private constant string HITEFFECT = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" // effect used upon impact
    private constant string EFFECT = "war3mapImported\\WaterMagic.mdx" // effect used every interval distance
    private constant real DINTERVAL = 200.0 // every distance interval where EFFECT is created
    
    private constant boolean USE_DAMAGE = true // true if you want to this spell to support the Damage library
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage dealt
    private constant damagetype DMG = DAMAGE_TYPE_NORMAL // damage type of damage dealt
endglobals

// damage dealt
private constant function GetDamage takes integer level returns real
    return level * 50. + 50.
endfunction

// freeze duration
private constant function GetDuration takes integer level returns real
    return level * 0.5 + 0.5
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

globals
    private constant real TRUESPEED = SPEED * T32_PERIOD
    private constant real TRUECOL = COLLISION * COLLISION
    private constant real HEIGHTRATE = (HEIGHT / HEIGHTDUR) * T32_PERIOD
endglobals

private struct Data
    thistype next
    thistype prev
    unit caster
    unit target
    unit dummy
    integer change = 1
    real damage
    real dur
    real height = 0
    real interval = DINTERVAL
    real x
    real y
    static unit castDummy
    static timer linkTimer = CreateTimer()
    static integer linkCount = 0
    
    private static method iterate takes nothing returns nothing
        local thistype this = thistype(0)
        
        loop
            set this = this.next
            exitwhen this == 0
        
            if this.dur <= 0 or not UnitAlive(this.target) then
                set this.next.prev = this.prev
                set this.prev.next = this.next
                
                set linkCount = linkCount - 1
                if linkCount == 0 then
                    call PauseTimer(linkTimer)
                endif
            
                call UnitRemoveAbility(this.target, BUFFID)
                call this.deallocate()
            else
                set this.dur = this.dur - PERIOD
            endif
        endloop
    endmethod
    
    private method periodic takes nothing returns nothing
        local real x = GetUnitX(this.dummy)
        local real y = GetUnitY(this.dummy)
        local real dx = GetUnitX(this.target) - x
        local real dy = GetUnitY(this.target) - y
        local real a
        
        if dx * dx + dy * dy <= TRUECOL then
            static if USE_DAMAGE then
                call UnitDamageTargetEx(this.caster, this.target, this.damage, true, false, ATK, DMG, null)
            else
                call UnitDamageTarget(this.caster, this.target, this.damage, true, false, ATK, DMG, null)
            endif
        
            call KillUnit(this.dummy)
            call DestroyEffect(AddSpecialEffect(HITEFFECT, dx + x, dy + y))
            call this.stopPeriodic()
            
            if dx + x != this.x or dy + y != this.y then
                call IssueTargetOrder(castDummy, "entanglingroots", this.target)
                
                set thistype(0).next.prev = this
                set this.next = thistype(0).next
                set thistype(0).next = this
                set this.prev = thistype(0)
                
                if linkCount == 0 then
                    call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
                endif
                set linkCount = linkCount + 1
            else
                call this.deallocate()
            endif
        elseif not UnitAlive(this.target) then
            call KillUnit(this.dummy)
            call this.stopPeriodic()
            call this.deallocate()
        else
            set dx = dx + x
            set dy = dy + y
            set a = Atan2(dy - y, dx - x)
            set x = x + TRUESPEED * Cos(a)
            set y = y + TRUESPEED * Sin(a)
            call SetUnitX(this.dummy, x)
            call SetUnitY(this.dummy, y)
            
            if this.interval <= 0 then
                call DestroyEffect(AddSpecialEffect(EFFECT, x, y))
                set this.interval = DINTERVAL
            else
                set this.interval = this.interval - TRUESPEED
            endif
            
            if this.height >= HEIGHT then
                set this.change = -1
                set this.height = HEIGHT
            elseif this.height <= 0 then
                set this.change = 1
                set this.height = 0
            endif
            set this.height = this.height + (HEIGHTRATE * this.change)
            call SetUnitFlyHeight(this.dummy, this.height, 0)
            
            set this.x = dx
            set this.y = dy
        endif
    endmethod
    implement T32x
    
    private static method onCast takes nothing returns boolean
        local thistype this
        local integer level
        
        set this = thistype.allocate()
        set this.caster = GetTriggerUnit()
        set this.target = GetSpellTargetUnit()
        set this.x = GetUnitX(this.target)
        set this.y = GetUnitY(this.target)
        set this.dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMYID, GetUnitX(this.caster), GetUnitY(this.caster), Atan2(GetUnitY(this.target) - this.y, GetUnitX(this.target) - this.x) * bj_RADTODEG)
        set level = GetUnitAbilityLevel(this.caster, ABILID)
        set this.damage = GetDamage(level)
        set this.dur = GetDuration(level)
            
        call this.startPeriodic()
        
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call GT_RegisterStartsEffectEvent(t, ABILID)
        call TriggerAddCondition(t, function thistype.onCast)
        set t = null
        
        set castDummy = CreateUnit(Player(13), CASTERID, 0, 0, 0)
        call UnitAddAbility(castDummy, DUMABILID)
    endmethod
endstruct

endlibrary



Credits

Jesus4Lyf - AIDS
- Event
- Damage
- GTrigger
- Timer32
Rising_Dusk - GroupUtils
Anachron - Aqua Field Icon
bigapple90 - Subzero Snap Icon
JetFangInferno - Subzero Snap Model
Romek - Using his name for the hero :D



Changelogs

- Initial relase


- Removed Table requirement
- Test map is now more user-friendly for trackpad users :p


Feedback will be appreciated.

Keywords:
myrmidon, water, glacial, ice, aqua, field, sub, zero, snap
Contents

Myrmidon Spellpack v1.1 (Map)

Reviews
12:00GMT, 17th Jun 2011 Bribe: This looks good. In the future, an update that makes the units get knocked back when that aqua field explodes would really add a nice effect. Approved

Moderator

M

Moderator

12:00GMT, 17th Jun 2011
Bribe:

This looks good. In the future, an update that makes the units get knocked back when that aqua field explodes would really add a nice effect.

Approved
 
It looks cool, and fine... though I'm not a fan of the UnitAlive native as vex's optimizer doesn't work with AI natives... ^_^

but maybe remove the constant keyword from the Get functions (as some of them doesn't really return a constant value, and most probably they won't be returning constant values when the formula is edited to accommodate dependence on level)... afaik, the constant keyword is only used for constant things...
 
Level 9
Joined
Dec 3, 2010
Messages
162
It looks cool, and fine... though I'm not a fan of the UnitAlive native as vex's optimizer doesn't work with AI natives... ^_^

but maybe remove the constant keyword from the Get functions (as some of them doesn't really return a constant value, and most probably they won't be returning constant values when the formula is edited to accommodate dependence on level)... afaik, the constant keyword is only used for constant things...

Just did a query on constant functions and this was the reply:

A constant function is a function that does not call any other non-constant functions and does not use the set statement. Following this, the function you posted is indeed constant.

In practice, constant functions are used (as your example shows) to denote data such as spell configuration. In terms of execution speed, it is absolutely the same as a non-constant function, however, Vexorian's map optimizer will always try to inline constant functions, which would ultimately result in a minor speed gain (not that JassHelper can also inline stuff, but the optimizer's inlining is much more "powerful"). There's also a consant function inliner by blu_da_noob (I haven't tested that one recently, but last time I did, it worked fine).
 
Top