• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Water Cyclone v1.00c

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Whirlpool
v1.00c (10/01/2010)
by BlackRose (aka Siraraz)

Introduction

I'm pretty sure this spell concept was inspired by Ai, the Serpent of the Sea from the Impossible Bosses mod for Warcraft 3. I just loved the use of the naga death water splash spiralling inwards. This spell was also submission for BlizzVault's Spell Contest (Theme: Water, 15/12/2009 to 15/01/2010).​

Description of Spell

Becomes the core of a vicious cyclone, waves of water swirl in, sweeping in enemy units. Upon reaching the eye, the water violently explodes, releasing enemy units swept in a violent nova outwards.​

Coding Details

Requirements:
Credits
  • Vexorian, for JassHelper, TimerUtils, xefx, xepreload
  • Azlier, for AutoFly
JASS:
//+--------------------------------------------
//|     Water Cyclone  v1.00c by BlackRose             
//+--------------------------------------------
//| Requires:
//+----------
//| TimerUtils by Vexorian
//| AutoFly    by Azlier
//| xefx       by Vexorian
//| xepreload  by Vexorian
//|
//| Description
//+------------
//| Becomes the core of a vicious cyclone, waves of water swirl in, 
//| sweeping in enemy units. Upon reaching the eye, the water violently
//| explodes, releasing enemy units swept in a violent nova outwards.
//|
//| Other:
//+--------
//| Uses a modified model of the NagaDeath effect. For performance issues. 
//+---------------------------------------------------------------------------+

scope WaterCyclone initializer onInit // requires TimerUtils, AutoFly, xefx, xepreload

	globals
        private constant integer SPELL_ID = 'A000'
        
        private constant boolean DAMAGE_OVERTIME = false
        private constant boolean SETXY           = false
        // Use Position or XY. Position stops orders.
                
        private constant real TIMER_INTERVAL = 0.03125  // Each x do stuff.
                
        private constant integer FIREWORKS_NUMBER = 35  // How many firework effects are made when done spinning in.
        private constant string  FIREWORKS_EFFECT = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdx"  // Main model of the effect.
        private constant string  FIREWORKS_FLASH  = ""  // Constant flash on effect.
        private constant real    FIREWORKS_SCALE  = 1.00
        private constant string  ON_LANDING_EFFECT = "war3mapImported\\NagaDeathSpare.mdx"//""
        private constant boolean CREATE_ON_DUMMY   = false
                
        private constant real AOE = 900         // Well, not really AoE. How far the spirals start from the caster.
        private constant real ENUM_AOE = 200    // Radius enemy units can be pulled into the washing machine (Cyclone).
        private constant real ENUM_HEIGHT = 90  // Max height units can be to get pulled.
        
        private constant integer SPIRAL_NUMBER = 5  // How many spirals lines there are.
        private constant real SPININ_DURATION = 1.5 // How long it take to spiral inwards.
        private constant real ANGLE_INCREMENT = -5  // Positive for clockwise, negative for anti-clockwise. This spell is indeed a toilet.
        
        private constant real HEIGHT = 45.          // Height of the effect moving in. If you set one.
        private constant string INWARDS_EFFECT = "" // Missile of the spiral. In this case it's none and just constant water flashing.
        private constant string FLASH_EFFECT = "war3mapImported\\NagaDeathSpare.mdx"
        private constant real   INWARDS_SCALE = 1.00 // Missile (INWARDS_EFFECT) size?
        
        private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE = DAMAGE_TYPE_MAGIC
        private constant weapontype WPN_TYPE = null
	endglobals

    // First half is dealt over time during the spin.
    // Second half is dealt upon the landing.
    // If DAMAGE_OVERTIME is true.
    private function DAMAGE takes integer Level returns real
        return Level * 125.
    endfunction
    
    // =========================================================
    //                     NOT CONFIGURABLE
    // =========================================================
    globals
        private constant group ENUM_GROUP = CreateGroup()
        private constant group INGROUP = CreateGroup()
        private unit TEMP_UNIT
        private unit TEMP_CASTER
        private player TEMP_PLAYER
        private boolexpr UNIT_FILTER
        
        private real TX
        private real TY
        private real TEMP_REAL
    endglobals
    
    native UnitAlive takes unit u returns boolean
    
    // ================================================================
    // Which units can be added to the swirl.
    // Currently it is: E
    // -Enemy units of caster, 
    // -Alive units
    // -Non structures.
    // -Units under the height limit.
    // -And units not already in a swirl group.
    // ================================================================
    
    private function UnitFilter takes nothing returns boolean
            set TEMP_UNIT = GetFilterUnit()
            return IsUnitEnemy( TEMP_UNIT, TEMP_PLAYER ) and /*
            */ UnitAlive( TEMP_UNIT ) and /*
            */ IsUnitType( TEMP_UNIT, UNIT_TYPE_STRUCTURE ) == false and/*
            */ GetUnitFlyHeight( TEMP_UNIT ) < ENUM_HEIGHT and /*
            */ IsUnitInGroup( TEMP_UNIT, INGROUP ) == false
    endfunction
    
