HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Water Vortex

12-29-2009, 01:18 AM#1
blanc_dummy
Created for a spell contest

Quote:
Originally Posted by Water Vortex
Ability Type: Channeling
Target Type: None
Effect: Area Damage, Disable

Description:
Creates a water vortex at caster point. The vortex sucks some units into it, removing them from existance and damages all other nearby units.

Collapse JASS:
/*
    water vortex v1.03 by scorpion182
    requires:
    timer utils, bound sentinel, xedamage, xefx by Vexorian
    grouputils by Rising_Dusk
*/

library WaterVortex requires TimerUtils, GroupUtils, xefx, xedamage, TerrainPathability, BoundSentinel
    private keyword data //don't touch this
//---------------------------------------------------------------------------------------------
//--------------CALIBRATION SECTION------------------------------------------------------------
    globals
        private constant integer SPELL_ID='A000' //ability rawcode
        private constant string ORDER_ID="starfall" //ability order string
        private constant integer MAXMISSCOUNT=50 // keep this value higher than number of GetMissileCount*GetLayerCount
        private constant string PATH="Abilities\\Weapons\\WaterElementalMissile\\WaterElementalMissile.mdl" //missile fx path
        private constant string CRUSH_FX="Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl" //on-death effect
        private constant string CRUSH_ATTCH="origin" //on-death effect attachment point
        private constant string DAMAGE_FX="" //on-damage effect
        private constant string DAMAGE_ATTCH="origin" //on-damage effect attachment point
        private constant real SCALE=1. //missile scale
        private constant integer RED=255 //vertex coloring in RGB , 
        private constant integer GREEN=255 // alpha is the opacity value
        private constant integer BLUE=255  
        private constant integer ALPHA=255 
        private constant real HEIGHT_INC=50. //missile height increment
        private constant real ANGLE_SPEED=.15 //missile angle speed, it's in radians
        private constant boolean INSTANTKILL=true //instant kill the sucked unit wandering too close if true
        private constant boolean DISABLEPATHING=true // If this is true, the spell will look better but can make units stop in weird places if the channeling is stopped.
                                                     // If it is false, the spell will look a bit worse, but shouldn't be able to create those problems.
    endglobals
    
    private constant function GetTargetCount takes integer lvl returns integer
        return 5+lvl*0 // how many units the maximum can suck into it at the same time
    endfunction
    
    private constant function GetAoE takes integer lvl returns real
        return 600.+lvl*0. // area of effect of the spell
    endfunction
    
    private constant function GetAngleSpeed takes integer lvl returns real
        return 1.309+lvl*0. // how much the targets turn while being sucked in per interval, it's in radians
    endfunction
    
    private constant function GetSpeed takes integer lvl returns real
        return 50.00+lvl*0. // how fast the units are sucked in
    endfunction
    
    private constant function DistanceToAbsorb takes integer lvl returns real
        return 80.00+lvl*0. // how close a sucked unit must be to the center to disappear.
    endfunction
    
    private constant function GetDamage takes integer lvl returns real
        return 30.*lvl //deal damage per interval
    endfunction
    
    private constant function GetMissileCount takes integer lvl returns integer 
        return 5*lvl+0 //number of missiles each layer
    endfunction
    
    private constant function GetLayerCount takes integer lvl returns integer
        return 2*lvl+0 //number of layers
    endfunction
    
    //damage filter
    private function DamageOptions takes xedamage spellDamage returns nothing
        set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
        set spellDamage.atype=ATTACK_TYPE_NORMAL
        set spellDamage.exception=UNIT_TYPE_STRUCTURE
        set spellDamage.visibleOnly=true 
        set spellDamage.damageAllies=false //damage allies if true
    endfunction
    
    //filter the targets, should match the value from DamageOptions
    private function IsValidTarget takes unit u, data s returns boolean
        
        return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitEnemy(u,GetOwningPlayer(s.caster))==true and IsUnitInGroup(u,s.victim)==false and IsUnitVisible(u,GetOwningPlayer(s.caster))==true
        
    endfunction
