• 🏆 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!

Whirlwind v2.00

Requires Jass NewGen Pack with the most recent Jasshelper.
Spell should be MUI and leakless.

Whirlwind
Conjures several small tornadoes that ravages the target area, dealing damage to nearby enemy units and slowing their movement speed. Enemy units that move too slowly will be tossed into the air, rendering them helpless.
Tornadoes accelerate every second, increasing their speed, damage, and slow.
Level 1 - 3 tornadoes, starts with 10 damage every second and slows by 12%. Lasts 6 seconds.
Level 2 - 4 tornadoes, starts with 13 damage every second and slows by 18%. Lasts 10 seconds.
Level 3 - 5 tornadoes, starts with 16 damage every second and slows by 24%, Lasts 12 seconds.

JASS:
//==========================================================================================
// Whirlwind v2.00 by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
//  - T32               http://www.thehelper.net/threads/timer32.118245/
//  - xe system         http://www.wc3c.net/showthread.php?t=101150
//  * BoundSentinel     http://www.wc3c.net/showthread.php?t=102576
//##########################################################################################
// Importing:
//  1. Copy the abilities: Whirlwind and Slow Aura(Whirlwind). Copy the buff Whirlwind (Slow Aura)
//  2. Implement the required libraries
//  3. Copy this trigger
//  4. Configure the spell, including the abilities
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
//  - Casting time has no effect on the actual ability.
//  - If a targeting spell, such as Blizzard, is targeted over the tornado, it will highlight 
//    it in green because it's a unit.
//==========================================================================================

scope Whirlwind initializer Init

//==========================================================================================
//                              CONSTANTS
//==========================================================================================

    globals
    
//------------------------------------------------------------------------------------------
//      General spell settings
//------------------------------------------------------------------------------------------

        private constant integer    SPELL_ID        = 'A000'  // The raw id of the Whirlwind ability
        private constant integer    SLOW_ID         = 'A001'  // The raw id of the Slow Aura (Whirlwind) ability
        private constant integer    CYCLONE_ID      = 'S000' // The raw id of the Cyclone (Whirlwind) ability
        private constant integer    CYCLONE_BUFF    = 'Bcy2' // The buff of the Cyclone (Whirlwind) ability
        private constant integer    CYCLONE_OID     = 852144 // The order id of the Cyclone ability
        private constant boolean    PRELOAD         = true // Determines whether or not to preload abilities and SFX

//------------------------------------------------------------------------------------------
//      Tornado settings
//------------------------------------------------------------------------------------------
        
        private constant real       TORNADO_AREA    = 100. // The area that each tornado deals damage
        private constant string     TORNADO_SFX     = "Abilities\\Spells\\Other\\Tornado\\TornadoElementalSmall.mdl" // The sfx used for the tornado
        private constant integer    MAX_TORNADOES   = 5 // The max number of tornadoes that can be spawned
        private constant real       ROTATION_SPEED  = bj_PI * 3 / 4 // The number of radians the tornado will move per second

//------------------------------------------------------------------------------------------
//      Damage settings
//------------------------------------------------------------------------------------------

        private constant attacktype ATK_TYPE        = ATTACK_TYPE_NORMAL
        private constant damagetype DMG_TYPE        = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WPN_TYPE        = null
   
   endglobals
   
//==========================================================================================
//                              OTHER CONFIGURATION
//==========================================================================================   
   
    // The target area of the spell
    private constant function Area takes integer lvl returns real 
        return 250. + 100*lvl
    endfunction
    
    // Determines which units will be affected by the spell
    private constant function AffectedTargets takes unit target, player owner returns boolean
        return not IsUnitType(target, UNIT_TYPE_DEAD) and /*
            */ IsUnitEnemy(target, owner) and /*
            */ not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
            */ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
    endfunction
    
    // Determines which units will be checked for cycloning if moving too slowly
    private function AffectedCycloneTargets takes unit target, player owner returns boolean
        return not IsUnitType(target, UNIT_TYPE_DEAD) and /*
            */ IsUnitEnemy(target, owner) and /*
            */ not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
            */ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) and /*
            */ not IsUnitType(target, UNIT_TYPE_FLYING) and /*
            */ GetUnitAbilityLevel(target, CYCLONE_BUFF) < 1
    endfunction
    
    // How long will the tornadoes last
    private constant function Duration takes integer lvl returns real 
        return 6. + 2*lvl
    endfunction
    
    // The number of tornadoes spawned per spell level. Change MAX_TORNADOES accordingly.
    private constant function Number takes integer lvl returns integer 
        return 2 + 1*lvl
    endfunction
    
    // The damage dealt by a tornado per second
    private constant function Damage takes integer lvl returns real 
        return 7. + 3*lvl
    endfunction
    
    // The number of radians the tornado accelerates by per second
    // Damage is increased by the tornado's speed compared to ROTATION_SPEED
    private constant function Acceleration takes integer lvl returns real
        return bj_PI / 10 + bj_PI / 14 * (lvl - 1)
    endfunction
    
    // The number of seconds it takes to increase the slow ability of the Whirlwind
    // Be sure to configure this with Duration accordingly
    private constant function UpdateSlowInterval takes integer lvl returns real
        return 3.0
    endfunction
    
    // The move speed at which units will be cycloned for moving too slowly
    private constant function MoveSpeedThreshold takes integer lvl returns real
        return 175.0
    endfunction
    
