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

Shadow Attack v1.00

shadow attack

Akama circles around his opponents, dashing through and attacking multiple victims before vanishing back within his maze of illusion.

Level 1: Deals 35 damage each target, maximum 3 targets.
Level 2: Deals 45 damage each target, maximum 4 targets.
Level 3: Deals 55 damage each target, maximum 5 targets.
JASS:
library ShadowAttack requires TimerUtils, GroupUtils, xedamage
//===============================================================================================
// shadow attack v1.00 by scorpion182
//  requires: TimerUtils, xedamage by Vexorian
//            GroupUtils by RisingDusk
//
//  optional: BoundSentinel by Vexorian (use it if you don't want your hero stuck outside the map bound)
//
//===============================================================================================
    globals
        private keyword data
//-----------------------CALIBRATION SECTION-----------------------------------------------------
        //spell id
        private constant integer        SPELL_ID        =   'A000'
        
        //shadow trail unit id
        private constant integer        SHADOW_TRAIL    =   'h000'
        
        //shadow number
        private constant integer        MAX_SHADOW      =   30
        
        //attack animation index
        private constant integer        ANIMATION_INDEX =   2
        
        //walk animation index
        private constant integer        ATTACK_AN_INDEX =   5
        
        //collision distance
        private constant real           COLLISION_DIST  =   75.
        
        //spell order id
        private constant string         ORDER_ID        =   "blizzard"
        
        //image special effect
        private constant string         IMAGE_FX        =   "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageCaster.mdl"
        
        //on-damage effect
        private constant string         ON_DAMAGE_FX    =   "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
        
        //on-damage effect attachment point
        private constant string         DAMAGE_ATTCH    =   "origin"
        
        //preload effect?
        private constant boolean        PRELOAD_FX      =   true
    endglobals
    
    //do damage each hit
    private constant function GetDamage takes integer lvl returns real
        return 35. + lvl * 10  
    endfunction
    
    //attack interval
    private constant function GetAttackInterval takes integer lvl returns real
        return 2. + lvl * 0
    endfunction
    
    //maximum number target per level
    private constant function GetTargetNum takes integer lvl returns integer
        return 2 + lvl * 1
    endfunction
    
    //rotation speed
    private constant function GetAngleSpeed takes integer lvl returns real
        return .05 + lvl * 0.
    endfunction
    
    //caster movement speed
    private constant function GetSpeed takes integer lvl returns real
        return 800. * XE_ANIMATION_PERIOD + lvl * 0
    endfunction
    
    //shadow number
    private constant function GetShadowNum takes integer lvl returns integer
        return 18 + lvl * 0
    endfunction
    
    //area of effect
    private constant function GetDistance takes integer lvl returns real
        return 450. + lvl * 0
    endfunction
    
    //filter the target
    private function IsValidTarget takes unit u, data s returns boolean
        return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 and IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitEnemy(u,GetOwningPlayer(s.caster))==true and IsUnitVisible(u,GetOwningPlayer(s.caster))==true
    endfunction
    
    //damage options must match the target filter
    private function DamageOptions takes xedamage spellDamage returns nothing
        set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
        set spellDamage.atype=ATTACK_TYPE_NORMAL
        set spellDamage.exception=UNIT_TYPE_STRUCTURE
        set spellDamage.visibleOnly=true 
    endfunction
