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: 588

deebee

New Member
Reaction score
15
Awesome spell! I assume you will provide support for adjusting damage later and interesting comments :p
 

BlackRose

Forum User
Reaction score
239
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
 

Dest

New Member
Reaction score
26
Map attachment.

The spell does look nice and is perfect fit for sea heroes. Great work! +rep
 

BlackRose

Forum User
Reaction score
239
Oops, I was gonna update it, I guess I removed the map attachtment, have no fear! It will soon be here!
 

Weep

Godspeed to the sound of the pounding
Reaction score
401
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
 

BlackRose

Forum User
Reaction score
239
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!!
 

GetTriggerUnit-

DogEntrepreneur
Reaction score
129
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.
 

RaiJin

New Member
Reaction score
40
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
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
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!
 

BlackRose

Forum User
Reaction score
239

RaiJin

New Member
Reaction score
40
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

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top