//------------END OF CALIBRATION----------------------------------------------------------------
    globals
        private group casters=CreateGroup()
        private xedamage xed
    endglobals
    
    private struct data
        unit caster
        timer t
        real x
        real y
        integer lvl
        integer count=0
        group victim
        xefx array fx[MAXMISSCOUNT]
        real array angle[MAXMISSCOUNT]
        private static thistype temp
        
        static method create takes unit c, real x, real y returns data
            local data this=data.allocate()
            local integer i=0
            local integer j=0
            local integer k=0
            local real height=HEIGHT_INC
            local real a=2*bj_PI
            
            call GroupAddUnit(casters,c)
            set .caster=c
            set .x=x
            set .y=y
            set .lvl=GetUnitAbilityLevel(c,SPELL_ID)
            set .victim=NewGroup()
            set .t=NewTimer()
            
            loop
            exitwhen i==GetLayerCount(.lvl)
            
                loop
                exitwhen j==GetMissileCount(.lvl)*(i+1)
                    
                    set .fx[j]=xefx.create(x,y,k*a/GetMissileCount(.lvl))
                    set .fx[j].fxpath=PATH
                    set .fx[j].scale=SCALE
                    set .fx[j].z=height
                    set .angle[j]=k*a/GetMissileCount(.lvl)
                    set height=height+HEIGHT_INC
                    
                    set k=k+1
                    set j=j+1
                endloop
                
                set height=HEIGHT_INC //to synchronize each layer missile' height
                set k=0
                set i=i+1
            endloop
            
            return this
        
        endmethod
        
        static method movecallback takes nothing returns nothing
            local unit f=GetEnumUnit()
            local real x
            local real y
            local real dist
            local real a
            local real d
            
            //move the target unit
            set x=temp.x-GetUnitX(f)
            set y=temp.y-GetUnitY(f)
            set dist=SquareRoot(x*x+y*y)
            
            set a=Atan2(GetUnitY(f)-temp.y, GetUnitX(f)-temp.x)+GetAngleSpeed(temp.lvl)*XE_ANIMATION_PERIOD
            set d=dist-GetSpeed(temp.lvl)*XE_ANIMATION_PERIOD
            set x = temp.x+d*Cos(a)
            set y = temp.y+d*Sin(a)
            
            if (DISABLEPATHING==true) or (DISABLEPATHING==false and (IsTerrainWalkable(x,y) or IsUnitType(f,UNIT_TYPE_FLYING))) then
                call SetUnitX(f, x)
                call SetUnitY(f, y)
            endif
            
            if dist<=DistanceToAbsorb(temp.lvl) and INSTANTKILL==true then
                call xed.useSpecialEffect(CRUSH_FX,CRUSH_ATTCH)
                //instant kill the unit
                call xed.damageTarget(temp.caster,f,99999.)
            endif
            //kick the dead units from the victim group
            if (IsUnitType(f, UNIT_TYPE_DEAD) or GetUnitTypeId(f) == 0) then
                call GroupRemoveUnit(temp.victim,f)
                set temp.count=temp.count-1
            endif
            
            set f=null
        endmethod
        
        //moving the missiles and the targets
        static method move takes nothing returns nothing
            local real a
            local integer i=0
            local integer j=0
            
            loop
            exitwhen i==GetLayerCount(temp.lvl)
                
                loop
                exitwhen j==GetMissileCount(temp.lvl)*(i+1)
                    
                    set a=temp.angle[j]+ANGLE_SPEED
                    
                    set temp.fx[j].x=temp.x+GetAoE(temp.lvl)/(i+1)*Cos(a)
                    set temp.fx[j].y=temp.y+GetAoE(temp.lvl)/(i+1)*Sin(a)
                    
                    set temp.angle[j]=a
                    
                    set j=j+1
                
                endloop
                
                set i=i+1
            endloop
            
            
            call ForGroup(temp.victim,function data.movecallback)
            
        endmethod
        
        private method onDestroy takes nothing returns nothing
            local unit f 
            local integer i=0
            
            call ReleaseTimer(.t)
            call GroupRemoveUnit(casters,.caster)
            //i just hate using ForGroup here :)
            loop
            set f=FirstOfGroup(.victim)
            exitwhen f==null
                call PauseUnit(f,false)
                call GroupRemoveUnit(.victim,f)
            endloop
            //destroy fx
            loop
            exitwhen i==GetLayerCount(.lvl)*GetMissileCount(.lvl)
                call .fx[i].destroy()
                set i=i+1
            endloop
            
            call ReleaseGroup(.victim)
            //unit f will already be null by the end of the FirstOfGroup loop
        endmethod
        
        //filter the targets
        static method VictimFilter takes nothing returns boolean
            local unit u=GetFilterUnit()
            local integer a=GetTargetCount(temp.lvl)
            
            if temp.count==a then
                set u=null
                return false
            endif
            
            if IsValidTarget(u,temp)==true then
                call GroupAddUnit(temp.victim,u)
                call PauseUnit(u,true)
                set temp.count=temp.count+1
            endif
            
            set u=null
            return false
        endmethod
        
        static method Loop takes nothing returns nothing
            local data this=data(GetTimerData(GetExpiredTimer()))
            local integer a=GetTargetCount(.lvl)
            local unit f
            local boolexpr be
            
            if (GetUnitCurrentOrder(.caster)==OrderId(ORDER_ID)) then
                set .temp=this
                //damage nearby units, include the targets
                call GroupEnumUnitsInArea(ENUM_GROUP,.x,.y,GetAoE(.lvl),BOOLEXPR_TRUE)
                call xed.useSpecialEffect(DAMAGE_FX,DAMAGE_ATTCH)
                call xed.damageGroup(.caster,ENUM_GROUP,GetDamage(.lvl)*XE_ANIMATION_PERIOD)
                //search targets
                if .count<a then
                    set be=Condition(function data.VictimFilter)
                    call GroupEnumUnitsInArea(ENUM_GROUP,.x,.y,GetAoE(.lvl),be)
                    call DestroyBoolExpr(be)
                endif
            
                call data.move()
            
                else
                    call .destroy()
            endif
            
            set be=null
            set f=null
        endmethod
    
        //trigger conditions are faster than trigger actions :D
        static method SpellEffect takes nothing returns boolean
            local data this
            local unit caster=GetSpellAbilityUnit()
        
            if GetSpellAbilityId() == SPELL_ID and IsUnitInGroup(caster,casters)==false then
                set this=data.create(caster,GetUnitX(caster),GetUnitY(caster))
                call SetTimerData(.t,this)
                call TimerStart(.t,XE_ANIMATION_PERIOD,true,function data.Loop)
            endif
            set caster=null
            return false
        endmethod
        
        //spell trigger actions
        static method onInit takes nothing returns nothing
            local trigger t=CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t,Condition(function data.SpellEffect))
        
            //init xedamage
            set xed=xedamage.create()
            call DamageOptions(xed)
        endmethod
    
    endstruct
    