// Credit to AceHart.
private function GetParabolaZ takes real x,real d,real h returns real
    return 4 * h * x * (d - x) / (d * d)
endfunction

    private struct FireworksData
        boolean NotDummy = false
        real Distance
        real Height
        real Duration
        unit Dummy
        unit Source
        
        real DamageDealt = 0
        real DamageMax
        
        real DPS
        real x
        real y
        real z
        real Speed
        real Cosine
        real Sine
        real d = 0
        
        timer t
        
        xefx myfx
        method onDestroy takes nothing returns nothing
            if not DAMAGE_OVERTIME then
                call UnitDamageTarget( .Source, .Dummy, .DPS, true, true, ATK_TYPE, /*
                */ DMG_TYPE, WPN_TYPE )
            elseif .NotDummy then
                set .DPS = .DamageMax - .DamageDealt
                call UnitDamageTarget( .Source, .Dummy, .DPS, true, true, ATK_TYPE, /*
                */ DMG_TYPE, WPN_TYPE )
            endif
            if not .NotDummy then
                if CREATE_ON_DUMMY then
                    call DestroyEffect( AddSpecialEffect( ON_LANDING_EFFECT, .x, .y ) )
                endif
                call .myfx.destroy()
            else
                call DestroyEffect( AddSpecialEffect( ON_LANDING_EFFECT, .x, .y ) )
                call SetUnitPathing( .Dummy, true )
                call GroupRemoveUnit( INGROUP, .Dummy )
                set .Dummy = null
                set .Source = null
            endif
            call ReleaseTimer( .t )
        endmethod
        
        static method move takes nothing returns nothing
            local thistype d = GetTimerData( GetExpiredTimer() )
            set d.d = d.d + d.Speed
            set d.x = d.x + d.Speed * d.Cosine 
            set d.y = d.y + d.Speed * d.Sine 
            set d.z = GetParabolaZ( d.d, d.Distance, d.Height )

            if d.NotDummy then
                if SETXY then
                    call SetUnitX( d.Dummy, d.x )
                    call SetUnitY( d.Dummy, d.y )
                else
                    call SetUnitPosition( d.Dummy, d.x, d.y )
                endif
                call SetUnitFlyHeight( d.Dummy, d.z, 0 )
            else
                set d.myfx.x = d.x
                set d.myfx.y = d.y
                set d.myfx.z = d.z
                if FIREWORKS_FLASH != "" then
                    call d.myfx.flash( FIREWORKS_FLASH )
                endif
            endif

            if DAMAGE_OVERTIME and d.NotDummy then
                call UnitDamageTarget( d.Source, d.Dummy, d.DPS, true, false, ATK_TYPE, /*
                */ DMG_TYPE, WPN_TYPE )
                set d.DamageDealt = d.DamageDealt + d.DPS
            endif

            if d.d >= d.Distance then
                call d.destroy()
            endif
        endmethod
    
        static method create takes real x, real y, unit Target, unit Source, real DPS returns thistype
            local thistype d = thistype.allocate()
            local real angle = GetRandomReal( 0, 359 ) * bj_DEGTORAD
    
            set d.Distance = GetRandomReal( 200, 600 )
            set d.Duration = d.Distance / 280
            set d.Height = d.Distance * 1.1
            
            set d.x = x
            set d.y = y
            set d.z = 0
            set d.Speed = d.Distance / ( d.Duration / TIMER_INTERVAL )
            set d.Cosine = Cos( angle )
            set d.Sine = Sin( angle )
            
            if Target == null then
                set d.myfx = xefx.create( x, y, angle )
                set d.myfx.fxpath = FIREWORKS_EFFECT
            else
                set d.NotDummy = true
                set d.Dummy = Target
                call SetUnitPathing( d.Dummy, false )
                set d.Source = Source
                set d.DPS = DPS
                if DAMAGE_OVERTIME then
                    set d.DamageMax = d.DPS * (SPININ_DURATION / TIMER_INTERVAL )
                endif
            endif
            
            set d.t = NewTimer()
            call SetTimerData( d.t, d )
            call TimerStart( d.t, TIMER_INTERVAL, true, function thistype.move )
            return 0
        endmethod
    endstruct

	private struct WCData
		unit Caster
		player Owner
		xefx array myfx[SPIRAL_NUMBER]
        real array Cosine[SPIRAL_NUMBER]
        real array Sine[SPIRAL_NUMBER]
        group array Group[SPIRAL_NUMBER]
        group DamagedGroup
        real Radius = AOE
        real RadianAdder
        real RadiusDecrement
        real cx
        real cy
        real DPS
        
        timer t
        static method GroupCallback takes nothing returns nothing
            local unit u = GetEnumUnit()
            local FireworksData f = FireworksData.create( TX, TY, u, TEMP_UNIT, TEMP_REAL )
            if not DAMAGE_OVERTIME then
                call UnitDamageTarget( TEMP_UNIT, u, TEMP_REAL, true, true, /*
                */ ATK_TYPE, DMG_TYPE, WPN_TYPE )
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            local integer i = 0
            local FireworksData f
            call ReleaseTimer( .t )
            if INWARDS_EFFECT != "" then
                loop
                    exitwhen i == SPIRAL_NUMBER
                    call .myfx[i].destroy()
                    set i = i + 1
                endloop
            endif
            set i = 0
            loop
                exitwhen i == FIREWORKS_NUMBER
                set f = FireworksData.create( .cx, .cy, null, null, 0 )
                set i = i + 1
            endloop
            set TX = .cx
            set TY = .cy
            set TEMP_UNIT = .Caster
            set TEMP_REAL = .DPS
            call ForGroup( .DamagedGroup, function thistype.GroupCallback )
            call GroupClear( .DamagedGroup )
            set .Caster = null
            set .Owner = null
        endmethod
        
		static method create takes nothing returns thistype
			local thistype d = thistype.allocate()
            local real x
            local real y
            local integer i = 0
            local real angle = 360 / SPIRAL_NUMBER
                        
            set d.t = NewTimer()

			set d.Caster = GetTriggerUnit()
			set d.Owner = GetOwningPlayer( d.Caster )
            set d.cx = GetUnitX(d.Caster)
            set d.cy = GetUnitY(d.Caster)
            
            set d.RadiusDecrement = ( AOE / ( SPININ_DURATION/TIMER_INTERVAL) )
            set d.RadianAdder     = ANGLE_INCREMENT * bj_DEGTORAD
            
            set d.DPS = DAMAGE( GetUnitAbilityLevel( d.Caster, SPELL_ID ) ) / 2
            if DAMAGE_OVERTIME then
                set d.DPS = d.DPS / ( SPININ_DURATION / TIMER_INTERVAL )
            endif
            
            loop
                exitwhen i == SPIRAL_NUMBER
                set d.Cosine[i] = ( bj_DEGTORAD * (angle*i) )
                set d.Sine[i] = ( bj_DEGTORAD * (angle*i) )
                set x = d.cx + AOE * Cos(d.Cosine[i])
                set y = d.cy + AOE * Sin(d.Sine[i])
                if INWARDS_EFFECT != "" then
                    set d.myfx[i] = xefx.create( x, y, HEIGHT )
                    set d.myfx[i].fxpath = INWARDS_EFFECT
                endif

                if d.Group[i] == null then
                    set d.Group[i] = CreateGroup()
                else
                    call GroupClear( d.Group[i] )
                endif
                set i = i + 1
            endloop
            
            if d.DamagedGroup == null then
                set d.DamagedGroup = CreateGroup()
            else
                call GroupClear( d.DamagedGroup )
            endif
            
			return d
		endmethod
                
        static method MoveXY takes nothing returns nothing
            local unit u = GetEnumUnit()
            call SetUnitX( u, TX )
            call SetUnitY( u, TY )
            if DAMAGE_OVERTIME then
                call UnitDamageTarget( TEMP_UNIT, u, TEMP_REAL, true, true, /*
                */ ATK_TYPE, DMG_TYPE, WPN_TYPE )
            endif
        endmethod
        
        static method update takes nothing returns nothing
            local thistype d = GetTimerData(GetExpiredTimer())
            local real x
            local real y
            local integer i = 0
            local unit u

            loop
                exitwhen i == SPIRAL_NUMBER
                set d.Cosine[i] = d.Cosine[i] + d.RadianAdder
                set d.Sine[i] = d.Sine[i] + d.RadianAdder

                set x = d.cx + d.Radius * Cos(d.Cosine[i])
                set y = d.cy + d.Radius * Sin(d.Sine[i])
                if INWARDS_EFFECT != "" then
                    set d.myfx[i].x = x
                    set d.myfx[i].y = y
                endif
                if FLASH_EFFECT != "" then
                    call DestroyEffect( AddSpecialEffect( FLASH_EFFECT, x, y ) )
                endif
                set TEMP_PLAYER = d.Owner
                call GroupEnumUnitsInRange( ENUM_GROUP, x, y, ENUM_AOE, UNIT_FILTER )
                loop
                    set u = FirstOfGroup( ENUM_GROUP )
                    exitwhen u == null
                    call GroupAddUnit( d.DamagedGroup, u )
                    call GroupAddUnit( d.Group[i], u )
                    call GroupAddUnit( INGROUP, u )
                    call GroupRemoveUnit( ENUM_GROUP, u )
                endloop
                set TX = x
                set TY = y
                set TEMP_UNIT = d.Caster
                set TEMP_REAL = d.DPS
                call ForGroup( d.Group[i], function thistype.MoveXY )
                set i = i + 1
            endloop

            set d.Radius = d.Radius - d.RadiusDecrement

            if d.Radius <= 0 then
                call d.destroy()
            endif
        endmethod
	endstruct

	private function CheckSpell takes nothing returns boolean
        local WCData d
		if GetSpellAbilityId() == SPELL_ID then
            set d = WCData.create()
            call SetTimerData( d.t, d )
			call TimerStart( d.t, TIMER_INTERVAL, true, function WCData.update )
		endif
		return false
	endfunction

	private function onInit takes nothing returns nothing
		local trigger t = CreateTrigger()
        
        call XE_PreloadAbility( SPELL_ID )

		call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
		call TriggerAddCondition( t, Condition( function CheckSpell ) )

        set UNIT_FILTER = Condition( function UnitFilter ) 
		set t = null
	endfunction

