Spell Whirlpool

BlackRose

Forum User
Reaction score
239
Whirlpool v1.03d by BlackRose

whirlpool.jpg

Becomes the core of a whirlpool, watery waves revolve around, dragging enemies that come along, eventually spitting them outwards.

This spell requires JassNewGenPack and the latest JassHelper (0.A.2.B.).

Made in vJass.

JASS:
library Whirlpool initializer onInit requires SpellEvent, KBS, TimerUtils, GroupUtils, SimError
//+---------------------------------------------------------+\\
//|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|\\
//|                                 v1.03d                  |\|
//|  WHIRLPOOL by BlackRose                                 |\\
//|                                                         |\\
//|_________________________________________________________|\\
//+---------------------------------------------------------+\\
//|                                                         |\\
//| Description: Creates a whirlpool upon the caster,       |\\
//|              bringing in enemies and spinning them      |\\
//|              then tossing them out wards.               |\\
//|                                                         |\\
//| Requirements:   - NewGen                                |\\ 
//|                 - JassHelper 0.A.2.B                    |\\
//|                 - xefx          by Vexorian             |\\
//|                 - SimError      by Vexorian             |\\
//|                 - GroupUtils    by Rising_Dusk          |\\  
//|                 - SpellEvent    by Anitarf              |\\
//|                 - TimerUtils    by Vexorian             |\\
//|                 - KBS           by Kenny!               |\\
//|                                                         |\\
//| JassHelper: <a href="http://www.wc3c.net/showthread.php?t=88142" target="_blank" class="link link--external" rel="nofollow ugc noopener">http://www.wc3c.net/showthread.php?t=88142</a>  |\\
//|                                                         |\\
//| Credit to Vile for his WaterArchon_missile model.       |\\
//|                                                         |\\
//|_________________________________________________________|\\
//+---------------------------------------------------------+\\

//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// CONFIGURABLE GLOBALS - BELOW
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    globals
        // SPELL CONFIGURABLES
        //~~~~~~~~~~~~~~~~~~~~~~
        private constant integer ABILITY_ID = &#039;A000&#039;
        
        private constant real TIMER_PERIOD  = 0.03125
                        
        private constant boolean DISALLOW_MUI  = true   // Can there be more than one instance per unit?
        private constant string  MUI_ERROR_MSG = &quot;Whirlpool is already active.&quot; 

        // VISUAL CONFIGS
        //~~~~~~~~~~~~~~~~~~~~~~
        
        // After the Whirlpool reaches RADIUS.
        // It will adjust to make a sort of randomness in it.
        // The minimum range the Whirlpool can adjust to is:
        private constant real MINIMUM_ADJUST    = 200.
        
        private constant string MISSILE_MODEL   = &quot;war3mapImported\\WaterArchon_Missile.mdx&quot;
        private constant real   MISSILE_SCALE   = 1.00
        private constant real   MISSILE_HEIGHT  = 45.00
        private constant real   ANGLE_INCREMENT = 3 * bj_DEGTORAD   // Angle goes at x every TIMER_PERIOD.
        
        private constant string XEFX_FLASH_SFX   = &quot;&quot;
        private constant string TARGET_FLASH_SFX = &quot;Abilities\\Spells\\Other\\CrushingWave\\CrushingWaveDamage.mdx&quot; 
        private constant string TARGET_FLASH_LOC = &quot;&quot;
        
        private constant boolean AT_LOCATION     = true // Whether to create the TARGET_FLASH at unit location or on it.
        
        private constant string KB_SFX     = &quot;Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdx&quot; 
        private constant string KB_SFX_LOC = &quot;origin&quot;
        
        private constant boolean ROTATE_MISSILE = false

        // DAMAGE CONFIGURABLES
        //~~~~~~~~~~~~~~~~~~~~~~
        private constant real ENUM_AOE  = 128.00
        private constant real TREE_AOE  = 32.00
