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

Cyclone Leap V1.04

A spell I made based on this request.
redblack;1735783 said:
Hello, I need help with one hero spells. First spell, second and third one has 4 levels but ultimate has only 3 levels.
First one:
Leap

Zephyr summons a gust of wind in the direction of his choosing, pushing and dealing damage to enemies it hits.
Action: Target Vector
Type: Enemy Units
Type: Magic
Range: 700
Radius: 250
Mana Cost: 120
Cooldown: 18.0 / 16.0 / 14.0 / 12.0 Seconds
Required Level: 1 / 3 / 5 / 7
Activation
Leaps to target position dealing 75 / 150 / 225 / 300 Magic damage. (Can be used to run from cliff)


v1.01 - fixed the bug that cause the unit to get moved permanently if spell is casted on your portrait...
v1.02 - changed the height changing method, does not use the parabola function now...
v1.02b - set the Filter function into a global variable
v1.03 - added a check to ensure that the unit will not go out of the map
v1.04 - optimized code



JASS:
/////////////////////////////////////////////////////////////////////////////////////////
//Cyclone Leap v1.04                                                                  //
//by Adiktuz                                                                           //
//Credits to The_Reborn_Devil and Silvenon for GroupRecycler and Knockback libraries   //
//How to use:                                                                          //
//1) Copy this whole library and paste into your map                                   //
//2) Copy the spell data if you want                                                   //
//3)Modify the fields to your liking                                                   //
/////////////////////////////////////////////////////////////////////////////////////////
scope Leap initializer init

    globals
        private constant integer LEAP_ID = 'A000' //Rawcode of Leap spell
        private constant integer FLY_ID = 'Amrf' //RAwcode of medivh's raven form
        private constant string CYCLONE = "Abilities\\Spells\\Other\\Tornado\\TornadoElemental.mdl" //Path to the cyclone model
        private constant real TICK = .03 //Timer interval
        private constant string A_POINT = "origin" //The attachment point of the effect
        private constant attacktype AT = ATTACK_TYPE_MAGIC
        private constant damagetype DT = DAMAGE_TYPE_NORMAL
        //Do not edit below the following globals
        //There are some functions to edit values after this globals
        private timer LEAP_TIMER = CreateTimer()
        private group TEMP_GROUP = CreateGroup() //I cannot use NewGroup in a global block
        private Leap data
        private Leap instance
        private real ANGLE = 0.00
        private integer array DAT[8190]
        private integer TOTAL = 0
        private boolexpr FILTER
        private real MaX
        private real MiX
        private real MaY
        private real MiY
        private unit FU = null
        //
    endglobals
    //Various functions for some of the data
    //This allows easier formula modification
    private function DPS takes integer level returns real
        return level*75.00
    endfunction
    
    private function RADIUS takes nothing returns real
        return 250.00
    endfunction
    
    private function DIST_PER_SEC takes nothing returns real
        return 600.00
    endfunction
    
    private function KB_DIST takes nothing returns real
        return 100.00
    endfunction
    
    private function KB_TIME takes nothing returns real
        return 1.00
    endfunction
    
    private function GRAV takes nothing returns real
        return 100.00
    endfunction
    //End functions
    
    struct Leap
        unit caster
        effect cyclone
        integer level
        player owner
        real x
        real y
        real tx
        real ty
        real dx
        real dy
        group damagegroup
        real damage
        real distanceperloop
        real totaldistance
        real z
        real dz
        
        static method check takes nothing returns boolean
            set FU = GetFilterUnit()
            if IsUnitEnemy( FU, instance.owner) and GetWidgetLife(FU) >= .405 and IsUnitInGroup(FU, instance.damagegroup) != true then
                call UnitDamageTarget(instance.caster, FU, instance.damage, false, false, AT, DT, null)
                call GroupAddUnit(instance.damagegroup, FU)
                call Knockback(FU, KB_DIST(), bj_DEGTORAD*(GetUnitFacing(FU) + 180.00),KB_TIME() )
            endif
            return true
        endmethod
        
        static method onLoop takes nothing returns nothing
            local integer i = 0
            loop
                exitwhen i == TOTAL
                set data = DAT[i]
                set data.x = data.x + data.dx
                set data.y = data.y + data.dy
                if data.x <= MaX and data.x >= MiX then
                    call SetUnitX(data.caster, data.x)
                endif
                if data.y <= MaY and data.y >= MiY then
                    call SetUnitY(data.caster, data.y)
                endif
                set data.z = data.z + data.dz
                set data.dz = data.dz - TICK*GRAV()
                call SetUnitFlyHeight(data.caster, data.z, 0.00)
                set instance = data
                call GroupEnumUnitsInRange(TEMP_GROUP, data.x, data.y, RADIUS() , FILTER)
                if data.z <= 0.00 then
                    call DestroyEffect(data.cyclone)
                    call ReleaseGroup(data.damagegroup)
                    call data.destroy()
                    set TOTAL = TOTAL - 1
                    set DAT[i] = DAT[TOTAL]
                    set i = i - 1
                endif
                set i = i + 1
            endloop
            if TOTAL == 0 then
                call PauseTimer(LEAP_TIMER)
            endif
        endmethod
        
    
    
        static method Create takes nothing returns thistype
            set data = Leap.allocate()
            set data.caster = GetTriggerUnit()
            set data.cyclone = AddSpecialEffectTarget(CYCLONE, data.caster, A_POINT)
            set data.owner = GetOwningPlayer(data.caster)
            set data.tx = GetSpellTargetX()
            set data.ty = GetSpellTargetY()
            set data.x = GetUnitX(data.caster)
            set data.y = GetUnitY(data.caster)
            set ANGLE = Atan2(data.ty - data.y, data.tx - data.x)
            set data.damagegroup = NewGroup()
            set data.level = GetUnitAbilityLevel(data.caster, LEAP_ID)
            set data.totaldistance = SquareRoot((data.tx - data.x)*(data.tx - data.x) + (data.ty - data.y)*(data.ty - data.y))
            set data.distanceperloop = TICK*DIST_PER_SEC()
            set data.z = 0.00
            set data.dz = (GRAV() * (data.totaldistance/DIST_PER_SEC()))/2
            //This next if is to ensure that the spell works correct when you cast in on your portrait
            //coz it causes a weird bug...
            if data.totaldistance <= 0.00 then
                set data.totaldistance = 1.00
            endif
            set data.dx = data.distanceperloop*Cos(ANGLE)
            set data.dy = data.distanceperloop*Sin(ANGLE)
            set data.damage = DPS(data.level)
            if UnitAddAbility(data.caster, FLY_ID) then
                call UnitRemoveAbility(data.caster, FLY_ID)
            endif
            set DAT[TOTAL] = data
            set TOTAL = TOTAL + 1
            if TOTAL == 1 then
                call TimerStart(LEAP_TIMER, TICK, true, function Leap.onLoop)
            endif
            return data
        endmethod
    endstruct
    
    
    private function LeapCheck takes nothing returns boolean
        return GetSpellAbilityId() == LEAP_ID
    endfunction
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local integer i = 0
        call TriggerAddAction(t, function Leap.Create)
        call TriggerAddCondition(t, Filter(function LeapCheck))
        loop
            exitwhen i > 15
            call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
            set i = i + 1
        endloop
        set FILTER = Filter(function Leap.check)
        set MaX = GetRectMaxX(bj_mapInitialPlayableArea)
        set MiX = GetRectMinX(bj_mapInitialPlayableArea)
        set MaY = GetRectMaxY(bj_mapInitialPlayableArea)
        set MiY = GetRectMinY(bj_mapInitialPlayableArea)
    endfunction
    