//----------------------END OF CALIBRATION-------------------------------------------------------
    globals
        private xedamage xed
        private constant real A = 2 * bj_PI
    endglobals
    
    private struct shadow
        unit array shadow[MAX_SHADOW]
        real array angle[MAX_SHADOW]
        boolean array show[MAX_SHADOW]
        group victim 
        unit target = null
        boolean sets = false
        boolean attack = false
        boolean ready = false
        boolean back = false
        boolean inpos = false
        real interval = 0
        real cap = 0
        real ag = 0
        integer idx = 0
        integer num = 0
        integer n = 0
        integer pos = 0
        unit caster
        timer t
        integer lvl
        real cx
        real cy
        private static thistype temp
        
        static method create takes unit c, real x, real y returns thistype
            local thistype this = thistype.allocate()
            local integer i = 0
            local real a
            
            set .caster = c
            set .t = NewTimer()
            set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
            set .victim = NewGroup()
            set .cap = A 
            set .cx = x
            set .cy = y
            
            loop
            exitwhen i == GetShadowNum(.lvl)
                set a = i * A / GetShadowNum(.lvl)
                set .shadow[i] = CreateUnit(GetOwningPlayer(.caster), SHADOW_TRAIL, x, y, a)
                set .angle[i] = a
                
                call SetUnitTimeScale(.shadow[i], 2.)
                call SetUnitColor(.shadow[i], PLAYER_COLOR_LIGHT_GRAY)
                call SetUnitVertexColor(.shadow[i], 255, 255, 255, 125)
                call SetUnitAnimationByIndex(.shadow[i], ANIMATION_INDEX)
                call ShowUnit(.shadow[i], false)
                
                set i = i + 1
            endloop
            
            call SetTimerData(.t, this)
            call TimerStart(.t, XE_ANIMATION_PERIOD, true, function thistype.onLoop)
            return this
        endmethod
        
        //i don't know why i can't detect locust unit, so i reverse it
        static method IsCaster takes nothing returns boolean
            local unit u = GetFilterUnit()
            
            if u==temp.caster and not temp.sets then
                set temp.pos = temp.idx
                set temp.sets = true
                call RemoveUnit(temp.shadow[temp.idx])
                set temp.shadow[temp.idx] = temp.caster
                set temp.show[temp.idx] = true
                set temp.num = temp.num + 1
            endif
            
            set u = null
            return false
        endmethod
        
        static method IsTarget takes nothing returns boolean
            local unit u = GetFilterUnit()
            
            if temp.target == null and IsValidTarget(u, temp) and not IsUnitInGroup(u, temp.victim) then
                set temp.target = u
                set temp.n = temp.n + 1
                
                set temp.attack = true
                call GroupAddUnit(temp.victim, u)
            endif
            
            set u = null
            return false
        endmethod
        
        static method onLoop takes nothing returns nothing
            local thistype this = thistype(GetTimerData(GetExpiredTimer()))
            local integer i = 0
            local real dx 
            local real dy 
            local real dis 
            local real angle
            
            if GetUnitCurrentOrder(.caster) == OrderId(ORDER_ID) then
              
                set temp = this
              
                if .ready then
                    
                    if .back then
                        set .interval = .interval + XE_ANIMATION_PERIOD
                        
                        if .interval > GetAttackInterval(.lvl) then
                            set .interval = 0.
                            set .back = false
                            set .shadow [.pos] = CreateUnit(GetOwningPlayer(.caster), SHADOW_TRAIL, GetUnitX(.caster), GetUnitY(.caster), GetUnitFacing(.caster))
                            
                            call SetUnitTimeScale(.shadow[.pos], 2.)
                            call SetUnitColor(.shadow[.pos], PLAYER_COLOR_LIGHT_GRAY)
                            call SetUnitVertexColor(.shadow[.pos], 255, 255, 255, 125)
                            call SetUnitAnimationByIndex(.shadow[.pos], ANIMATION_INDEX)
                            call UnitAddAbility(.shadow[.pos], 'Aloc')
                
                            call GroupClear(.victim)
                            
                            
                        endif
                    
                    endif
                    
                    if not .attack and .target == null and .n < GetTargetNum(.lvl) and not .back then
                        call GroupEnumUnitsInRange(ENUM_GROUP, .cx, .cy, GetDistance(.lvl), function thistype.IsTarget)
                        
                        if .n == 0 and .target == null and not .inpos then
                            set .inpos = true
                            call RemoveUnit(.shadow[.pos])
                            set .shadow[.pos] = .caster
                            set .show[.pos] = true
                            call SetUnitAnimationByIndex(.caster, ANIMATION_INDEX)
                        endif
                        
                        if .n >= 1 and .target == null then
                            set .ready = false
                            call SetUnitAnimationByIndex(.caster, ANIMATION_INDEX)
                        endif
                    elseif .attack and .target!=null then
                        set dx = GetUnitX(.target) - GetUnitX(.caster)
                        set dy = GetUnitY(.target) - GetUnitY(.caster)
                        set dis = SquareRoot(dx * dx + dy * dy)
                        set angle = Atan2(dy, dx)
                        
                        if dis > COLLISION_DIST and IsUnitInRangeXY(.target, .cx, .cy, GetDistance(.lvl)) then
                            call SetUnitX(.caster, GetUnitX(.caster) + GetSpeed(.lvl) * Cos(angle))
                            call SetUnitY(.caster, GetUnitY(.caster) + GetSpeed(.lvl) * Sin(angle))
                            call SetUnitFacing(.caster, angle * bj_RADTODEG)
                        else
                            call xed.damageTarget(.caster,.target,GetDamage(.lvl))
                            set .attack = false
                            set .target = null
                            call SetUnitAnimationByIndex(.caster, ATTACK_AN_INDEX)
                        endif
                    elseif .n == GetTargetNum (.lvl) then
                        set .ready = false
                        call SetUnitAnimationByIndex(.caster, ANIMATION_INDEX)
                    endif
                else
                    if .num == GetShadowNum(.lvl) then
                        
                        set dx = GetUnitX(.shadow[.pos]) - GetUnitX(.caster)
                        set dy = GetUnitY(.shadow[.pos]) - GetUnitY(.caster)
                        set dis = SquareRoot(dx * dx + dy * dy)
                        set angle = Atan2(dy, dx)
                            
                        if dis > 215. then
                            call SetUnitX(.caster, GetUnitX(.caster) + GetSpeed(.lvl) * Cos(angle))
                            call SetUnitY(.caster, GetUnitY(.caster) + GetSpeed(.lvl) * Sin(angle))
                            call SetUnitFacing(.caster, angle * bj_RADTODEG)
                        else
                            
                            if not .back then
                                set .back = true
                                set .ready = true
                                set .attack = false
                                set .inpos = false
                                set .n = 0
                                set .target = null
                                set .pos = .pos - 1
                    
                                
                                if .pos < 0 then
                                    set .pos = GetShadowNum(.lvl) - 1
                                endif
                                
                                call RemoveUnit(.shadow[.pos])
                                set .shadow[.pos] = .caster
                                set .show[.pos] = true
                                call SetUnitAnimationByIndex(.caster, ANIMATION_INDEX)
                            
                            endif
                        endif
                    endif
                endif
              
                if .sets and .ag > .cap then
                    set temp.idx = temp.idx - 1
                    
                    if temp.idx < 0 then
                        set temp.idx = GetShadowNum(.lvl) - 1
                    endif
                
                    set .ag = 0
                    
                    if not .show[.idx] then
                        set .num = .num + 1
                        
                        if .num > GetShadowNum(.lvl) then
                            set .num = GetShadowNum(.lvl)
                        endif
                        
                        if .num == GetShadowNum(.lvl) then
                            
                            set .ready = true
                            set .shadow [.pos] = CreateUnit(GetOwningPlayer(.caster), SHADOW_TRAIL, GetUnitX(.caster), GetUnitY(.caster), GetUnitFacing(.caster))
                            call SetUnitTimeScale(.shadow[.pos], 2.)
                            call SetUnitColor(.shadow[.pos], PLAYER_COLOR_LIGHT_GRAY)
                            call SetUnitVertexColor(.shadow[.pos], 255, 255, 255, 125)
                            call SetUnitAnimationByIndex(.shadow[.pos], ANIMATION_INDEX)
                            call UnitAddAbility(.shadow[.pos], 'Aloc')
                            
                        endif
                        set .show[.idx] = true
                        call ShowUnit(.shadow[.idx], true)
                        call UnitAddAbility(.shadow[.idx], 'Aloc')
                        call DestroyEffect(AddSpecialEffectTarget(IMAGE_FX, .shadow[.idx], "origin"))
                    endif
                    
                endif
                
                
                
                loop
                exitwhen i == GetShadowNum(.lvl)
                    if not .sets then
                        call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(.shadow[i]), GetUnitY(.shadow[i]), COLLISION_DIST, function thistype.IsCaster)
                        set .idx = i
                    endif
                    set .ag = .ag + GetAngleSpeed(.lvl)
                    set .angle[i] = .angle[i] + GetAngleSpeed(.lvl)
                    call SetUnitX(.shadow[i], .cx + GetDistance(.lvl) * Cos(.angle[i]))
                    call SetUnitY(.shadow[i], .cy + GetDistance(.lvl) * Sin(.angle[i]))
                    call SetUnitFacing(.shadow[i], .angle[i] * bj_RADTODEG + 90. )
                    
                    set  i = i + 1
                endloop
                
            else
                call .destroy()
            endif
            
        endmethod
        
        private method onDestroy takes nothing returns nothing
            local integer i = 0
        
            call ReleaseTimer(.t)
            call ReleaseGroup(.victim)
            call SetUnitTimeScale(.caster, 1.)
            
            loop
                exitwhen i == GetShadowNum(.lvl)
                    set .show[i] = false
                    if .shadow[i] != .caster then
                        call SetUnitExploded(.shadow[i], true)
                        call KillUnit(.shadow[i])
                    endif
                    set  i = i + 1
                endloop
        endmethod
    endstruct
    
    private struct data
        unit caster
        timer t
        real tx
        real ty
        integer lvl
        real cx
        real cy
        
        static method create takes unit c, real x, real y returns thistype
            local thistype this = thistype.allocate()
            local real angle = bj_RADTODEG * Atan2(y - GetUnitY(c), x - GetUnitX(c))
            
            set .cx = x
            set .cy = y
            set .caster = c
            set .t = NewTimer()
            set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
            set .tx = x + GetDistance(.lvl) * Cos(angle)
            set .ty = y + GetDistance(.lvl) * Sin(angle)
            call SetUnitFacing(.caster, angle)
            call SetUnitAnimationByIndex(.caster, ANIMATION_INDEX)
            call SetUnitTimeScale(.caster, 2.)
            
            return this
        endmethod
        
        private method onDestroy takes nothing returns nothing
            call ReleaseTimer(.t)
        endmethod
        
        static method onLoop takes nothing returns nothing
            local thistype this = thistype(GetTimerData(GetExpiredTimer()))
            local real dx = .tx - GetUnitX(.caster) 
            local real dy = .ty - GetUnitY(.caster)
            local real dis = SquareRoot(dx * dx + dy * dy)
            local real angle = Atan2(dy, dx)
            local shadow s
            
            if dis > COLLISION_DIST and GetUnitCurrentOrder(.caster) == OrderId(ORDER_ID) then
                call SetUnitX(.caster, GetUnitX(.caster) + GetSpeed(.lvl) * Cos(angle))
                call SetUnitY(.caster, GetUnitY(.caster) + GetSpeed(.lvl) * Sin(angle))
                call SetUnitFacing(.caster, angle * bj_RADTODEG)
            else
                call .destroy()
                set s = shadow.create(.caster, .cx, .cy) 
            endif
            
        endmethod
        
        static method SpellEffect takes nothing returns boolean
            local thistype this
            local unit u = GetTriggerUnit()
            local real x = GetSpellTargetX()
            local real y = GetSpellTargetY()
            
            if GetSpellAbilityId() == SPELL_ID then
                set this = thistype.create(u, x, y)
                call SetTimerData(.t, this)
                call TimerStart(.t, XE_ANIMATION_PERIOD, true, function thistype.onLoop)
            endif
            
            set u = null
            return false
        endmethod

        static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.SpellEffect))
            
            //init xedamage
            set xed=xedamage.create()
            call DamageOptions(xed)
            call xed.useSpecialEffect(ON_DAMAGE_FX, DAMAGE_ATTCH)
            
            //preload fx
            static if PRELOAD_FX then
                call Preload(IMAGE_FX)
                call Preload(ON_DAMAGE_FX)
            endif
        endmethod
        
    endstruct

