Spell Path of Shadows

Kenny

Back for now.
Reaction score
202
Path of Shadows
BTNPathOfShadows.jpg
by kenny!
v11

General Information:

This is a really fun spell that i just had to make and share with everyone. It may look a little basic, but the overall application of a skill like this is limitless. It took a while to get all the kinks worked out, but i think i finally got most of it down packed. So yeah, download the map, give it a try, as the screenshots will not do it justice. I left many options configurable so you can muck around and do almost anything with it. Have fun!​

Requires:

  • GTrigger by Jesus4Lyf.
  • JassHelper 0.9.G.0 or higher (Can be found in the latest NewGen pack).

Description:

Gives the hero the ability to move rapidly to the target point or unit of an order it receives.

Level 1 - Moves at a rapid pace and has a maximum distance of 600. Also deals 40 damage to units who are hit by the hero. Has a 14 second cooldown.
Spell will not trigger if the target point or unit is within 200 distance.

Level 2 - Moves at a very rapid pace and has a maximum distance of 800. Also deals 80 damage to units who are hit by the hero. Has a 12 second cooldown.
Spell will not trigger if the target point or unit is within 300 distance.

Level 3 - Moves at an extremely rapid pace and has a maximum distance of 1000. Also deals 120 damage to units who are hit by the hero. Has a 10 second cooldown.
Spell will not trigger if the target point or unit is within 400 distance.

Note: The cooldown for this ability has been changed to 2 seconds for testing purposes.

Technical Details:

  • When a unit is given an order it will 'dash' to the target location/unit of that order.
  • Orders include: "move", "attack" and "smart".
  • Movement speed, timescale, cooldown, maximum distance, minimum distance, mana cost and damage are all configurable in the trigger.
  • Requires three (3) abilities from the object editor to use.
  • It is MUI/MPI and all that.
  • It is leakless and lagless and quite efficient.
  • Importing difficulty is probably around easy/medium just because of the 3 abilities needed.
  • It is pretty awesome.

Implementation:

JASS:


//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  To Implement:                                                                     \\
//                                                                                    \\
//    - THIS VERSION REQUIRES GTrigger by Jesus4Lyf. Either copy the trigger labelled \\
//      GT or create a new trigger in your map, convert it to custom script and paste \\
//      the script for GTrigger into it.                                              \\
//                                                                                    \\
//    - Copy the PathofShadows trigger below this, or make a new trigger in your map  \\
//      and name it PathofShadows, then copy the script from this map to you own.     \\
//                                                                                    \\
//    - Copy the 3 abilities needed for this skill from this map to yours.            \\
//      IT IS HIGHLY RECOMMENDED THAT YOU DO THIS AS THEY ARE ALREADY SET UP FOR YOU. \\
//                                                                                    \\
//    Tip: The abilities in the object editor have their usage as an editor suffix    \\
//         so that you can easily see which ability needs to be under which raw code  \\
//         in the trigger. Example: Path of Shadows (READY_ID) relates to READY_ID,   \\
//         and Path of Shadows (COOLDOWN_ID) relates to COOLDOWN_ID in the trigger.   \\
//                                                                                    \\
//    - Once you have copied the abilities, change the raw codes in the PathofShadows \\
//      trigger to the ones in your map, then change any configurables you want.      \\
//                                                                                    \\
//    - Add the ability to a unit and enjoy <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />.                                       \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\

//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  Credits:                                                                          \\
//                                                                                    \\
//    - Not really sure who did the icon i have used for PathOfShadows, but i do not  \\
//      take credit for the original icon. I did, however, make the passive one, but  \\
//      credit for both icons should go to the original maker, whoever he/she is.     \\
//                                                                                    \\
//    - Credits to Vexorian for original Check Pathability idea, and to Anitarf for   \\
//      the up-to-date version of it.                                                 \\
//                                                                                    \\
//    - Credits to Vexorian for SimError.                                             \\
//                                                                                    \\
//    - Credits to Tinki3 for the test map.                                           \\
//                                                                                    \\
//    - Credits to Jesus4Lyf for GTrigger. It is awesome.                             \\
//                                                                                    \\
//    - And thanks to anyone who helped with this spell.                              \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\

Config Descriptions:

JASS:


//------------------------------------------------------------------------------------\\
//                                                                                    \\
//  Config Descriptions:                                                              \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                              \\
//    Below is a description of all the constant globals and functions used in Path   \\
//    of Shadows.                                                                     \\
//                                                                                    \\
//  GLOBALS:                                                                          \\
// ¯¯¯¯¯¯¯¯¯                                                                          \\
//  Integers and Reals:                                                               \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                               \\
//    - LEARN_ID      - Raw code of the hero ability &#039;Path of Shadows&#039; - Used for     \\
//                      learning the ability.                                         \\
//                                                                                    \\
//    - READY_ID      - Raw code of the Initial dummy ability - This is the main      \\
//                      ability used by the spell.                                    \\
//                                                                                    \\
//    - COOLDOWN_ID   - Raw code of the extra dummy ability added when the unit uses  \\ 
//                      the &#039;dash&#039;.                                                   \\
//                                                                                    \\
//    - ANIM_INDEX    - Animation of the unit by index - it is best to use the        \\
//                      walking animation.                                            \\
//                                                                                    \\
//    - RED           - Red tint given to unit while &#039;dashing&#039;. 255 is max.           \\
//                                                                                    \\
//    - GREEN         - Green tint given to unit while &#039;dashing&#039;. 255 is max.         \\
//                                                                                    \\
//    - BLUE          - Blue tint given to unit while &#039;dashing&#039;. 255 is max.          \\
//                                                                                    \\
//    - ALPHA         - Alpha given to the unit while &#039;dashing&#039;. Alpha = transparency.\\
//                      255 is max.                                                   \\
//                                                                                    \\
//    - INTERVAL      - Interval given for the repeating timer. 0.04 is highly        \\
//                      recommended. If you need a higher frequency 0.02 is okay.     \\
//                      It is far easier to get an accurate distance if the interval  \\
//                      can be easily multiplied into both max dist and move speed.   \\
//                                                                                    \\
//    - DISTANCE      - Distance in front of the unit to check pathability.           \\
//                      Does not need to be changed.                                  \\
//                                                                                    \\
//    - COLLISION     - The distance away from the hero a unit needs to be to get     \\
//                      damaged.                                                      \\
//                                                                                    \\
//  Special Effects:                                                                  \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                                  \\
//    -  PLAY_DASH_SFX    - Boolean to decide whether or not to play the dash sfx at  \\
//                          the start and end of the &#039;dash&#039;.                          \\
//                                                                                    \\
//    - DASH_SFX          - Special effect played at the start and end of the &#039;dash&#039;. \\
//                                                                                    \\
//    - PLAY_COOLDOWN_SFX - Boolean to decide whether or not to play the Cooldown_sfx \\
//                          when the cooldown is finished.                            \\
//                                                                                    \\
//    - COOLDOWN_SFX      - Special effect played to signal the cooldown is over.     \\
//                                                                                    \\
//    - COOLDOWN_POINT    - Attachment point of the Cooldown_sfx attached to the unit \\
//                          when the cooldown is over.                                \\
//                                                                                    \\
//    - PLAY_DAMAGE_SFX   - Boolean to decide whether or not to play the Damage_sfx   \\
//                          when a unit is damaged.                                   \\
//                                                                                    \\
//    - DAMAGE_SFX        - Special effect played when a unit gets damaged.           \\
//                                                                                    \\
//    - DAMAGE_POINT      - Attachment point of the Damage_sfx attached to a unit     \\
//                          when it is damaged.                                       \\
//                                                                                    \\
//  Error Message:                                                                    \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                                    \\
//    - ERROR_MSG          - Error message for when you do not have enough mana.      \\
//                                                                                    \\
//  Booleans:                                                                         \\
// ¯¯¯¯¯¯¯¯¯¯                                                                         \\
//    - ALLOW_PRELOAD      - Whether or not to allow preloading of special effects.   \\
//                                                                                    \\
//    - ALLOW_PATHING      - Whether or not to check for pathable terrain.            \\
//                                                                                    \\
//    - ALLOW_MANA_COST    - Whether or not to allow the mana cost.                   \\
//                                                                                    \\
//    - ALLOW_DAMAGE       - Whether or not to damage units in the way.               \\
//                                                                                    \\
//    - ALLOW_ERROR_MSG    - Whether or not to show the error message if you have no  \\
//                           mana.                                                    \\
//                                                                                    \\
//    - ALLOW_PAUSING      - Whether or not to pause the shadow trail images. Some    \\
//                           may like the effect when they are unpaused, others may   \\
//                           not.                                                     \\
//                                                                                    \\
//    - ALLOW_ORDER_MOVE   - Whether or not the &quot;move&quot; order will set off the dash.   \\
//                                                                                    \\
//    - ALLOW_ORDER_SMART  - Whether or not the &quot;smart&quot; order will set off the dash.  \\
//                                                                                    \\
//    - ALLOW_ORDER_ATTACK - Whether or not the &quot;attack&quot; order will set off the dash. \\
//                                                                                    \\
//  Shadow Trail Variables:                                                           \\
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                           \\
//    - OFFSET             - Distance behind the caster at which images are created.  \\
//                                                                                    \\
//    - ALPHA_START        - Starting alpha value of the images created.              \\
//                                                                                    \\
//    - ALPHA_MINUS        - How much ALPHA_START is decreased by every INTERVAL.     \\
//                                                                                    \\
//    - LOCUST_ID          - Raw code of the locust ability.                          \\
//                                                                                    \\
//    - CROW_ID            - Raw code of the crow form ability.                       \\
//                                                                                    \\  
//  FUNCTIONS:                                                                        \\
// ¯¯¯¯¯¯¯¯¯¯¯                                                                        \\
//    - Max_dist       - Maximum distance of the dash effect.                         \\
//                                                                                    \\
//    - Min_dist       - If the distance between the two points (unit and target      \\
//                       point) is less then this, then the spell will not trigger.   \\
//                       Set to 0.00 if you do not want a minimum distance.           \\
//                                                                                    \\
//    - Movement_speed - Movement speed of the unit (In units/second).                \\
//                       Currently: 1.5, 2.0, 2.5 times max movespeed in a map (522). \\
//                                                                                    \\
//    - Time_scale     - Timescale of the unit/level (makes the unit look like its    \\
//                       running fast).                                               \\
//                                                                                    \\
//    - Cool_down      - Cooldown of the ability. If this is set to zero (0.00) than  \\
//                       the spell will automatically change the cooldown to the      \\
//                       lowest possible cooldown that will not result in an error.   \\
//                       This is usually Distance / Speed.                            \\
//                                                                                    \\
//    - Mana_cost      - Mana cost of the ability, if you want it to cost mana.       \\
//                                                                                    \\
//    - Damage         - Damage dealt to units hit, per level.                        \\
//                                                                                    \\
//    - Enemy_filt     - Used to filter out units that should be hit. Checking for    \\
//                       enemies is done internally.                                  \\
//                                                                                    \\
//------------------------------------------------------------------------------------\\

