Spell Water Cyclone

BlackRose

Forum User
Reaction score
239
Water Cyclone

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.

Screenshots:
Thanks to Tom_Kazansky
watercyclone1.jpg
watercyclone2.jpg

The libraries this spell requires are:
TimerUtils, xefx, xepreload, AutoFly

JASS:
//+--------------------------------------------
//|     Water Cyclone 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
        private constant real ENUM_AOE = 200
        private constant real ENUM_HEIGHT = 90
        private constant integer SPIRAL_NUMBER = 3
        private constant real SPININ_DURATION = 1.5
        private constant real ANGLE_INCREMENT = 5
        private constant real HEIGHT = 45.
        private constant string INWARDS_EFFECT = ""//"Abilities\\Weapons\\WardenMissile\\WardenMissile.mdx"
        private constant string FLASH_EFFECT = "war3mapImported\\NagaDeathSpare.mdx"//"Abilities\\Spells\\Human\\Feedback\\ArcaneTowerAttack.mdx"
        private constant real   INWARDS_SCALE = 1.00
        
        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
        
    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

// 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.Dummy = CreateUnit( Player(14), DUMMY_ID, x, y, angle * bj_RADTODEG)
                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
        debug real Seconds = 0.00
        method onDestroy takes nothing returns nothing
            local integer i = 0
            local unit u
            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
            loop
                set u = FirstOfGroup( .DamagedGroup )
                exitwhen u == null
                set f = FireworksData.create( .cx, .cy, u, .Caster, .DPS )
                if not DAMAGE_OVERTIME then
                    call UnitDamageTarget( .Caster, u, .DPS, true, true, /*
                    */ ATK_TYPE, DMG_TYPE, WPN_TYPE )
                endif
                call GroupRemoveUnit( .DamagedGroup, u )
            endloop
            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>)
                //set d.Dummy<i> = CreateUnit( d.Owner, DUMMY_ID, x, y, bj_UNIT_FACING )
                if INWARDS_EFFECT != &quot;&quot; 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 UnitFilter takes nothing returns boolean
            set TEMP_UNIT = GetFilterUnit()
            return IsUnitEnemy( TEMP_UNIT, TEMP_PLAYER ) and /*
            */ GetWidgetLife( TEMP_UNIT ) &gt; 0.405 and /*
            */ IsUnitType( TEMP_UNIT, UNIT_TYPE_STRUCTURE ) == false and/*
            */ GetUnitFlyHeight( TEMP_UNIT ) &lt; ENUM_HEIGHT and /*
            */ IsUnitInGroup( TEMP_UNIT, INGROUP ) == false
        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
            debug set d.Seconds = d.Seconds + TIMER_INTERVAL
            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 != &quot;&quot; then
                    set d.myfx<i>.x = x
                    set d.myfx<i>.y = y
                endif
                if FLASH_EFFECT != &quot;&quot; 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 &lt;= 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 WCData.UnitFilter ) 
		set t = null
	endfunction


endscope</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>


Is my indenting really that messed up? [del]I don't recall having random blank lines everywhere.[/del] Alright, I do have random lines. But the spacing is copying / pasting wrongly!!!
 

BlackRose

Forum User
Reaction score
239
[del]I tried. It failed. It made my .gif into a .jpg and then the screenshot failed even more. =.="

Getting a new one.[/del]

And, my god. I don't like [ljass]BJDebugMsg[/ljass]'s stacking up to the point I get confused as to which instance it's showing messages for. [del]I'll debug the function and endfunction then o.o[/del] Removed it entirely.
 

BlackRose

Forum User
Reaction score
239
Try working around with the constant globals, like setting ANGLE_INCREMENT to 25 or something :)
 

BRUTAL

I'm working
Reaction score
118
I don't know if this counts as a glitch, but i spam-casted it like, 4 times quickly.
This happened.
idkr.jpg
 

BlackRose

Forum User
Reaction score
239
How many firework missiles are there? Chances are maybe exceeded TimerUtils quantity. Debug mode will confirm this! Try again :)
 

Joker(Div)

Always Here..
Reaction score
86
Oh, I somehow missed it.

Anyway, spell looks awesome. The screenshot doesnt really give it any justice. I thought it was going to be one of those boring spiral abilites, but it has a cool finish. Good job. :)
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Monovertex Monovertex:
    How are you all? :D
    +1
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top