//==========================================================================================
//                              END OF CONFIGURATION
//==========================================================================================
   
    private struct Data
        unit cast       // The caster unit
        player owner    // Owner of the caster
        integer lvl     // The level of the spell when the spell was used
        real area       // The spell target area
        real accel      // The tornado's acceleration
        real x          // The x coordinate of the spell target
        real y          // The y coordinate of the spell target
        real count = 0  // Counts how long the spell has been running
        real update = 0 // Determines when to update the slow ability level for each tornado
        
        xefx array tornado[MAX_TORNADOES] // Holds the tornado spawns
        real array ang[MAX_TORNADOES] // Used to make the tornado cycle
        real array dist[MAX_TORNADOES] // The distance the tornado is from the spell target's coordinates
        real array speed[MAX_TORNADOES] // Determines the speed of the tornado based on its distance and ROTATION_SPEED
        integer array turn[MAX_TORNADOES] // The direction of the tornado. 1 is counterclockwise. -1 is clockwise
                   
        method periodic takes nothing returns nothing
            local integer i = 0
            local unit u
            local real dmg
            local boolean increaseSlow = false
            local xecast xc
            local real life
            
            if .count < Duration(.lvl) then    
                set .count = .count + T32_PERIOD
                
                set .update = .update + T32_PERIOD
                if .update > UpdateSlowInterval(.lvl) then
                    set increaseSlow = true
                    set .update = .update - UpdateSlowInterval(.lvl)
                endif      
                
                loop                        
                    set .speed[i] = .speed[i] + .accel * T32_PERIOD
                    set .ang[i] = .ang[i] + .turn[i]*.speed[i] * T32_PERIOD
                    set .tornado[i].x = .x + .dist[i]*Cos(.ang[i])
                    set .tornado[i].y = .y + .dist[i]*Sin(.ang[i])
                    if increaseSlow then
                        set .tornado[i].abilityLevel = .tornado[i].abilityLevel + 1
                    endif
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup, .tornado[i].x, .tornado[i].y, TORNADO_AREA, null)
                    set dmg = Damage(.lvl) * T32_PERIOD * .speed[i] / ROTATION_SPEED
                    loop
                        set u = FirstOfGroup(bj_lastCreatedGroup)
                        exitwhen u == null
                        call GroupRemoveUnit(bj_lastCreatedGroup, u)
                        if AffectedTargets(u, .owner) then
                            // still deal damage to cycloned units by decreasing life
                            // If life is too low, removes the buff and attempts to kill target
                            if GetUnitAbilityLevel(u, CYCLONE_BUFF) > 0 then
                                set life = GetWidgetLife(u)
                                if life - dmg > 0.405 then
                                    call SetWidgetLife(u, life - dmg)
                                else
                                    call UnitRemoveAbility(u, CYCLONE_BUFF)
                                    call UnitDamageTarget(.cast, u, 9999, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE) 
                                endif
                            else
                                call UnitDamageTarget(.cast, u, dmg, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE) 
                            endif
                        endif
                        if AffectedCycloneTargets(u, .owner) and /*
                        */ GetUnitMoveSpeed(u) < MoveSpeedThreshold(.lvl) then
                            set xc = xecast.createBasicA(CYCLONE_ID, CYCLONE_OID, .owner)
                            set xc.level = .lvl
                            call xc.castOnTarget(u)
                        endif
                    endloop
                    set i = i + 1
                    exitwhen i == Number(.lvl)
                endloop
            else // Clean up the spell
                loop
                    call .tornado[i].destroy() // Destroys the tornao sfx
                    set i = i + 1
                    exitwhen i == Number(.lvl)
                endloop
                call .stopPeriodic()
                call .destroy() // The spell is done, destroy the struct
            endif
        endmethod
        
        implement T32x
        
        static method create takes unit c, real x, real y returns thistype
            local thistype this = thistype.allocate()
            local integer i = 0
            local integer mod
            local real randx
            local real randy
            local real distx
            local real disty
            set .cast = c
            set .owner = GetOwningPlayer(c)
            set .x = x
            set .y = y
            set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
            set .area = Area(.lvl)
            set .accel = Acceleration(.lvl)
            
            // Spawning the tornadoes
            loop     
                // randomly spawn the tornado
                set randx = .x + GetRandomReal(-.area/2,.area/2)
                set randy = .y + GetRandomReal(-.area/2,.area/2)
                               
                set .tornado[i] = xefx.create(randx,randy,0)
                set .tornado[i].fxpath = TORNADO_SFX
                set .tornado[i].abilityid = SLOW_ID
                set .tornado[i].abilityLevel = .lvl
                set .tornado[i].owner = .owner
                
                set distx = .x - randx
                set disty = .y - randy
                set .ang[i] = Atan2(disty, distx) 
                set .dist[i] = SquareRoot(distx*distx+disty*disty)
                // farther tornadoes from the cast point move slower
                set .speed[i] = ROTATION_SPEED * (.area-.dist[i]) /.area
                
                // changes the rotation direction depending on what number they were spawned
                set mod = i - (i / 2) * 2
                if mod == 0 then
                    set .turn[i] = 1
                else
                    set .turn[i] = -1
                endif
                set i = i + 1
                exitwhen i == Number(.lvl)
            endloop
            
            call .startPeriodic()
            return this
        endmethod
    endstruct
    
    // Creates a Data struct when the spell is cast
    private function Actions takes nothing returns boolean
        if GetSpellAbilityId() == SPELL_ID then
            call Data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Actions)) // Used a condition since it gets executed faster
        set t = null // Just to null handles
        
        static if PRELOAD then
            call XE_PreloadAbility(SLOW_ID)
            call XE_PreloadAbility(CYCLONE_ID)
            call Preload(TORNADO_SFX)            
        endif        
    endfunction
    