// Cut and pasted from Kenny&#039;s KBS:
//**    - Area          - This determines whether or not trees will be knocked down.   **
//**                      For trees to be knocked down, a positive number (real) must  **
//**                      be used, such as 150.00, which would give a radius of 150.00 **
//**                      in which trees will be knocked down.                         **
//**                      For trees to not be knocked down, a negative number (real)   **
//**                      must be used, such as -150.00, which would create a radius   **
//**                      that if a tree comes within it, the unit will stop moving.   **
//**                      For none of those effects, the number 0 (0.00) can be used.  **
//**                      This will just cause the units to &quot;bounce&quot; off trees.        **
        
        private constant boolean BUILDUP_DAMAGE = false // Whether damage is built up over time or instantly.
        private constant boolean MULTI_DAMAGE   = true  // Allow units to be swirled more than once per instance?
        
        private constant boolean ABANDON_ON_BLINK = true // For teleport issues, Blinking with Whirlpool is not fun <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite3" alt=":(" title="Frown    :(" loading="lazy" data-shortname=":(" />
        private constant real    BLINK_RANGE      = 300. // Range for a Teleport to be counted.
        
        private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL   // Everyday damage globals.
        private constant damagetype DMG_TYPE = DAMAGE_TYPE_COLD
        private constant weapontype WPN_TYPE = null
    endglobals
    
    // Speed the Whirlpool travels and adjusts.
    private constant function SPEED takes integer level returns real
        return 900.
    endfunction
    
    private constant function DAMAGE takes integer level returns real
        return level * 50.
    endfunction
        
    // Number of waves summoned?
    private constant function WAVES takes integer level returns integer
        return 25
    endfunction
    
    // How long spell last?
    private constant function DURATION takes integer level returns real
        return 2. * level + 2.
    endfunction
    
    // Radius the Whirlpool goes out?
    private constant function RADIUS takes integer level returns real
        return 500.
    endfunction
    
    // Maximum adjust.
    private constant function MAXIMUM_ADJUST takes integer level returns real
        return RADIUS( level ) + 200.
    endfunction
    
    private constant function KB_DISTANCE takes integer level returns real
        return 500.
    endfunction
    
    private constant function KB_DURATION takes integer level returns real
        return 1.5
    endfunction
    
    // How long a unit is in the Whirlpool for.
    private constant function SPIN_DURATION takes integer level returns real
        return 2.5
    endfunction
    
    // How long the wave is &#039;idle&#039; for after finishing on a unit.
    private constant function SPIN_COOLDOWN takes integer level returns real
        return 2.
    endfunction
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// CONFIGURABLE GLOBALS - ABOVE
//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    
    globals
        private group HOLDER_GROUP = CreateGroup()  // Holds units already in a whirlpool.
        private group CASTER_GROUP = CreateGroup()  // For MUI things.
    endglobals
        
    private struct Data
        private group DmgdGroup
        boolean stable  = false
        boolean higher  = false
        boolean stable2 = false
        unit caster
        unit target = null
        integer lvl
        player owner
        
        real x
        real y
        real lx
        real ly
        real angle
        real cd
        real cdDur
        real spinDur
        boolean pushed  = false
        
        real spellRadius 
        real targetRadius = ENUM_AOE
        real dist   = 0.00
        real cdist  = 0.00
        real speed
        
        xefx sfx
        
        real sec = 0.00
        real dur
        real dmg    = 0.00
        real dmgX
        real kbdist = 0.00
        real kbdur  = 0.00
        real kbdistX
        real kbdurX 
        
        timer t
        static thistype tempData
        static method unitFilter takes nothing returns boolean
            local unit u = GetFilterUnit()
            
            if      IsUnitEnemy( u, thistype.tempData.owner )       and /*
            */      IsUnitType( u, UNIT_TYPE_DEAD ) == false        and /*
            */      IsUnitType( u, UNIT_TYPE_STRUCTURE ) == false   and /*
            */      KBS_IsUnitSliding( u ) == false                 and /*
            */      IsUnitInGroup( u, HOLDER_GROUP ) == false       and /*
            */      thistype.tempData.cd &gt;= thistype.tempData.cdDur then
            
                call GroupAddUnit( HOLDER_GROUP, u )
                set thistype.tempData.target   = u
                set thistype.tempData.cd       = 0.00
                set thistype.tempData.pushed   = false
                return false
            endif
            
            return false
        endmethod
        
        private method knockbackUnit takes nothing returns nothing
            if KB_SFX != &quot;&quot; then
                call DestroyEffect( AddSpecialEffectTarget( KB_SFX, this.target, KB_SFX_LOC ) )
            endif
            
            call UnitDamageTarget( this.caster, this.target, this.dmg, true, true, /*
            */   ATK_TYPE, DMG_TYPE, WPN_TYPE )

            call KBS_Begin( this.target, this.kbdist, this.kbdur, this.angle * bj_RADTODEG, TREE_AOE )
            static if MULTI_DAMAGE then
                call GroupRemoveUnit( HOLDER_GROUP, this.target )
            else
                call GroupAddUnit( this.DmgdGroup, this.target )
            endif
            set this.pushed = true
            set this.kbdist = 0.
            set this.kbdur  = 0.
            set this.dmg    = 0.
        endmethod
        
        private static method clearGroup takes nothing returns nothing
            call GroupRemoveUnit( HOLDER_GROUP, GetEnumUnit() )
        endmethod
        
        private method onDestroy takes nothing returns nothing
            static if DISALLOW_MUI then
                if IsUnitInGroup( this.caster, CASTER_GROUP ) then
                    call GroupRemoveUnit( CASTER_GROUP, this.caster )
                endif
            endif
            if this.target != null and this.kbdist &gt; 0 and this.kbdur &gt; 0 then
                call this.knockbackUnit()
                set this.target = null
            endif
            call this.sfx.destroy()

            if MULTI_DAMAGE == false then
                call ForGroup( this.DmgdGroup, function thistype.clearGroup )
                call ReleaseGroup( this.DmgdGroup )
            endif
            
            call ReleaseTimer( this.t )
            set this.t      = null
            set this.caster = null
            set this.owner  = null
        endmethod
                
        private static method periodic takes nothing returns nothing
            local thistype this = GetTimerData( GetExpiredTimer() )
            local real cx = GetUnitX( this.caster )
            local real cy = GetUnitY( this.caster )
            local real tempX = this.lx - cx         // For Blink tracking.
            local real tempY = this.ly - cy         // 
            local integer i  = 0                    // Boolean, if it is 0 then we move.
            set this.x = cx + this.dist * Cos( this.angle )
            set this.y = cy + this.dist * Sin( this.angle )
            set this.lx = cx
            set this.ly = cy
            set this.angle = this.angle + ANGLE_INCREMENT
            set this.sfx.x = this.x
            set this.sfx.y = this.y
                        
            //==============================================================================
            // Whirlpool target movement.
            
            if this.target != null then
                set this.cd = this.cd + TIMER_PERIOD
                // If current cooldown becomes greater than washin duration and we haven&#039;t pushed.
                // PUSH THE UNIT.
                                
                if this.cd &gt;= this.spinDur and this.pushed == false then
                    set i = 1
                    call this.knockbackUnit()
                // Otherwise if cooldown is less tahn spinin duration, we just set X/Y.
                elseif this.cd &lt; this.spinDur then
                    set this.kbdist = this.kbdist + this.kbdistX
                    set this.kbdur  = this.kbdur  + this.kbdurX
                    
                    static if AT_LOCATION then
                        call DestroyEffect( AddSpecialEffect( TARGET_FLASH_SFX, this.x, this.y ) )
                    elseif
                        if TARGET_FLASH_SFX != &quot;&quot; then
                            call DestroyEffect( AddSpecialEffectTarget( TARGET_FLASH_SFX, this.target, TARGET_FLASH_LOC ) )
                        endif
                    endif

                    static if BUILDUP_DAMAGE then
                        set this.dmg = this.dmg + this.dmgX
                    endif
                    
                    // BLINK TRACKER.
                    static if ABANDON_ON_BLINK then
                        if SquareRoot( tempX*tempX+tempY*tempY ) &gt; BLINK_RANGE then
                            set this.cd = this.spinDur - 0.01
                            set i = 1
                        endif
                    endif
                    if i == 0 then
                        call SetUnitX( this.target, this.x )
                        call SetUnitY( this.target, this.y )
                    endif
                endif
                
                if this.cd &gt;= this.cdDur then
                    set this.target = null
                endif
            else
                set thistype.tempData = this
                call GroupEnumUnitsInRange( ENUM_GROUP, this.x, this.y, this.targetRadius, function thistype.unitFilter )
            endif
            
            //==============================================================================
            // Missile visual stuff.
            
            if XEFX_FLASH_SFX != &quot;&quot; then
                call this.sfx.flash( XEFX_FLASH_SFX )
            endif
            
            static if ROTATE_MISSILE then
                set this.sfx.xyangle = this.angle
            endif

            //==============================================================================
            // Reform back inwards.
            
            if this.dist &gt;= this.spellRadius and not this.stable then
                set this.cdist = GetRandomReal( MINIMUM_ADJUST, MAXIMUM_ADJUST( this.lvl ) )
                set this.stable = true
                if this.cdist &gt; this.spellRadius then
                    set this.higher = true
                endif
            elseif not this.stable then
                set this.dist = this.dist + this.speed
                
            elseif this.stable and this.dist &gt;= this.cdist and not this.stable2 then
                set this.dist = this.dist - (this.speed/2)
                 if this.dist &lt;= this.cdist then
                    set this.stable2 = true
                endif
                
            elseif this.stable and this.higher and not this.stable2 then
                set this.dist = this.dist + (this.speed/2)
                if this.dist &gt;= this.cdist then
                    set this.stable2 = true
                endif
            endif
            
            
            //==============================================================================
            // Destruction.
            
            set this.sec = this.sec + TIMER_PERIOD
            if  this.sec &gt;= this.dur                            or /*
            */  IsUnitType( this.caster, UNIT_TYPE_DEAD )       or /*
            */  this.caster == null                             then
                call .destroy()
            endif
        endmethod
                
        static method create takes unit caster, real angle returns thistype
            local thistype this  = thistype.allocate()
            local real     ticks
            
            set this.caster = caster
            set this.lvl    = GetUnitAbilityLevel( this.caster, ABILITY_ID )
            set this.owner  = GetOwningPlayer( this.caster )
            set this.x      = GetUnitX( this.caster )
            set this.y      = GetUnitY( this.caster )
            static if ABANDON_ON_BLINK then
                set this.lx     = x
                set this.ly     = y
            endif
            set this.angle      = angle
            
            set this.sfx        = xefx.create( this.x, this.y, this.angle )
            set this.sfx.fxpath = MISSILE_MODEL
            set this.sfx.z      = MISSILE_HEIGHT
            set this.sfx.scale  = MISSILE_SCALE
            
            set this.dur            = DURATION( this.lvl )
            set this.spellRadius    = RADIUS( this.lvl )
            set this.spinDur        = SPIN_DURATION( this.lvl )
            set this.cdDur          = SPIN_COOLDOWN( this.lvl ) + this.spinDur
            set this.cd             = this.cdDur
            set this.dmg            = DAMAGE( this.lvl )

            set ticks               = this.spinDur / TIMER_PERIOD
            set this.kbdurX         = KB_DURATION( this.lvl ) / ticks
            set this.kbdistX        = KB_DISTANCE( this.lvl ) / ticks
            
            set this.speed          = SPEED( this.lvl ) * TIMER_PERIOD
            
            static if BUILDUP_DAMAGE then
                set this.dmgX       = this.dmg / ticks
                set this.dmg        = 0.00
            endif
            
            if MULTI_DAMAGE == false then
                set this.DmgdGroup = NewGroup()
            endif
            
            set this.t = NewTimer()
            call SetTimerData( this.t, this )
            call TimerStart( this.t, TIMER_PERIOD, true, function thistype.periodic )
            //call KT_Add( function thistype.periodic, this, TIMER_PERIOD )
            return 0
        endmethod
    endstruct

    private function onEffect takes nothing returns nothing
        local unit u      = SpellEvent.CastingUnit
        local integer i   = 0
        local integer lvl = GetUnitAbilityLevel( u, ABILITY_ID )
        local real angle  = 0.00
        local real aIncr  = 360*bj_DEGTORAD / WAVES( lvl )
        loop
            exitwhen i == WAVES( lvl )
            call Data.create( u, angle )
            set angle = angle + aIncr
            set i = i + 1
        endloop
        
        static if DISALLOW_MUI then
            call GroupAddUnit( CASTER_GROUP, u )
        endif
        set u = null
    endfunction
    
    private function onChannel takes nothing returns nothing
        local unit u = SpellEvent.CastingUnit
        if IsUnitInGroup( u, CASTER_GROUP ) then
            call SimError( GetOwningPlayer( u ), MUI_ERROR_MSG )
            call PauseUnit( u, true )
            call IssueImmediateOrder( u, &quot;stop&quot; )
            call PauseUnit( u, false )
        endif
        set u = null
    endfunction

