//+--------------------------------------------
//| 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 = 'A04J'
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 = 1000 // Well, not really AoE. How far the spirals start from the caster.
private constant real ENUM_AOE = 300 // 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 250. + (Level * 250.)
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