endscope
Changelog
v1.00b (10/01/2010)
  • Replaced GetWidgetLife function calls with UnitAlive.
  • Removed FirstOfGroup loops, now using ForGroup() to do group actions.
  • Improved description of global variables.
v1.00b (08/01/2010)
  • Removed DUMMY_ID from globals.
v1.00 (02/01/2010)
  • Initial release.

Keywords:
Water, Vortex, Cyclone, Aqua, Nova, Eyecandy, Pretty, Eye-sex-spells, Spiral, Explosion, Parabola, Fireworks, Waterworks, Hydro
Contents

Water Cyclone (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 14 Nov 2011 Bribe: FirstOfGroup loop should completely replace the group filter, just use "null" for the filter. This just needs 1 timer, not TimerUtils. DAMAGE_OVERTIME -> what...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.


14 Nov 2011
Bribe: FirstOfGroup loop should completely replace the group filter, just use "null" for the filter.

This just needs 1 timer, not TimerUtils.

DAMAGE_OVERTIME -> what happened to static if's that you showed me so long ago?

WPN_TYPE -> null

GetOwningPlayer( d.Caster ) -> GetTriggerPlayer()

onDestroy - destroy that calls deallocate.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
This spell is sooooooo awesome :D

5/5 +rep

I want to use the spell but I don't want to use the JNGP so here is a version which does not require JNGP :grin: (it also works to up to 8190 instances :p)

full credits to BlackRose of cause
and I don't want to steal his downloads or something I just want to give all the people who don't use JNGP the opportunity to use this awesome spell
I still recommend BlackRose's spell for everyone who HAS JNGP
 

Attachments

  • Water Cyclone.w3x
    33.1 KB · Views: 142
Last edited:
Level 25
Joined
Jun 5, 2008
Messages
2,572
I don't like the indentation of the spell, you could have made it a bit better(my opinion only tho).

On the other hand:
/*GetWidgetLife()

If you are giving the people a chance to modify the filters as they should do so properly, use UnitAlive() native as it is faster.

Also FirstOfGroup() is slow, you should replace that.

Didn't test the spell though so can't comment on it.
 
Level 15
Joined
Jul 6, 2009
Messages
889
I don't like the indentation of the spell, you could have made it a bit better(my opinion only tho).
I don't know about the indentation.. It messes up when I paste it from World Editor. If you opened it in WE, it is fine!! WTF!!

On the other hand:
/*GetWidgetLife()

If you are giving the people a chance to modify the filters as they should do so properly, use UnitAlive() native as it is faster.

Hmmm... I heard that native stuffs on optimizing. I dunno.

Also FirstOfGroup() is slow, you should replace that.

Didn't test the spell though so can't comment on it.

Yeah. I should. Will fix with ForGroup() later. You should test the spell :)
 
Level 9
Joined
Oct 7, 2008
Messages
299
Im not scripting but found the lag.
LAG: Man.. If two people starts the effect of Water Cyclone, It don't pick ups units, don't swirl. It instantly explodes at position of caster.

hum ... this is no lag, dude.
If you would say lack, then isn't it right, too.
Do you wanted to say leak? :D

Nevermind,
Like this spell ... very nice to modify. :p
I had made a little fire cyclone out of it. :D
Nice effect. ^^
 
Level 15
Joined
Jul 6, 2009
Messages
889
Hmm, It bugs when you rapid cast it 10 times a row, rapidly.
Leaving dummies in the ground.

Exceeding TimerUtils quantity, 10 timers for the main spell. Then 350 timers for each firework set. GG. Quantity in TimerUtils is 256. o.o Better add a warning too the spell.

Im not scripting but found the lag.
LAG: Man.. If two people starts the effect of Water Cyclone, It don't pick ups units, don't swirl. It instantly explodes at position of caster.

Oh try using the default nagaDeath effect. You would probably experience WORSE lag (Might it be the effects that lag not the coding?). I cast it on two different units, all went fine. Try setting the effects to "" (nothing or something else) and cast it rapidly, what happens then?


Now, used UnitAlive() and no more FoG() loop!! v1.00c :p
 
Last edited:
Top