//===========================================================================
private function onInit takes nothing returns nothing
    call RegisterSpellEffectResponse( ABILITY_ID, onEffect )
    static if DISALLOW_MUI then
        call RegisterSpellChannelResponse( ABILITY_ID, onChannel )
    endif
endfunction

endlibrary


v1.03d: 10th April 2010 Saturday
- Code update.
- Fixed up unit facing issues.

v1.03c: 9th April 2010 Friday
<If I remember all>
- Spell completely recoded.
- Added Teleport safety config, if you Blink the targets will not move along with you but be knocked during that time.
- Damage can be either built up over spinin duration or at knockback.
- Knockback and duration are builtup now.
- Added way more configurables, such as flash effects, and crap.
- TOO MANY TO LIST <3

v1.03b: 11th August
- Using KT2 instead of TimerUtils.
- 1.24 compatible (works with 1.23 as well)

v1.03: 21st July
- Stops targets from getting out of map boundary.

v1.02: 17th July
- Code udpate.
- Rather than moving instantly into the middle, leaving the strange "ribbon
effect", it now moves in.

v1.01: 9th July
- Code udpate, DAMN IFs.... /* */ FTW.
- Extra configurable, allow units to be multiple swirled?

v1.00: 9th July 2009
- Initial release.
 

Attachments

  • Whirlpool v1.03d.w3x
    156.2 KB · Views: 611