Script:

JASS:


//------------------------------------------------------------------------------------\\
//                             Path of Shadows [v11]                                  \\
//                                   By kenny!                                        \\
//                            Constructed using vJASS                                 \\
//                         Requires NewGen WE &amp; GTrigger                              \\
//------------------------------------------------------------------------------------\\

scope PathofShadows initializer Init

    globals
    // Configurables:

    // Integers and Reals:
        private constant integer LEARN_ID           = &#039;A000&#039;
        private constant integer READY_ID           = &#039;A001&#039;
        private constant integer COOLDOWN_ID        = &#039;A002&#039;
        private constant integer ANIM_INDEX         = 7
        private constant integer RED                = 128
        private constant integer GREEN              = 128
        private constant integer BLUE               = 128
        private constant integer ALPHA              = 250
        private constant real    INTERVAL           = 0.04 // Or 0.02
        private constant real    DISTANCE           = 50.00
        private constant real    COLLISION          = 100.00

    // Special Effects:
        private constant boolean PLAY_DASH_SFX      = true
        private constant string  DASH_SFX           = &quot;Abilities\\Spells\\Undead\\CarrionSwarm\\CarrionSwarmDamage.mdl&quot;

        private constant boolean PLAY_COOLDOWN_SFX  = true
        private constant string  COOLDOWN_SFX       = &quot;Abilities\\Spells\\Undead\\CarrionSwarm\\CarrionSwarmDamage.mdl&quot;
        private constant string  COOLDOWN_POINT     = &quot;overhead&quot;

        private constant boolean PLAY_DAMAGE_SFX    = true
        private constant string  DAMAGE_SFX         = &quot;Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayTarget.mdl&quot;
        private constant string  DAMAGE_POINT       = &quot;head&quot;
        
    // Error Messsage:
        private constant string  ERROR_MSG          = &quot;Your hero does not have enough mana to walk the path of shadows.&quot;
        
    // Booleans:
        private constant boolean ALLOW_PRELOAD      = true
        private constant boolean ALLOW_PATHING      = true
        private constant boolean ALLOW_MANA_COST    = false   
        private constant boolean ALLOW_DAMAGE       = true
        private constant boolean ALLOW_ERROR_MSG    = true
        private constant boolean ALLOW_SHADOWS      = true
        private constant boolean ALLOW_PAUSING      = false
        
        private constant boolean ALLOW_ORDER_MOVE   = true
        private constant boolean ALLOW_ORDER_SMART  = true
        private constant boolean ALLOW_ORDER_ATTACK = true
        
    // Attack/Damage/Weapon types of damage dealt:
        private constant attacktype A_TYPE          = ATTACK_TYPE_CHAOS
        private constant damagetype D_TYPE          = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype W_TYPE          = WEAPON_TYPE_WHOKNOWS
        
    // Variables for the shadow trail:
        private constant real    OFFSET             = 20.00
        private constant integer ALPHA_START        = 250
        private constant integer ALPHA_MINUS        = 50
        private constant integer LOCUST_ID          = &#039;Aloc&#039;
        private constant integer CROW_ID            = &#039;Amrf&#039;
    endglobals
 
    //---------------------------------------------------------------------\\
    private function Max_dist takes integer lvl returns real
        return 400.00 + (200.00 * lvl)
    endfunction

    private function Min_dist takes integer lvl returns real
        return 100.00 + (100.00 * lvl)
    endfunction                  

    private function Movement_speed takes integer lvl returns real
        return 1000.00 + (500.00 * lvl)
    endfunction                  

    private function Time_scale takes integer lvl returns real
        return 1.00 + (1.00 * lvl)
    endfunction

    private function Cool_down takes integer lvl returns real
        return 2.00 + (0.00 * lvl)
    endfunction

    private function Damage_dealt takes integer lvl returns real
        return 40.00 * lvl
    endfunction

    private function Mana_cost takes integer lvl returns integer
        return 20 + (20 * lvl)
    endfunction

    private function Enemy_filt takes nothing returns boolean
        return GetWidgetLife(GetFilterUnit()) &gt; 0.405 and IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) == false and GetUnitFlyHeight(GetFilterUnit()) &lt; 40.00
    endfunction

    //---------------------------------------------------------------------\\
    //                                                                     \\
    //  DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!  \\
    //                                                                     \\
    //---------------------------------------------------------------------\\

    globals
    // Needed non-constant globals:
        private item     array H1                // Needed for hidding necessary items.
        private integer  H1_total       = 0      // Needed for counting hidden items.
        private real     Game_maxX      = 0.00   // Used to set up map bounds.
        private real     Game_minX      = 0.00   // Used to set up map bounds.
        private real     Game_maxY      = 0.00   // Used to set up map bounds.
        private real     Game_minY      = 0.00   // Used to set up map bounds.
        private item     Path_item      = null   // Item needed for pathing detection.
        private rect     Path_rect      = null   // Rect needed for pathing detection.
        private sound    Error_sound    = null   // Error sound used for error message.
        
    // Needed Constant Globals:
        private constant real    MAX_RANGE    = 10.00  // Needed for checking pathing.
        private constant integer ORDER_STOP   = 851972 // Order ID of the command &quot;stop&quot;.
        private constant integer ORDER_MOVE   = 851986 // Order ID of the command &quot;move&quot;.
        private constant integer ORDER_SMART  = 851971 // Order ID of the command &quot;Smart&quot;.
        private constant integer ORDER_ATTACK = 851983 // Order ID of the command &quot;attack&quot;.
    endglobals

    //---------------------------------------------------------------------\\
    private function Safe_x takes real x returns real
        if x &lt; Game_minX then
            return Game_minX
        elseif x &gt; Game_maxX then
            return Game_maxX
        endif
        return x
    endfunction

    private function Safe_y takes real y returns real
        if y &lt; Game_minY then
            return Game_minY
        elseif y &gt; Game_maxY then 
            return Game_maxY
        endif
        return y
    endfunction

    //---------------------------------------------------------------------\\
    private function Filter_items takes nothing returns nothing
        if IsItemVisible(GetEnumItem()) then
            set H1[H1_total] = GetEnumItem()
            call SetItemVisible(H1[H1_total],false)
            set H1_total = H1_total + 1
        endif
    endfunction

    private function Check_pathability takes real x1, real y1 returns boolean
        local real x2 = 0.00
        local real y2 = 0.00
        
        call MoveRectTo(Path_rect,x1,y1)
        call EnumItemsInRect(Path_rect,null,function Filter_items)

        call SetItemPosition(Path_item,x1,y1)
        set x2 = GetItemX(Path_item)
        set y2 = GetItemY(Path_item)
        call SetItemVisible(Path_item,false)

        loop
            exitwhen H1_total &lt;= 0
            set H1_total = H1_total - 1
            call SetItemVisible(H1[H1_total],true)
            set H1[H1_total] = null
        endloop

        return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) &lt; MAX_RANGE * MAX_RANGE
    endfunction

    //---------------------------------------------------------------------\\
    private function Sim_error takes player whichplayer, string message returns nothing
        set message = &quot;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00&quot; + message + &quot;|r&quot;
        if (GetLocalPlayer() == whichplayer) then
            call ClearTextMessages()
            call DisplayTimedTextToPlayer(whichplayer,0.5125,0.97120,2.00,message)
            call StartSound(Error_sound)
        endif
    endfunction
    
    //---------------------------------------------------------------------\\
    private struct Shadow

        unit    shadow = null
        integer alpha  = 0
        
        static thistype array S
        static integer  Total = 0
        static timer    Timer = null
        
        static method create takes unit caster, integer id, real angle, real timescale, real x, real y returns thistype
            local thistype s = thistype.allocate()

            set s.alpha = ALPHA_START
            set s.shadow = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),id,x,y,GetUnitFacing(caster))
            
            call SetUnitColor(s.shadow,GetPlayerColor(GetOwningPlayer(caster)))
            call SetUnitVertexColor(s.shadow,RED,GREEN,BLUE,s.alpha)
            call SetUnitAnimationByIndex(s.shadow,ANIM_INDEX)
            call UnitAddAbility(s.shadow,LOCUST_ID)
            call SetUnitPathing(s.shadow,false)
            
            if ALLOW_PAUSING then
                call SetUnitTimeScale(s.shadow,0.00)
                call PauseUnit(s.shadow,true)
            else
                call SetUnitTimeScale(s.shadow,timescale)
            endif
            
            call SetUnitX(s.shadow,x + OFFSET * Cos(angle))
            call SetUnitY(s.shadow,y + OFFSET * Sin(angle))
            
            if GetUnitFlyHeight(caster) &gt; 1.00 then
                call UnitAddAbility(s.shadow,CROW_ID)
                call UnitRemoveAbility(s.shadow,CROW_ID)
                call SetUnitFlyHeight(s.shadow,GetUnitFlyHeight(caster),0.00)
            endif
            
            if .Total == 0 then
                call TimerStart(.Timer,INTERVAL,true,function thistype.update)
            endif
            set .S[.Total] = s
            set .Total = .Total + 1

            return s
        endmethod
        
        static method update takes nothing returns nothing
            local thistype s = 0
            local integer  i = 0
            
            loop
                exitwhen i &gt;= .Total
            
                set s = .S<i>
                
                if s.alpha &lt;= 0 then
                    call s.destroy()
                    set .Total = .Total - 1
                    set .S<i> = .S[.Total]
                    set i = i - 1
                else        
                    call SetUnitVertexColor(s.shadow,RED,GREEN,BLUE,s.alpha)
                    call SetUnitAnimationByIndex(s.shadow,ANIM_INDEX)
                    
                    set s.alpha = s.alpha - ALPHA_MINUS
                endif
                
                set i = i + 1
            endloop
            
            if .Total == 0 then
                call PauseTimer(.Timer)
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            call RemoveUnit(.shadow)
            set .shadow = null
        endmethod
        
        static method onInit takes nothing returns nothing
            set .Timer = CreateTimer()
        endmethod
        
    endstruct

    //---------------------------------------------------------------------\\
    private struct Movement

        unit    cast   = null
        widget  t      = null
        group   g      = null
        real    cx     = 0.00
        real    cy     = 0.00
        real    tx     = 0.00
        real    ty     = 0.00
        real    move   = 0.00
        real    dist   = 0.00
        real    cos    = 0.00
        real    sin    = 0.00
        integer ticks  = 0
        integer lvl    = 0
        integer id     = 0
        
        static thistype array M
        static integer  Total = 0
        static timer    Timer = null
        static group    Group = null
        static boolexpr Filt  = null
        
        method resetPath takes real distance, real castx, real casty, real targx, real targy, widget targ, integer order returns nothing
            local real angle = Atan2((targy - casty),(targx - castx))
            
            if distance &lt; .dist then
                set .ticks = R2I(distance / .move)
            endif
            
            set .cos = Cos(angle)
            set .sin = Sin(angle)
            set .tx  = targx
            set .ty  = targy
            set .t   = targ
            set .id  = order
                
            call SetUnitFacing(.cast,angle * bj_RADTODEG)
        endmethod 
        
        static method isDashing takes unit whichUnit returns thistype
            local integer i = 0
            
            loop
                exitwhen i &gt;= .Total
                if .M<i>.cast == whichUnit then
                    return .M<i>
                endif
                set i = i + 1
            endloop
        
            return -1
        endmethod
        
        static method create takes unit caster, real distance, real castx, real casty, real targx, real targy, widget targ, integer order returns thistype
            local thistype m     = thistype.allocate()
            local real     angle = 0.00
            
            set m.cast = caster
            set m.cx   = castx
            set m.cy   = casty
            set m.tx   = targx
            set m.ty   = targy
            set m.lvl  = GetUnitAbilityLevel(m.cast,COOLDOWN_ID)
            set m.move = Movement_speed(m.lvl) * INTERVAL
            set m.dist = distance
            set angle  = Atan2((m.ty - m.cy),(m.tx - m.cx))
            set m.cos  = Cos(angle)
            set m.sin  = Sin(angle)
            set m.t    = targ
            set m.id   = order
            
            if m.dist &gt; Max_dist(m.lvl) then
                set m.dist = Max_dist(m.lvl)
            endif
            
            set m.ticks = R2I(m.dist / m.move)
        
            call SetUnitPathing(m.cast,false)
            call SetUnitFacing(m.cast,angle * bj_RADTODEG)
            call SetUnitTimeScale(m.cast,Time_scale(m.lvl))
            call SetUnitVertexColor(m.cast,RED,GREEN,BLUE,ALPHA)
            
            if ALLOW_DAMAGE then
                set m.g = CreateGroup()
            endif
            
            if PLAY_DASH_SFX then
                call DestroyEffect(AddSpecialEffect(DASH_SFX,m.cx,m.cy))
            endif
        
            if .Total == 0 then
                call TimerStart(.Timer,INTERVAL,true,function thistype.update)
            endif
            set .M[.Total] = m
            set .Total = .Total + 1
            
            return m
        endmethod
        
        static method update takes nothing returns nothing
            local thistype m = 0
            local integer  i = 0
            local integer  j = 0
            local real     x = 0.00
            local real     y = 0.00
            local unit     u = null
             
            loop
                exitwhen i &gt;= .Total
                
                set m = .M<i>
                
                set x = GetUnitX(m.cast)
                set y = GetUnitY(m.cast)
                
                if ALLOW_PATHING then
                    if Check_pathability(x + DISTANCE * m.cos,y + DISTANCE * m.sin) == false then
                        set j = 1
                    endif
                endif
            
                if m.ticks &lt;= 0 or j == 1 or GetWidgetLife(m.cast) &lt; 0.406 then
                    call m.destroy()
                    set .Total = .Total - 1
                    set .M<i> = .M[.Total]
                    set i = i - 1
                    set j = 0
                else
                    set x = Safe_x(x + m.move * m.cos)
                    set y = Safe_y(y + m.move * m.sin)
                    
                    call SetUnitX(m.cast,x)
                    call SetUnitY(m.cast,y)
                    call SetUnitAnimationByIndex(m.cast,ANIM_INDEX)
                    call IssueImmediateOrderById(m.cast,ORDER_STOP)
                    
                    if ALLOW_SHADOWS then
                        call Shadow.create(m.cast,GetUnitTypeId(m.cast),Atan2((m.cy - y),(m.cx - x)),Time_scale(m.lvl),x,y)
                    endif
                    
                    if ALLOW_DAMAGE then
                        call GroupEnumUnitsInRange(.Group,x,y,COLLISION,.Filt)
                        loop
                            set u = FirstOfGroup(.Group)
                            exitwhen u == null
                            call GroupRemoveUnit(.Group,u)
                            if not(IsUnitInGroup(u,m.g)) and IsUnitEnemy(u,GetOwningPlayer(m.cast)) == true then
                                call GroupAddUnit(m.g,u)
                                call UnitDamageTarget(m.cast,u,Damage_dealt(m.lvl),false,false,A_TYPE,D_TYPE,W_TYPE)
                                if PLAY_DAMAGE_SFX then
                                    call DestroyEffect(AddSpecialEffectTarget(DAMAGE_SFX,u,DAMAGE_POINT))
                                endif
                            endif
                        endloop
                    endif
                    
                    set m.cx = x
                    set m.cy = y
                    
                    set m.dist  = m.dist - m.move
                    set m.ticks = m.ticks - 1
                endif
                
                set i = i + 1
            endloop
            
            if .Total == 0 then
                call PauseTimer(.Timer)
            endif
            
            set u = null
        endmethod
        
        method onDestroy takes nothing returns nothing
            call SetUnitPathing(.cast,true)
            call SetUnitTimeScale(.cast,1.00)
            call SetUnitAnimation(.cast,&quot;stand&quot;)
            call SetUnitVertexColor(.cast,255,255,255,255)
            
            if .t != null then
                call IssueTargetOrderById(.cast,.id,.t)
            else
                set .tx = .tx + 10.00 * .cos
                set .ty = .ty + 10.00 * .sin
                call IssuePointOrderById(.cast,.id,.tx,.ty)
            endif
            
            if PLAY_DASH_SFX then
                call DestroyEffect(AddSpecialEffect(DASH_SFX,.cx,.cy))
            endif
            
            if ALLOW_DAMAGE then
                call GroupClear(.g)
                call DestroyGroup(.g)
            endif
            
            set .g    = null   
            set .t    = null
            set .cast = null
        endmethod
        
        static method onInit takes nothing returns nothing
            set .Timer = CreateTimer()
            set .Group = CreateGroup()
            set .Filt  = Filter(function Enemy_filt)
        endmethod
        
    endstruct

    //---------------------------------------------------------------------\\
    private struct Cooldown

        unit cast   = null
        real time   = 0.00
    
        static thistype array C
        static integer  Total = 0
        static timer    Timer = null
        
        static method create takes nothing returns thistype
            local thistype c   = thistype.allocate()
            local integer  lvl = 0
            
            set c.cast = GetTriggerUnit()
            set lvl    = GetUnitAbilityLevel(c.cast,READY_ID)
            set c.time = Cool_down(lvl)
            
            call UnitRemoveAbility(c.cast,READY_ID)
            call UnitAddAbility(c.cast,COOLDOWN_ID)
            call SetUnitAbilityLevel(c.cast,COOLDOWN_ID,lvl)
            
            if .Total == 0 then
                call TimerStart(.Timer,INTERVAL,true,function thistype.update)
            endif
            set .C[.Total] = c
            set .Total = .Total + 1
        
            return c
        endmethod

        static method update takes nothing returns nothing
            local thistype c = 0
            local integer  i = 0
            
            loop
                exitwhen i &gt;= .Total
                
                set c = .C<i>
                
                if c.time &lt;= 0.00 or GetWidgetLife(c.cast) &lt; 0.406 then
                    call c.destroy()
                    set .Total = .Total - 1
                    set .C<i> = .C[.Total]
                    set i = i - 1
                else
                    set c.time = c.time - INTERVAL // Unfortunately needed to, incase caster died.
                endif
                
                set i = i + 1
            endloop
            
            if .Total == 0 then
                call PauseTimer(.Timer)
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            local string  sfx = &quot;&quot;
            local integer lvl = GetUnitAbilityLevel(.cast,COOLDOWN_ID)
        
            call UnitRemoveAbility(.cast,COOLDOWN_ID)
            call UnitAddAbility(.cast,READY_ID)
            call SetUnitAbilityLevel(.cast,READY_ID,lvl)
                
            if PLAY_COOLDOWN_SFX then
                set sfx = COOLDOWN_SFX
                
                if GetLocalPlayer() != GetOwningPlayer(.cast) then
                    set sfx = &quot;&quot;
                endif
                
                call DestroyEffect(AddSpecialEffectTarget(sfx,.cast,COOLDOWN_POINT))
            endif
            
            set .cast = null
        endmethod
        
        static method onInit takes nothing returns nothing
            set .Timer = CreateTimer()
        endmethod
        
    endstruct

    //---------------------------------------------------------------------\\
    private function Move_actions takes nothing returns boolean
        local unit     cast  = GetTriggerUnit()
        local widget   targ  = GetOrderTarget()
        local real     castx = GetUnitX(cast)
        local real     casty = GetUnitY(cast)        
        local real     targx = 0.00
        local real     targy = 0.00
        local real     dist  = 0.00
        local integer  lvl   = GetUnitAbilityLevel(cast,READY_ID)
        local integer  id    = GetIssuedOrderId()
        local Movement m     = Movement.isDashing(cast)
            
        if lvl &gt; 0 or GetUnitAbilityLevel(cast,COOLDOWN_ID) &gt; 0 then
            if targ != null then
                set targx = GetWidgetX(targ)
                set targy = GetWidgetY(targ)
            else
                set targx = GetOrderPointX()
                set targy = GetOrderPointY()
            endif

            set dist = SquareRoot((targx - castx) * (targx - castx) + (targy - casty) * (targy - casty))
            
            if m == -1 and lvl &gt; 0 then
                if dist &gt; Min_dist(lvl) then
                    if ALLOW_MANA_COST then
                        if GetUnitState(cast,UNIT_STATE_MANA) &gt;= Mana_cost(lvl) then
                            call SetUnitState(cast,UNIT_STATE_MANA,GetUnitState(cast,UNIT_STATE_MANA) - Mana_cost(lvl))
                            call Cooldown.create()
                            call Movement.create(cast,dist,castx,casty,targx,targy,targ,id)
                        else
                            if ALLOW_ERROR_MSG then
                                call Sim_error(GetOwningPlayer(cast),ERROR_MSG)
                            endif
                        endif
                    else
                        call Cooldown.create()
                        call Movement.create(cast,dist,castx,casty,targx,targy,targ,id)
                    endif
                endif
            else
                call m.resetPath(dist,castx,casty,targx,targy,targ,id)
            endif
        endif
        
        set cast = null
        set targ = null
        
        return false
    endfunction

    //---------------------------------------------------------------------\\
    private function Learn_actions takes nothing returns boolean
        local unit cast = GetLearningUnit()
        
        if GetUnitAbilityLevel(cast,LEARN_ID) == 1 then
            call UnitAddAbility(cast,READY_ID)
        else
            if GetUnitAbilityLevel(cast,READY_ID) &gt; 0 then
                call IncUnitAbilityLevel(cast,READY_ID)
            elseif GetUnitAbilityLevel(cast,COOLDOWN_ID) &gt; 0 then
                call IncUnitAbilityLevel(cast,COOLDOWN_ID)
            endif
        endif
        
        set cast = null
        
        return false
    endfunction

    //---------------------------------------------------------------------\\
    private function Init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        
        set Game_maxX = GetRectMaxX(bj_mapInitialPlayableArea) - 64.00
        set Game_minX = GetRectMinX(bj_mapInitialPlayableArea) + 64.00
        set Game_maxY = GetRectMaxY(bj_mapInitialPlayableArea) - 64.00
        set Game_minY = GetRectMinY(bj_mapInitialPlayableArea) + 64.00
        
        if ALLOW_ORDER_MOVE then
            call GT_RegisterPointOrderEvent(trig,ORDER_MOVE)
            call GT_RegisterTargetOrderEvent(trig,ORDER_MOVE)
        endif
        if ALLOW_ORDER_SMART then
            call GT_RegisterPointOrderEvent(trig,ORDER_SMART)
            call GT_RegisterTargetOrderEvent(trig,ORDER_SMART)
        endif
        if ALLOW_ORDER_ATTACK then
            call GT_RegisterPointOrderEvent(trig,ORDER_ATTACK)
            call GT_RegisterTargetOrderEvent(trig,ORDER_ATTACK)
        endif

        call TriggerAddCondition(trig,Condition(function Move_actions))

        call GT_AddLearnsAbilityAction(function Learn_actions,LEARN_ID)

        if ALLOW_PRELOAD then
            call DestroyEffect(AddSpecialEffect(DASH_SFX,0.00,0.00))
            call DestroyEffect(AddSpecialEffect(COOLDOWN_SFX,0.00,0.00))
            call DestroyEffect(AddSpecialEffect(DAMAGE_SFX,0.00,0.00))
        endif

        set Error_sound = CreateSoundFromLabel(&quot;InterfaceError&quot;,false,false,false,10,10)
        set Path_rect   = Rect(0.00,0.00,128.00,128.00)
        set Path_item   = CreateItem(&#039;ciri&#039;,0.00,0.00)
        call SetItemVisible(Path_item,false) 
    endfunction

endscope

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


Screenshots:

They are pretty average, so it is much better to download the map to see its full effect.

Path of Shadows: Ready

PoSReady.jpg

Path of Shadows: Moving

PoSMoving.jpg

Path of Shadows: In cooldown

PoSIncooldown.jpg

Updates:

Previous updates can be seen in the below spoiler.

  • 23rd Dec - Initial release
  • 23rd Dec - Updated test map. Changed map description to correctly display spell name.
  • 26th Dec - Updated the code. Changed some really stupid mistakes by me. Made it a bit more efficient.
  • 18th Jan - Updated the spell to allow people to choose to check for pathable terrain using a modified version of Vexorians CheckPathablility function.
  • 14th Feb - Spell is now independent (Requires no systems). Changed a few things in the script, now works a bit better.
  • 5th March - Various spell updates and changes, including:
    • User can now specify whether or not the spell needs mana. And change the mana cost accordingly.
    • User can now specify whether or not to damage enemy units. And change the damage accordingly, as well as the area of effect.
    • User can specify a minimum distance, so that the spell will not trigger if you click within that distance.
    • Fixed cooldown issue, so that is a user attempts to put 0.00 as the cooldown, it will be set to the lowest possible cooldown without causing any problems (distance / speed).
    • Added a sim error if the hero does not have enough mana. User can choose to show it or not.
    • Minor coding updates and changes. Made things easier to read. Standardized most, if not all, function names and parameters.
  • 25th March - Few more spell and documentation updates, including:
    • Spell now uses single global group for GroupEnum function.
    • More script standardization.
    • Documentation for configurables section has been removed from spell script and added into another trigger. Makes it easier to read, and takes up less space within the important trigger.
    • Slight changes to how distance is calculated. Now works a bit better when targetting units (no "bounce" effect on most occasions).
    • Changed from Vexorians old Check Pathability function to Anitarf's more up-to-date version for Check Pathability. Credits to both of them for the ideas.
    • This is now version 7. Version 6 was an unreleased version due to the updates i found shortly after finishing version 6. Updates from version 6 (which are included in version 7) include:
      • Removed DistanceXY and AngleXY functions. Now calculates distance and angle directly. I hope Lord Panda is happy :p.
      • Few minor scripting adjustments.
      • More comments throughout the script, to explain what happens.
      • Path of Shadows now stops if the unit somehow dies during the spells effect.
  • 27th March - Fixed leveling up bug found by CraZyFroG. Version 8 is now out.
  • 11th April - Version 9 is out. Hopefully the last version of this spell. Update includes:
    • Changed the safety on the minimum cooldown to make sure no bugs will occur.
    • Found out that i wasn't pausing the second timer needed for the spell, it now pauses (can't believe no one saw this).
    • Ported the spell to use Jesus4Lyf's GTrigger Event System. Therefore you now need that. If you really don't want to import GTrigger, i have left a stand alone version for you to use. However, the one that uses GTrigger is better, so use it :p.
    • Few minor optimisations of things that were overlooked. Nothing life changing.
  • 23rd April - Version 10 released. THIS VERSION REQUIRES GTRIGGER, there is no stand-alone version anymore. Couple more changes made after reading through the script carefully. Updates include:
    • More minor optimizations.
    • Made the dash work when targeting items and destructables as well as units, not just units.
    • Got rid of the hack-job cooldown check to avoid double-frees. Replaced it with a small O(n) search (don't worry it is only fired once during the entire spell, at the start). This also means that the spell can now have a 0.00 second cooldown if you want.
    • More standardisation of global and function names.
    • Unit is now un-paused when the dash triggers, keeping its command card. I think this looks much better.
    • Unit is re-ordered its last order before the dash, when the dash finishes. This is to fix a very rare bug that can cause the unit to begin walking back to the start position for a split second.
    • Unit is constantly issued stop order to fix issues with unpausing the unit. This also helps with distance accuracy.
    • Added options to allow "move", "smart" and "attack" orders separately. Now the user can specify which orders they want to use, which can be very handy. This is mainly due to how much easier GTrigger made the triggering of this spell.
  • 29th June - Version 11 released. Updates include:
    • Minor optimisations to script, including removal of few unnecessary variables.
    • Added functionally to the spell. If a unit is currently dashing and a new order is given, the unit will re-direct its dash to the new target point/unit. The overall distance of the dash can not increase from the original distance if this is done.
    • Added a shadow trail to the spell. Thought it needed some eye-candy to get people interested.
    • Removed one of the effects available for change by end users.
    • Updated the struct stack to use the 0 index.
    • Renamed various variables and functions to make a bit more sense.
 

Attachments

  • [Spell] - Path of Shadows [v10].w3x
    109.9 KB · Views: 337
  • [Spell] - Path of Shadows [v11].w3x
    123.5 KB · Views: 366

Joker(Div)

Always Here..
Reaction score
86
Isn't there a way to give a cooldown to a passive ability? I'm pretty sure I've seen it in some maps. Unfortunately, I don't know how it's done.
 

Kenny

Back for now.
Reaction score
202
If you want to give a cooldown to a passive ability, im pretty sure the easiest way consists of having 3 different abilities, that are all the same (as in tooltips and such).

The first ability is a hero ability, which has be be an ability that does not show an icon, such as permanent invisibility or something. You give that ability the "learn" tooltip and make it do basically nothing. So when you learn it, there will be no ability where one should be.

Second ability is a passive unit ability, that has the information for each different level of the ability, it should also do nothing, unless you want it to. This ability is the one that gets checked in a trigger. If the unit has this ability, the trigger fires. If the trigger fires, then this ability is removed from the unit.

The second ability is then replaced with the third ability, which is basically the exact double of the second ability, except for the fact that it is a usable ability with a cooldown. Once it is place where the second ability was, you order the unit to use the ability, then the cooldown starts.

Then you wait X seconds, and remove the third ability and add the second one again.

Thats basically it, however, if your passive ability requires an order, such as mine, it becomes a hastle, as the third ability will change the units current order. But you can get around this. However using a usable ability, kinda ruins the whole effect of the passive skill in my opinion, because the first time i saw a skill like that, when the cooldown started, i clicked the spell and figured it wasnt passive anymore. Which ruined it for me.

I think the spell i made as a pretty decent way of showing the cooldown anyway. It has a status section in the tooltip and shows a special effect when the cooldown is over.

Anyone got any insight into this spell? Is everything ok and what-not.
 

Kenny

Back for now.
Reaction score
202
Bump.

Any thoughts?

JASS:
scope NovaCharge initializer Init

    globals
        private constant integer ABIL_ID           = &#039;A003&#039;
        private constant integer ANIM_INDEX        = 6
        private constant integer RED               = 150
        private constant integer GREEN             = 150
        private constant integer BLUE              = 150
        private constant integer ALPHA             = 150
        private constant integer MAX_SLASHES       = 8
        private constant real    INTERVAL          = 0.04
        private constant real    COLLISION_RADIUS  = 100.00
        private constant real    KNOCK_PERC        = 0.75
        
        private constant boolean PLAY_HIT_SFX      = true
        private constant string  HIT_SFX           = &quot;Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl&quot; 
        private constant boolean PLAY_CONSTANT_SFX = false
        private constant string  CONSTANT_SFX      = &quot;Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl&quot;
        private constant boolean PLAY_POINT_SFX    = true
        private constant string  POINT_SFX         = &quot;Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl&quot;
        private constant string  ATTACH_POINT      = &quot;chest&quot;
        
        private constant attacktype A_TYPE         = ATTACK_TYPE_CHAOS
        private constant damagetype D_TYPE         = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype W_TYPE         = WEAPON_TYPE_WHOKNOWS
    endglobals
    
    globals    
        private real Game_MaxX = 0.00
        private real Game_MinX = 0.00
        private real Game_MaxY = 0.00
        private real Game_MinY = 0.00
        private boolexpr TrueFilter = null
    endglobals

//=======================================================================
private function Movespeed takes integer lvl returns real
    return 1200.00+(400.00*lvl)
endfunction              

private function Timescale takes integer lvl returns real
    return 21.00+(1.00*lvl)
endfunction

private function Maxdist takes integer lvl returns real
    return 600.00+(200.00*lvl)
endfunction

private function Damage takes integer lvl returns real
    return 100.00
endfunction

private function Maxslashes takes integer lvl returns integer
    return 2+(2*lvl)
endfunction
 
private function FilterEnemies takes nothing returns boolean
	return GetWidgetLife(GetFilterUnit())&gt;0.405 and (IsUnitType(GetFilterUnit(),UNIT_TYPE_MECHANICAL)==false) and (IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE)==false) and GetUnitFlyHeight(GetFilterUnit())&lt;150.00