endscope

Needs JNGP to edit/save


The_Reborn_Devil for GroupRecycler
Silvenon for Knockback system
The_Reborn_Devil for the suggestions


Keywords:
HoN, zephyr, leap, cyclone, knockback, damaging, jass, vjass, scripts, wind, element
Contents

Cyclone Leap 1.04 (Map)

Reviews
10:14, 23rd Oct 2010 The_Reborn_Devil: The triggering looks good now. Status: Approved Rating: Useful PM me or another mod once you've updated this to get it reviewed again. Have a nice day! Edit: Wut, u haz fixed? Lemme test again...

Moderator

M

Moderator

10:14, 23rd Oct 2010
The_Reborn_Devil:
The triggering looks good now.

Status: Approved
Rating: Useful

PM me or another mod once you've updated this to get it reviewed again. Have a nice day!


Edit: Wut, u haz fixed? Lemme test again.
Edit2: Still getting the bug I mentioned.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
JASS:
    function NewGroup takes nothing returns group
        if Total == 0 then
            set G[0] = CreateGroup()
        else
            set Total = Total - 1
        endif
        return G[Total]
    endfunction
leaks a group first time it is used because group[0] is initialized
also this function does not need the "else"
JASS:
            else
                call GroupClear(g)
                set G[Total] = g
                set Total = Total + 1
            endif
also overwrites group[0]

not your code though and 1 leak is not that much :p