Awesome spell! I assume you will provide support for adjusting damage later and interesting comments :p
 
Err.... :D You do mean the random if's part?
What do you mean provide support? You can;
JASS:
set G_Damage = Level * 55.

Thanks anyway
 
Map attachment.

The spell does look nice and is perfect fit for sea heroes. Great work! +rep
 
Oops, I was gonna update it, I guess I removed the map attachtment, have no fear! It will soon be here!
 
Cool spell, but looks a bit awkward when the spinning water elements "teleport" inward after their initial spiral outward, making a momentary straight "ribbon" line connecting their old and new radius...

Also, no tooltips? :p
 
Finally, on a small I get feedback :D
Cool spell
Thanks.
but looks a bit awkward when the spinning water elements "teleport" inward after their initial spiral outward, making a momentary straight "ribbon" line connecting their old and new radius...
I realise, then you must notice that it also moves any associated target with it inwards.
Also, no tooltips?
I find Object Editor the hardest out of everything :D

Thanks. I'll try smoothen the spell further later.... my favourite show is ON!!
 
JASS:
    globals
        private real G_WaterNo
        private real G_SpinPickupAoE
        private real G_SpinDur
        private real G_SpellDur
        private real G_SpinOutDist
        private real G_MinSpinDist
        private real G_MinAngSpeed
        private real G_MaxAngSpeed
        private real G_InitSpeed
        private real G_Damage
        private real G_KBDist
        private real G_KBDur
    endglobals