endlibrary

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

Keywords:
akama, draenei, mirror, image, circle, ground, area, clone, human, orc, dash, ninja
Contents

Shadow Attack (Map)

Reviews
17:32, 3rd May 2010 The_Reborn_Devil: The coding looks good, but you could probably consider using one constant timer and a loop which loops through all instances though. Status: Approved Rating: Recommended

Moderator

M

Moderator

17:32, 3rd May 2010
The_Reborn_Devil:

The coding looks good, but you could probably consider using one constant timer and a loop which loops through all instances though.


Status: Approved
Rating: Recommended
 
Level 6
Joined
Nov 3, 2008
Messages
117
Ok feedback.

Spell description says 35/45/55 damage.

Spell code will do 45/55/65 because of :

[jass=code]private constant function GetDamage takes integer lvl returns real
return 35. + lvl * 10
endfunction[/code]

"i don't know why i can't detect locust unit, so i reverse it"

- you can detect locust units, but not with Group(Enum)UnitsInRange or with GroupUtils Group(Enum)UnitsInArea. To detect locust units you have to use Group(Enum)UnitsOfPlayer. (Someone told me about this, but i never tested it :p)

Anyways this spell looks good and the code is fine aswell.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
Note that I have only skimmed through the code on the site.

  • You're using GroupUtils, so you should use GroupEnumUnitsInArea instead of GroupEnumUnitsInRange. Hey, does this seem familiar? =P
  • You should try to preload your dummy units so you don't get some lag upon first cast of this spell.
  • This is optional, but you could try to recycle the dummy units.
  • This is very optional, but you might want to consider making the dummy units owned by some other Player. (Like Neutral Passive) The only reason is that it'll look nicer at the score screen where you won't see a huge amount of units produced for that player.
Your coding has improved a lot that I pretty much can't give you any other suggestions. XD
 
Level 2
Joined
May 14, 2010
Messages
10
uh excuse me im new to this. but how do you import this skill on another map?
a fast reply would be appreciated. btw nice skill
 
Top