JASS:
private function TreeFilter takes nothing returns boolean
    local destructable d = GetFilterDestructable()
    local boolean i = IsDestructableInvulnerable(d)
    local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, GetWidgetX(d), GetWidgetY(d), 0)
    local boolean result = false

    call UnitAddAbility(u, 'Ahrl')

    if i then
        call SetDestructableInvulnerable(d, false)
    endif

    set result = IssueTargetOrder(u, "harvest", d)
    call RemoveUnit(u)

    if i then
      call SetDestructableInvulnerable(d, true)
    endif

    set u = null
    set d = null
    return result
endfunction
this tree filter function is bad because it creates a unit for every checked destructible
use a global one
(not your code again but still bad)

JASS:
            if dat.r != 0 then
                set BoolAr[0] = dat.r > 0
                
                set rad = dat.r
                
                if not BoolAr[0] then
                    set rad = rad * (-1)
                endif
                
                set r = Rect(x - rad, y - rad, x + rad, y + rad)

                call EnumDestructablesInRect(r, Cond, function KillTree)
                call RemoveRect(r)
                
                set r = null
            endif
rect is created again and again although its size is the same
better create it once and use "call MoveRectTo(rect, x, y)"

further the knockback has no pathing check

JASS:
IsUnitInGroup(GetFilterUnit(), TEMP_GROUP_E) != true
is the same as
JASS:
not IsUnitInGroup(GetFilterUnit(), TEMP_GROUP_E)
but there are some functions which require the !=/==check because blizzard is stupid
don't know about this one
 
Level 22
Joined
Dec 31, 2006
Messages
2,216
JASS:
    function NewGroup takes nothing returns group
        if Total == 0 then
            set G[0] = CreateGroup()
        else
            set Total = Total - 1
        endif
        return G[Total]
    endfunction
leaks a group first time it is used because group[0] is initialized
Hmm, can't remember why I did it like that :D
Should be:
JASS:
function NewGroup takes nothing returns group
    if Total == 0 then
        return CreateGroup()
    endif
    set Total = Total - 1
    return G[Total]
endfunction

also this function does not need the "else"
JASS:
            else
                call GroupClear(g)
                set G[Total] = g
                set Total = Total + 1
            endif
The else is needed. If the array is full then the group should be destroyed, if the array isn't full then the group should be added to the array and cleared.

JASS:
private function TreeFilter takes nothing returns boolean
    local destructable d = GetFilterDestructable()
    local boolean i = IsDestructableInvulnerable(d)
    local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, GetWidgetX(d), GetWidgetY(d), 0)
    local boolean result = false

    call UnitAddAbility(u, 'Ahrl')

    if i then
        call SetDestructableInvulnerable(d, false)
    endif

    set result = IssueTargetOrder(u, "harvest", d)
    call RemoveUnit(u)

    if i then
      call SetDestructableInvulnerable(d, true)
    endif

    set u = null
    set d = null
    return result
endfunction
this tree filter function is bad because it creates a unit for every checked destructible
use a global one
Using 1 global unit can actually bug if the function is used rapidly after each other.

JASS:
            if dat.r != 0 then
                set BoolAr[0] = dat.r > 0
                
                set rad = dat.r
                
                if not BoolAr[0] then
                    set rad = rad * (-1)
                endif
                
                set r = Rect(x - rad, y - rad, x + rad, y + rad)

                call EnumDestructablesInRect(r, Cond, function KillTree)
                call RemoveRect(r)
                
                set r = null
            endif
rect is created again and again although its size is the same
better create it once and use "call MoveRectTo(rect, x, y)"
Yup, but it's not his system xD

further the knockback has no pathing check
The pathing check isn't required as the system uses SetUnitPosition (which checks pathing) and not SetUnitX/Y (which ignore pathing).

JASS:
IsUnitInGroup(GetFilterUnit(), TEMP_GROUP_E) != true
is the same as
JASS:
not IsUnitInGroup(GetFilterUnit(), TEMP_GROUP_E)
but there are some functions which require the !=/==check because blizzard is stupid
don't know about this one
Ye.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
The_Reborn_Devil said:
The else is needed.
ok

The_Reborn_Devil said:
Using 1 global unit can actually bug if the function is used rapidly after each other.

never happened to me
do you have any prove for this?
(I don't want to accuse you of saying wrong stuff; I'm just curious :grin:)

The_Reborn_Devil said:
The pathing check isn't required as the system uses SetUnitPosition (which checks pathing) and not SetUnitX/Y (which ignore pathing).
oops
didnt see
 