Shoudlnt these be struct members since 2 units having the ability at a different level can cast it.
 
JASS:
    globals
        private real G_WaterNo
        private real G_SpinPickupAoE
        private real G_SpinDur
        private real G_SpellDur
        private real G_SpinOutDist
        private real G_MinSpinDist
        private real G_MinAngSpeed
        private real G_MaxAngSpeed
        private real G_InitSpeed
        private real G_Damage
        private real G_KBDist
        private real G_KBDur
    endglobals


Shoudlnt these be struct members since 2 units having the ability at a different level can cast it.

no because its reset here

JASS:
set d.SpinPickupAoE = G_SpinPickupAoE
            set d.SpinDur     = G_SpinDur
            set d.SpellDur    = G_SpellDur
            set d.SpinOutDist = G_SpinOutDist * G_SpinOutDist
            set d.InitSpeed   = G_InitSpeed
            set d.MinAngSpeed = G_MinAngSpeed
            set d.MaxAngSpeed = G_MaxAngSpeed
            set d.Damage = G_Damage
            set d.KBDist = G_KBDist
            set d.KBDur = G_KBDur
 
I want multiple whirlpool on a hero !!! :D

JASS:
call DestroyGroup( .RotateGroup )


Destroying group is not good, why not recycle it?

JASS:
struct blah~
group RotateGroup = CreateGroup()
endstruct


Create a group on struct init, then recycle it!
 
create another global group instead of dynamically creating one then destroying it, and instead of CountUnitsInGroup(g) > 0 do

FirstOfGroup(g) != null

D: make a global group and if you do make a global group don't do GroupClear because its pointless
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top