endlibrary

Requires:
- TimerUtils by Vexorian
- xe by Vexorian
- BoundSentinel by Vexorian
- GroupUtils by Rising_Dusk
- TerrainPathability by Rising_Dusk

History:
~ v1.00 First Release
~ v1.01 Fixed code a little bit :D
~ v1.01b Remove unnecessary codes.
~ v1.01c Fixed minor bugs.
~ v1.01d Make disable unit pathing configurable.
~ v1.01e Get rid UnitAlive native.
~ v1.02 Fixed disablepathing issues, number of missiles and number of layers are configurable now.
~ v1.02b Replace GroupEnumUnitsInRange with GroupEnumUnitsInArea
~ v1.03 Added TerrainPathability to requirements.
Attached Images
File type: jpgwv.jpg (95.5 KB)
Attached Files
File type: w3xWater Vortex v1.03.w3x (68.1 KB)
12-29-2009, 01:42 AM#2
Rising_Dusk
The visuals look strikingly similar to something I made awhile ago called Hurricane, but whatever. I'll check it out when the time reveals itself to me.
12-29-2009, 11:43 AM#3
blanc_dummy
Quote:
Originally Posted by Rising_Dusk
The visuals look strikingly similar to something I made awhile ago called Hurricane, but whatever. I'll check it out when the time reveals itself to me.