Level 22
Joined
Dec 31, 2006
Messages
2,216
never happened to me
do you have any prove for this?
(I don't want to accuse you of saying wrong stuff; I'm just curious :grin:)
Well, apparenly a unit won't stop immediately after it's done. If you had used 1 global unit you would need to issue a stop order. If you're calling the function many times (if you're f.ex, looping through a bunch of destructables to check which ones are trees) then the unit cannot start the harvest order, stop and then start again fast enough. At least this is what happened when I tried to have 1 global unit cast some instant spells. I haven't really tried with the harvest order though, but I'm guessing it's similar to other orders.
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
Well, apparenly a unit won't stop immediately after it's done. If you had used 1 global unit you would need to issue a stop order. If you're calling the function many times (if you're f.ex, looping through a bunch of destructables to check which ones are trees) then the unit cannot start the harvest order, stop and then start again fast enough. At least this is what happened when I tried to have 1 global unit cast some instant spells. I haven't really tried with the harvest order though, but I'm guessing it's similar to other orders.

well when I checked this with a map full of trees it worked just fine but there might be some special case where it will not

for spells:
I guess you set casting point/backswing point and so on to 0 but you could try if items are fast enough
 
Level 15
Joined
Jul 6, 2009
Messages
889
How to use:
1) Copy this whole library and paste into your map
2) Copy the spell data if you want
3) Modify the fields to your liking

1) It's a library? I thought it was trigger file or something like that :X
2) If I want? So the spell data (object data?) is not necessary, I should only copy it if I want to?

DIST_PER_SEC, KB_TIME, DAMAGE_PER_LEVEL, and RADIUS should be in the form of configurable functions. Spell values shouldn't be made constant throughout levels, but possible to change.

---

I'm not sure how the approval comment can say "The triggering looks good."; when I found all these.

You could make some effort to present the code of the spell more neatly. I'm really discouraged to post feedback / criticism on messy coded things.

- Why not just utilise the create method that structs offer?

- Why is Leap not private? You can fix use a keyword for data.

- call UnitDamageTarget(DU, GetFilterUnit(), DAMAGE_PER_LEVEL*abillevel, false, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, null). ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, and null (for completeness) should be adjustable globals.

- GetFilterUnit is called seven times in that method, I'm sure the answer is obvious.

- You should store the amount of damage the instance does as a struct member, rather than making the calculation each time a unit is hit.

- Rather than having DU, OWNER, TEMP_GROUP_E, and abillevel variables, why not just have one integer called "CurrentInstance" or something, that way you can access OWNER using CurrentInstance.owner.

- You can use delimited comments to make the if statements in static method check more readable.

JASS:
        static method check takes nothing returns boolean
			local unit filt = GetFilterUnit()
			if 	IsUnitEnemy( filt, tempData.owner ) 					and  /* Enemy
			*/	GetWidgetLife( filt ) > 0.405							and  /* Living
			*/	IsUnitInGroup( filt, tempData.damageGroup )	== false	then // Not damaged
				
				call UnitDamageTarget( tempData.caster, filt, tempData.damage, blah )
				call Knockback()
				
			endif
			
			set filt = null
			return false
        endmethod

- Why is TEMP_GROUP_E initialised as CreateGroup? It leaks.

- Although the source caster unit could change, I still think it's better to set the owner of the caster to a struct member, instead of setting DU to the owner every timer tick.

- set data.cyclone = AddSpecialEffectTarget(CYCLONE, data.caster, "origin"). "origin" should be configurable.

- There's probably a whole lot more, but blah. CBF.
 
well, this spell was made because of a REQUEST so I made it by the specifications of the request that's why some things are constant...

I might add those things if I ever have the motivation too... maybe later...

about the library thing... well yeah, it just happened that I only used to upload libraries...

anyway, I probably wont do anything about the delimited comments... I'm not used to using them...

and, oh... I saved the damage but never used it...

2) yes, the object data isnt needed... its just a dummy spell... anyone can just create it...
 
Last edited:
Level 15
Joined
Jul 6, 2009
Messages
889
Well, the point was to improve the implementation steps, although I myself don't even bother adding them. Anyways. That's not a biggie.

Well, the spell no longer becomes a request when uploaded here, it becomes a submission, and should follow all these unset, always changing, non-existent standards :O

I also think the spell would look better if the hero was riding on the tornado instead :O
 
Well, the point was to improve the implementation steps, although I myself don't even bother adding them. Anyways. That's not a biggie.

Well, the spell no longer becomes a request when uploaded here, it becomes a submission, and should follow all unset standards ;P

I also think the spell would look better if the hero was riding on the tornado instead :O

updated!...

I was thinking bout that from the start (well about making the tornado on the ground)... but that was the specification and I wont change it probably...

or maybe I could just replicate the scope and modify it, making a second version of the spell...
 
Top