endscope

v2.00
Removed xedamage
TimerUtils is removed in favor of T32
xecast is now used to cyclone units
Tornadoes now accelerate over time, increases their damage and slow
Tornadoes will now cyclone units if they move too slowly. Cycloned units will still receive damage from the tornadoes.
Clarified documentation
Slow aura is now on tornadoes instead of affecting the whole area

v1.00b
Some form of recycling dummy units was implemented

v1.00a
Minor code fixes suggested by PurgeandFire111

v1.00
Released.


Please give me credits if you use this spell in your map.
Credits to:
~ Jesus4Lyf for T32
~ PurgeandFire111 for feedback
~ Vexorian for BoundSentinel, and xe system

Please give any kind of feedback on how I could improve the spell.

Keywords:
whirlwind, tornado, slow, cycle, wind, air, cyclone, rotation, movement speed
Contents

Whirlwind (Map)

Reviews
08:15, 14th Apr 2010 TriggerHappy: The only thing I would suggest is dummy recycling.

Moderator

M

Moderator

08:15, 14th Apr 2010
TriggerHappy:

The only thing I would suggest is dummy recycling.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Seas =)

After some tries I'm still unable to test this map - I always fall back to the "Singleplayer MapScreen" <-- Sorry for that
Just to let people know, I made this with patch 1.24c, though I don't think it really makes a difference...?

I just downloaded the map and I'm able to test it w/o saving, meaning it's compiled correctly.

Did you use JNGP with the most recent jasshelper to test it?
 
Nice job.

I only have two critiques:
JASS:
        set t = null

For the trigger. Lol

And add this:
JASS:
            local real distX
            local real distY

Then add this:
JASS:
                set distX = .y-randy
                set distY = .x-randx
                //might as well pre-calculate them since you use each 3 times
                set .tornado[i] = xefx.create(randx,randy,0)
                set .tornado[i].fxpath = TORNADO_SFX
                set .ang[i] = Atan2(distY,distX) 
                set .dist[i] = SquareRoot((distX)*(distX)+(distY)*(distY))

Since it might be a bit less calculation-heavy to do that.

The only other thing I can think of is that you could also use dummy recycling, but you only use one dummy so I guess it isn't really that needed. =P

Nice coding, I wish I could see the end result but I never bothered to redownload JassHelper properly to get static ifs and optionals to compile.

EDIT: Nice update. Voted for approval. =)
 
Last edited:
Top