Take your time

Updated Version 1.01
12-29-2009, 05:25 PM#4
Hans_Maulwurf
Quote:
Originally Posted by Rising_Dusk
The visuals look strikingly similar to something I made awhile ago called Hurricane.

I think like 50% of all mapmaker created something with water elemental attack missile circling around once...
12-29-2009, 11:26 PM#5
blanc_dummy
Updated Version 1.01b
12-29-2009, 11:41 PM#6
Veev
I think adding some effect to units as they're being pulled around might help make it look more exciting. The spiraling water is cool and all, but it's boring when units get dragged along.

Also, units entering the vortex don't get pulled along. They just walk straight up to the caster and die (because they're in the center of the vortex, I presume).
12-30-2009, 12:11 AM#7
blanc_dummy
Quote:
Originally Posted by Veev
I think adding some effect to units as they're being pulled around might help make it look more exciting. The spiraling water is cool and all, but it's boring when units get dragged along.

Also, units entering the vortex don't get pulled along. They just walk straight up to the caster and die (because they're in the center of the vortex, I presume).

Quote:
The vortex sucks some units

Collapse JASS:
private constant function GetTargetCount takes integer lvl returns integer
        return 5+lvl*0 // how many units the maximum can suck into it at the same time
    endfunction

Quote:
Originally Posted by Veev
I think adding some effect to units as they're being pulled around might help make it look more exciting.

you can add the effect.

Collapse JASS:
private constant string DAMAGE_FX="" //damage fx
12-30-2009, 12:32 AM#8
Veev
Oh, nice! I didn't see that config line there. Sorry. Maybe explain the effects a little more in the comments? I know the crush effect is the on-death effect, but only after testing the spell in the game. And I only know that DAMAGE_FX is the on-damage effect after you explained it to me. ;)
12-30-2009, 03:03 AM#9
blanc_dummy
Quote:
Originally Posted by Veev
Oh, nice! I didn't see that config line there. Sorry. Maybe explain the effects a little more in the comments? I know the crush effect is the on-death effect, but only after testing the spell in the game. And I only know that DAMAGE_FX is the on-damage effect after you explained it to me. ;)
That's my bad

Updated Version 1.01c
12-30-2009, 03:50 PM#10
titan23
cool spell , i would like to use it , but for some reason it gets units stuck up cliffs/threw walls . this is quite problematic when a hero or unit has no escape ability, will it be possible to prevent this?
12-30-2009, 11:24 PM#11
blanc_dummy
Quote:
Originally Posted by titan23
cool spell , i would like to use it , but for some reason it gets units stuck up cliffs/threw walls . this is quite problematic when a hero or unit has no escape ability, will it be possible to prevent this?


Updated Version 1.01d


disable unit pathing is configurable now, then set DISABLEPATHING to false.

Collapse JASS:
private constant boolean DISABLEPATHING=true // If this is true, the spell will look better but can make units stop in weird places if the channeling is stopped.
                                                     // If it is false, the spell will look a bit worse, but shouldn't be able to create those problems.

EDIT:

Updated Version 1.01e
12-31-2009, 12:18 PM#12
titan23
thanks alot blanc_dummy , nice spell :) +rep to you . would you mind if i use this?
12-31-2009, 03:33 PM#13
azlier
Why did you stop using the UnitAlive native? You essentially removed some safety. And probably speed, but we don't know that yet.
12-31-2009, 06:50 PM#14
grim001
I think the number of layers of orbiting projectiles should be configurable, as well as the number of projectiles in each layer. It would look better with a third layer, I think. In fact the projectile count and number of layers could increase per level. The rotation effect of the enemies should also become stronger with each level of the ability, and should be configurable.
12-31-2009, 08:14 PM#15
titan23
damn :(

i tried it, this is what i have
Collapse JASS:
private constant boolean DISABLEPATHING=false
and it does nothing to stop units going threw buildings or up cliffs/threw walls .

is their any library you can attach to this to prevent units getting stuck up cliffs or going threw buildings? if you stop casting this spell and your right next to a building and time it correctly, the unit will be stuck in the middle of the building. :P