endfunction

//=======================================================================
private function SafeX takes real x returns real
    if x&lt;Game_MinX then
        return Game_MinX
    elseif x&gt;Game_MaxX then
        return Game_MaxX
    endif
    return x
endfunction

private function SafeY takes real y returns real
    if y&lt;Game_MinY then
        return Game_MinY
    elseif y&gt;Game_MaxY then
        return Game_MaxY
    endif
    return y
endfunction

//=======================================================================
private function AngleXY takes real x1, real y1, real x2, real y2 returns real
    return Atan2((y2-y1),(x2-x1))
endfunction

private function DistanceXY takes real x1, real y1, real x2, real y2 returns real
    return SquareRoot((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1))
endfunction

//=======================================================================
private struct Data
    timer t
    unit cast
    real castx
    real casty
    real array startx[MAX_SLASHES]
    real array starty[MAX_SLASHES]
    real array endx[MAX_SLASHES]
    real array endy[MAX_SLASHES]
    real angle
    real move
    real count
    integer lvl
    integer c = 0
    effect sfx
    group Group1 = CreateGroup()
    group Group2 = CreateGroup()
    boolean PoSon = false
    
    static method create takes nothing returns Data
        local Data d = Data.allocate()
        local real angle1
        local real angle2
        local integer i = 1
        
        set d.cast = GetTriggerUnit()
        set d.castx = GetUnitX(d.cast)
        set d.casty = GetUnitY(d.cast)
        if PLAY_POINT_SFX then
            call DestroyEffect(AddSpecialEffect(POINT_SFX,d.castx,d.casty))
        endif
        set d.lvl = GetUnitAbilityLevel(d.cast,ABIL_ID)
        
        loop  
            exitwhen i &gt; Maxslashes(d.lvl)
            set angle1 = (225.00 * i) - 180.00
            if angle1 &gt; 360.00 then
                loop
                    exitwhen angle1 &lt; 360.00
                    set angle1 = angle1 - 360.00
                endloop
            endif
            set d.startx[i-1] = d.castx + (Maxdist(d.lvl) / 2.00) * Cos(angle1*bj_DEGTORAD)
            set d.starty[i-1] = d.casty + (Maxdist(d.lvl) / 2.00) * Sin(angle1*bj_DEGTORAD)
            set angle2 = (225.00 * i)
            if angle2 &gt; 360.00 then
                loop
                    exitwhen angle2 &lt; 360.00
                    set angle2 = angle2 - 360.00
                endloop
            endif
            set d.endx[i-1] = d.castx + (Maxdist(d.lvl) / 2.00) * Cos(angle2*bj_DEGTORAD)
            set d.endy[i-1] = d.casty + (Maxdist(d.lvl) / 2.00) * Sin(angle2*bj_DEGTORAD)
            set i = i + 1
        endloop

        set d.move = Movespeed(d.lvl)*INTERVAL
        set d.count = 0.00
        
        if GetUnitAbilityLevel(d.cast,&#039;WOE2&#039;) &gt; 0 then
            set d.PoSon = true
        endif
        
        call PauseUnit(d.cast,true)
        call SetUnitPathing(d.cast,false)
        call SetUnitInvulnerable(d.cast,true)
        call SetUnitTimeScale(d.cast,Timescale(d.lvl))
        call SetUnitVertexColor(d.cast,RED,GREEN,BLUE,ALPHA)
        
        call SetUnitX(d.cast,SafeX(d.startx[0]))
        call SetUnitY(d.cast,SafeY(d.starty[0]))
        
        set d.t = NewTimer()
        call SetTimerData(d.t,d)
        call TimerStart(d.t,INTERVAL,true,function Data.Update)
        
        return d
    endmethod
    
    static method Update takes nothing returns boolean
        local Data d = GetTimerData(GetExpiredTimer())
        local real newx
        local real newy
        local real tmpx
        local real tmpy
        local real tmpdist
        local real tmpang
        local unit u
        
        if d.count == 0.00 then
            if PLAY_POINT_SFX then
                call DestroyEffect(AddSpecialEffect(POINT_SFX,d.startx[d.c],d.starty[d.c]))
            endif
            if PLAY_CONSTANT_SFX then
                set d.sfx = AddSpecialEffectTarget(CONSTANT_SFX,d.cast,ATTACH_POINT)
            endif
        endif
        
        set d.count = d.count + d.move

        if d.count &gt;= Maxdist(d.lvl) then
            call GroupClear(d.Group1)
            if PLAY_CONSTANT_SFX then
                call DestroyEffect(d.sfx)
            endif
            if PLAY_POINT_SFX then
                call DestroyEffect(AddSpecialEffect(POINT_SFX,d.endx[d.c],d.endy[d.c]))
            endif
            if d.c &lt; Maxslashes(d.lvl) then
                call SetUnitX(d.cast,SafeX(d.startx[d.c+1]))
                call SetUnitY(d.cast,SafeY(d.starty[d.c+1]))
            endif
            set d.c = d.c + 1
            set d.count = 0.00
        endif
        
        set d.angle = AngleXY(d.startx[d.c],d.starty[d.c],d.endx[d.c],d.endy[d.c])
        set newx = GetUnitX(d.cast) + d.move * Cos(d.angle)
        set newy = GetUnitY(d.cast) + d.move * Sin(d.angle)
            
        call SetUnitX(d.cast,SafeX(newx))
        call SetUnitY(d.cast,SafeY(newy))
        call SetUnitFacing(d.cast,d.angle*bj_RADTODEG)
        call SetUnitAnimationByIndex(d.cast,ANIM_INDEX)
        
        call GroupEnumUnitsInRange(d.Group2,SafeX(newx),SafeY(newy),COLLISION_RADIUS,Condition(function FilterEnemies))
        loop
            set u = FirstOfGroup(d.Group2)
            exitwhen u == null
            if IsUnitAlly(u,GetOwningPlayer(d.cast)) then
                call GroupAddUnit(d.Group1,u)
            endif
            if not(IsUnitInGroup(u,d.Group1)) then
                call GroupAddUnit(d.Group1,u)
                set tmpx = GetUnitX(u)
                set tmpy = GetUnitY(u)
                set tmpdist = DistanceXY(tmpx,tmpy,d.castx,d.casty)
                set tmpang = AngleXY(tmpx,tmpy,d.castx,d.casty)
                call SetUnitX(u,SafeX(tmpx+(tmpdist*KNOCK_PERC)*Cos(tmpang)))
                call SetUnitY(u,SafeY(tmpy+(tmpdist*KNOCK_PERC)*Sin(tmpang)))
                call UnitDamageTarget(d.cast,u,Damage(d.lvl),false,false,A_TYPE,D_TYPE,W_TYPE)
                if PLAY_HIT_SFX then
                    call DestroyEffect(AddSpecialEffectTarget(HIT_SFX,u,ATTACH_POINT))
                endif
                call GroupRemoveUnit(d.Group2,u)
            endif
                call GroupRemoveUnit(d.Group2,u)
        endloop
        
        if d.c == Maxslashes(d.lvl) then
            call d.destroy()
            return true
        endif
        
        set u = null
        return false
    endmethod
    
    private method onDestroy takes nothing returns nothing
        local integer i = 1
        
        if .sfx != null then
            call DestroyEffect(.sfx)
            set .sfx = null
        endif
        
        if PLAY_POINT_SFX then
            loop
                exitwhen i &gt; Maxslashes(.lvl)
                call DestroyEffect(AddSpecialEffect(POINT_SFX,.startx[i-1],.starty[i-1]))
                call DestroyEffect(AddSpecialEffect(POINT_SFX,.endx[i-1],.endy[i-1]))
                set i = i + 1
            endloop
        endif
        
        if .PoSon then
            call UnitAddAbility(.cast,&#039;WOE2&#039;)
        endif
        
        call SetUnitX(.cast,.castx)
        call SetUnitY(.cast,.casty)
        
        call PauseUnit(.cast,false)
        call SetUnitPathing(.cast,true)
        call SetUnitTimeScale(.cast,1.00)
        call SetUnitAnimation(.cast,&quot;stand&quot;)
        call SetUnitInvulnerable(.cast,false)
        call SetUnitVertexColor(.cast,255,255,255,255)
        call DestroyEffect(AddSpecialEffect(POINT_SFX,.castx,.casty))
        call IssueImmediateOrder(.cast,&quot;stop&quot;)
        set .cast = null
        
        call GroupClear(.Group1)
        call DestroyGroup(.Group1)
        call GroupClear(.Group2)
        call DestroyGroup(.Group2)
        
        call ReleaseTimer(.t)
    endmethod
endstruct
  
//=======================================================================
private function MoveConditions takes nothing returns boolean
    return GetSpellAbilityId()== ABIL_ID
endfunction

//=======================================================================
private function MoveActions takes nothing returns nothing
    call Data.create()
endfunction

//=======================================================================
private function TrueFilt takes nothing returns boolean
    return true
endfunction

//=======================================================================
private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    local integer i = 0
    
    set Game_MaxX = GetRectMaxX(bj_mapInitialPlayableArea)-64.00
    set Game_MinX = GetRectMinX(bj_mapInitialPlayableArea)+64.00
    set Game_MaxY = GetRectMaxY(bj_mapInitialPlayableArea)-64.00
    set Game_MinY = GetRectMinY(bj_mapInitialPlayableArea)+64.00
    
    set TrueFilter = Filter(function TrueFilt)
    
    loop
        call TriggerRegisterPlayerUnitEvent(trig,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,TrueFilter)
        set i = i + 1
        exitwhen i == bj_MAX_PLAYER_SLOTS
    endloop
    
    call TriggerAddCondition(trig,Condition(function MoveConditions))
    call TriggerAddAction(trig,function MoveActions)
endfunction

endscope
 

quraji

zap
Reaction score
144
Very cool ability. Works well as far as I can tell. One thing though, is that the cooldown is always 2.00 as indicated by this function:
JASS:
private function Cooldown takes integer lvl returns real
    return 2.00 // 10.00-(1.00+(1.00*lvl)) // Cooldown of the ability.
endfunction

Probably something you forgot to change after testing? You could also easily change the formula to "9.00-lvl". :)

Other than that, I can just offer one suggestion. Make the ability optionally trigger on a double-click instead of any point/target order. This way the player isn't forced to use it every 'cooldown' seconds. I have a sprint ability similar to this (but drains mana per second of usage) that activates on double-click order, it's nice.
 

Kenny

Back for now.
Reaction score
202
One thing though, is that the cooldown is always 2.00 as indicated by this function:

Probably something you forgot to change after testing? You could also easily change the formula to "9.00-lvl".

From first post:

Description:

Gives the hero the ability to move rapidly to the target point or unit of an order it receives.

Level 1 - Has a maximum range of 500, with an 8 second cooldown.

Level 2 - Has a maximum range of 600, with a 7 second cooldown.

Level 3 - Has a maximum range of 700, with a 6 second cooldown.

Note: The cooldown for this ability has been changed to 2 seconds for testing purposes.

:p As for the weird formula i was using, i wasnt really paying attention when i did it, just made up a random one that involved subtracting from 10, got no idea why, lol.

Make the ability optionally trigger on a double-click instead of any point/target order. This way the player isn't forced to use it every 'cooldown' seconds.

That would be a really good idea, except i wouldn't know where to start for double clicking. :eek:
 

quraji

zap
Reaction score
144
From first post:

Suppose I should have actually read it then :p

As for the double clicking, try something like this:

JASS:
scope doubleclick initializer init

globals
    private real doubleclick_time = .3
    private trigger trig
endglobals

private function doubleclick takes nothing returns nothing
    call ClearTextMessages()
    call BJDebugMsg(&quot;you double-clicked!!!&quot;)
endfunction

private function callback takes nothing returns nothing
    call DestroyTrigger(trig)
    call DestroyTimer(GetExpiredTimer())
endfunction

private function action takes nothing returns nothing
    local timer timmy = CreateTimer()
    set trig = CreateTrigger()
    
    call TriggerRegisterUnitEvent(trig, GetTriggerUnit(), EVENT_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddAction(trig, function doubleclick)
    
    call TimerStart(timmy, doubleclick_time, false, function callback)
    
    set timmy = null
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddAction(t, function action)

endfunction

endscope


It's crude and messy but that's sort of what you're looking for. Hope it helps :)
 

Kenny

Back for now.
Reaction score
202
Thats as far as i got with double clicking. However when i tried to implement it into the spell it became more difficult for me for a few reasons.

- I would need to check which order was used in the first click then compare it to the second click, to make sure it the user doesnt use attack then move and it still activates. How exactly would that be done.

- And would the global trigger make it non-MUI? not sure about that.

Thanks for the idea. I will try to get it working. + rep.
 

WolfieeifloW

WEHZ Helper
Reaction score
372
I didn't read the above posts, but I'm pretty sure no one said this.
When right-clicking [spamming it] on a creep to attack it, the hero uses the ability even though he doesn't move.
 

Kenny

Back for now.
Reaction score
202
When right-clicking [spamming it] on a creep to attack it, the hero uses the ability even though he doesn't move.

Not quite sure what you mean by that. Are you right next to the unit while spamming or are you right clicking from a distance?

I'll do some testing and see if i cant get any results.

EDIT:

From my tests all i can see that has any resemblance to what you are saying is when you issue the order to target a unit that is already being attacked at close range. However, it does not set off the passive spell. This was actually not meant, but it gives the effect that i wanted to achieve. So it works as planned, i guess. :p
 

WolfieeifloW

WEHZ Helper
Reaction score
372
The hero is right next to the enemy.
And I'm right-clicking so he attacks the unit and he uses the skill instead of just attacking since the unit's already range.
 

Kenny

Back for now.
Reaction score
202
The hero is right next to the enemy.
And I'm right-clicking so he attacks the unit and he uses the skill instead of just attacking since the unit's already range.

I cannot recreate what you are describing. When i spam right-click on a unit that i am already attacking at close range, all that happens is the hero just keeps attacking without using the skill, so basically the opposite of what you are saying, unless i am reading it wrong.
 

WolfieeifloW

WEHZ Helper
Reaction score
372
[del]I'll make a video of it, one sec.[/del]

EDIT: My FRAPS doesn't work, of course right when I need it :mad: .
But it still uses the skill even when I'm in range attacking a unit.
[del]I'll upload the replay, at the start I sit still because FRAPS kept erroring.[/del]

EDITEDIT: I've uploaded a better replay.

EDITEDITEDIT: I've actually noticed it doesn't set the skill on cooldown, but it does the little black animation above the heroes head, which makes it look like the skill was used.
 
O

orgolove

Guest
Is there a way to make it show cooldown in the traditional manner? (the clock-like light) And make it an autocast/activateable skill? :(
 

Kenny

Back for now.
Reaction score
202
@ WolfieNoCT:

Im sorry i cant get the replay to work. However I have done extensive tests and i still cant get even the effect to play while spamming right click on the target unit. It could have something to do with how the special effect is shown, but im almost 100% its ok.

Maybe someone else can watch the replay to verify?

@ orgolove:

Making the tradition cooldown is possible, however it is a bit more difficult to code, especially for this type of spell. As for autocasting, again im sure its possible, however im not sure if i can code it.

As a side note. Im pretty much stumped on implementing the double-clicking idea while still keeping this MUI, so i think im gonna give up on that.
 

quraji

zap
Reaction score
144
EDITEDITEDIT: I've actually noticed it doesn't set the skill on cooldown, but it does the little black animation above the heroes head, which makes it look like the skill was used.

Too lazy to verify this, but IIRC the animation is shown when the cooldown resets.
 
O

orgolove

Guest
Sorry about not noticing the reply... I thought you were gone, since there were no posts by you for about the past week or so...

I'm trying to make the following spell that could be coded:

gaeasteps-1.jpg


Walk of Elune

Kairi taps into her inner connection with Elune, allowing her to move with such speed that even the fastest bullets cannot catch up to her.

Kairi moves at a rate of 5 times her current speed to a target location.


In Depth Explanation

Duration is infinite
Casting Range is 700
Casting Time is Instant




Cooldown: 10/5/1/0
Mana Cost: 100/80/60/50
Spell effects:
When on autocast, each move command causes her to speed toward the target of the move. If the target is greater than the max range of the spell, she will only move however the max range is, then walk the rest of the way.


I tried your spell, and just changed the 3d model into the night elf runner model. A weird bug happened... As you can see in the first map I uploaded, whenever the model moves, there's the runner death sound playing.



I would really appreciate it if you could help me make the spell possible. The speed formula I can tweak... just the movement triggering is what's making things the most difficult.



---------------------------------------------



Also, I was wondering if I could implement pathing with this spell, so that it cannot jump across unwalkable terrain. What I was thinking was for every iteration of the "slide," (0.01 seconds) I would order the unit to move. This would allow the unit to take advantage of the pathing engine inherent in wc3, and cause him to face appropriately. Since there's a turning speed, I thought waiting 0.5 seconds every four iterations of the spell might work.

The second map is that attempt.... As you can see, it's going crazy all over the place.


Your help would be appreciated in making this work...
 

Kenny

Back for now.
Reaction score
202
I tried your spell, and just changed the 3d model into the night elf runner model. A weird bug happened... As you can see in the first map I uploaded, whenever the model moves, there's the runner death sound playing.

That is due to the global integer variable AnimationByIndex, which i set to the run animation of the mountain king. To fix it, just find out what the run animation is for your desired unit, then change that variable in the global block.

Also, I was wondering if I could implement pathing with this spell, so that it cannot jump across unwalkable terrain.

I was actually thinking about implementing this into the spell, using Vexorians Check Pathability function. So i may do that.

Give me some time and ill update the spell.

As far as the spell idea posted above. Im not entirely sure its possible, as there is no auto-castable spell in warcraft 3 that triggers off right-clicks to the ground. But it may be possible just to do it for right-clicking on a unit.

I will fool around with the idea for a bit and see if i am able to do it, but there are no promises :p.

PS: Where'd you get the icon from?

EDIT:

I have done some testing, and i have figured out that it is indeed very hard to make it auto-castable. Especially for melee heroes, as buffs from arrow spells do not appear for them. I have however, gotten pretty close, I can make it work, however it becomes a constant loop due to the order not being completed before the spell is triggered. I will post the script some time soon and maybe someone else can figure it out.

Also the spell will be updated soon to include checking pathability.
 
O

orgolove

Guest
Thank you so much...

Rather than being auto castable, perhaps it could be easier to have it be toggleable? I mean, something like:

1. Put a buff on the unit whenever the player toggles the ability on.
2. As long as there's a buff, run the trigger, and whenever the trigger is run, remove a certain portion of the unit's mana. If there isn't enough mana, do not run the trigger.

That's what I wanted to try, after I had gotten the ability to work.




All the icons for this hero were made by using guild wars icons as a base. I added borders and applied a pallete shift to "purple" to complete the effect.



In addition to this spell, I was trying to achieve the following concept.

novarush1.jpg



Nova Charge

Spell Effects

Kairi strikes across the battlefield multiple times (0.5s per strike), damaging everything in her wake and pushing everything she strikes into the center.



Level 1 - Hits enemies within a 400 unit radius. Strikes across the battlefield 4 times for 100 damage each.
Level 2 - Hits enemies within a 500 unit radius. Strikes across the battlefield 6 times for 100 damage each.
Level 3 - Hits enemies within a 600 unit radius. Strikes across the battlefield 8 times for 100 damage each.


In Depth Explanation

Duration 2/3/4 seconds
Casting Range is 100 (Kairi must be right next to the charging area)
Casting Time is 0.5s


Cooldown: 60 seconds
Mana Cost: 150/225/300

The following is an illustration of exactly how this works:
instruction.jpg


Each enemy will take damage equal to 100 times the number of times Kairi has passed through him/her. Kairi is invulnerable during that time, meaning aoe stuns/binds will not stop it. Note how the enemy hero in the lower right corner was able to escape the ult by just running out of the radius.

Each "run" would cause her to shadow-walk across terrain. But because I didn't know how to shadow walk across, the below is where I am at right now (thanks to exirrah). Ignore the illusions-they are the effects of a passive that I have been thinking about putting into the hero.
 

T.s.e

Wish I was old and a little sentimental
Reaction score
133
Isn't this spell in Rising_Dusk's Desert of Exile?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top