Spell Shadow Charge

Andrewgosu

The Silent Pandaren Helper
Reaction score
716
Long time a spell from me!

May I present, Shadow Charge!


The concept behind the spell is simple and not very unique - basic Charge spell with stun and knock back in the end.

However, the fun part comes in when taking a look at the eye-candy:

the spell creates a diablo-like shadows behind the caster and behind the target when it's hit.

It works with all units and no dummies besides one doing the stun casting is required.


Yes, you can edit the amount of "shadows" behind the target and caster.

Yes, you can edit the charge and knockback speed.


Though, there's only one drawback:

The spells creates replicas of the unit itself, meaning, if it is used (or the target) by a hero, it will create heroes which show up an interface icon.

To remove the issue, one has to disable the in-game icon showing for heroes.


Anyway, here are some screenshots!

shadowc.jpg




The actual code itself, quite lengthy, I know I am bad at coding.

JASS:
scope SHADOWCHARGE
    globals
        private constant integer SHADOW_CHARGE_ID          = 'A000'
        private constant integer DUMMY_STUN_ID             = 'A001'
        private constant string  DUMMY_STUN_ORDER          = "thunderbolt"
        private constant integer DUMMY_UNIT_ID             = 'h000'
        private constant integer CASTER_SHADOW_AMOUNT      = 4
        private constant integer CASTER_MOVE_ANI_INDEX     = 0 // Animation "move" index for the caster
        private constant integer TARGET_SHADOW_AMOUNT      = 2
        private constant real    SHADOW_SPACING            = 55. // Distance between the "shadows"
        private constant real    CASTER_CHARGE_SPEED       = 1125.
        private constant real    TARGET_KNOCKBACK_SPEED    = 375.
        private constant real    TARGET_KNOCKBACK_LENGTH   = 200.
        private constant real    CASTER_ANI_SPEED_INCREASE = 250. // % the animation speed increases during charge
        private constant real    TARGET_RANGE_RADIUS       = 125. // The radius from the target which the charge will be ended
        private constant real    DESTRUCTABLE_KILL_RADIUS  = 160. // The radius in which the trees are kill around the caster
    endglobals

    globals
        private constant real TIMER_VAL = .0325
        private constant playerstate FOOD = PLAYER_STATE_RESOURCE_FOOD_USED
    endglobals

    private struct casterdata
        unit    caster   = null
        player  owner    = null
        unit    target   = null  
        timer   expired  = null
        trigger trig     = null
        integer foodCost = 0 
        
        real    facing 
        real    x
        real    y
        real    startX
        real    startY
        
        unit    array shadows[CASTER_SHADOW_AMOUNT]
        
        method removeShadows takes nothing returns nothing
            local integer index = 1
            loop
                exitwhen (index > CASTER_SHADOW_AMOUNT)
                call RemoveUnit(.shadows[index])
                call SetPlayerState(.owner, FOOD, GetPlayerState(.owner, FOOD) + .foodCost)
                set index = index + 1
            endloop            
        endmethod

        method onDestroy takes nothing returns nothing
            call SetUnitPathing(.target, false) 
            call .removeShadows()
            call DestroyTrigger(.trig)
            call ReleaseTimer(.expired)
        endmethod
    endstruct  

    private function EnumDestructablesInCircle takes real radius, real x, real y, code actionFunc returns nothing
        local rect r
        
        if (radius >= 0) then
            set r = Rect(x - radius, y - radius, x + radius, y + radius)
            call EnumDestructablesInRect(r, null, actionFunc)
            call RemoveRect(r)
        endif
    endfunction
   
    private function ShadowCharge_KillDestructables takes nothing returns nothing
        call KillDestructable(GetEnumDestructable())
    endfunction      
    
    private function ShadowCharge_MoveTarget takes nothing returns nothing
        local timer      t     = GetExpiredTimer()
        local casterdata data  = GetCSData(t)
        local real       x     = GetUnitX(data.target)
        local real       y     = GetUnitY(data.target)
        local real       dx    = x - data.startX
        local real       dy    = y - data.startY
        local real       speed = TARGET_KNOCKBACK_SPEED / (1 / TIMER_VAL)
        local integer    index = 1
        
        if (data.x == x) and (data.y == y) or (IsUnitType(data.target, UNIT_TYPE_DEAD) == true) then
            call data.destroy()
        else
            set data.x = x
            set data.y = y
            if (SquareRoot(dx * dx + dy * dy) < TARGET_KNOCKBACK_LENGTH) then
                set dx = x + speed * Cos(data.facing * bj_DEGTORAD)
                set dy = y + speed * Sin(data.facing * bj_DEGTORAD)
                call EnumDestructablesInCircle(DESTRUCTABLE_KILL_RADIUS, dx, dy, function ShadowCharge_KillDestructables)
                call SetUnitPosition(data.target, dx, dy)
                loop
                    exitwhen (index > TARGET_SHADOW_AMOUNT)
                    set dx = x + (SHADOW_SPACING * index) * Cos(data.facing * bj_DEGTORAD)
                    set dy = y + (SHADOW_SPACING * index) * Sin(data.facing * bj_DEGTORAD)
                    call SetUnitX(data.shadows[index], dx)
                    call SetUnitY(data.shadows[index], dy)
                    set index = index + 1
                endloop
            else
                call data.destroy()
            endif
        endif
    endfunction
    
    private function ShadowCharge_ChargeFinished takes nothing returns boolean
        local unit       trig  = GetTriggerUnit()
        local casterdata data  = GetCSData(trig)
        local real       sin   = Sin((GetUnitFacing(data.target)) * bj_DEGTORAD)
        local real       cos   = Cos((GetUnitFacing(data.target)) * bj_DEGTORAD)
        local integer    id    = GetUnitTypeId(data.target)
        local integer    index = 1
        local real       dx
        local real       dy
        local integer    alpha
        
        if (trig == data.caster) then
            call PauseUnit(data.caster, false)
            call SetUnitAnimation(data.caster, "stand")
            call SetUnitTimeScale(data.caster, 1)
            call data.removeShadows()                        
            set data.startX = GetUnitX(data.target)
            set data.startY = GetUnitY(data.target)
            set bj_ghoul[2012] = CreateUnit(data.owner, DUMMY_UNIT_ID, data.startX, data.startY, 0)
            set data.owner = GetOwningPlayer(data.target)
            set data.foodCost = GetUnitFoodUsed(data.target)
            call SetUnitFacing(data.target, data.facing + 180)
            loop
                exitwhen (index > TARGET_SHADOW_AMOUNT)
                set dx = data.startX + (SHADOW_SPACING * index) * cos
                set dy = data.startY + (SHADOW_SPACING * index) * sin
                set data.shadows[index] = CreateUnit(data.owner, id, dx, dy, data.facing + 180)
                call SetPlayerState(data.owner, FOOD, GetPlayerState(data.owner, FOOD) - data.foodCost)
                call UnitAddAbility(data.shadows[index], 'Aloc')
                call SetUnitX(data.shadows[index], dx)
                call SetUnitY(data.shadows[index], dy)                
                set alpha = PercentToInt(100. - (index * 100. / (I2R(TARGET_SHADOW_AMOUNT) + 1.)), 255)
                call SetUnitVertexColor(data.shadows[index], 255, 255, 255, alpha)
                set index = index + 1                
            endloop
            call SetUnitPathing(data.caster, true) 
            call SetUnitPathing(data.target, false) 
            call UnitApplyTimedLife(bj_ghoul[2012], 'BTLF', 3.)
            call UnitAddAbility(bj_ghoul[2012], DUMMY_STUN_ID)
            call SetUnitAbilityLevel(bj_ghoul[2012], DUMMY_STUN_ID, GetUnitAbilityLevel(data.caster, SHADOW_CHARGE_ID))
            call IssueTargetOrder(bj_ghoul[2012], DUMMY_STUN_ORDER, data.target)
            call TimerStart(data.expired, TIMER_VAL, true, function ShadowCharge_MoveTarget)
        endif
        return false
    endfunction
    
    private function ShadowCharge_MoveCharger takes nothing returns nothing
        local timer      expired = GetExpiredTimer()
        local casterdata data    = GetCSData(expired)
        local real       x       = GetUnitX(data.caster)
        local real       y       = GetUnitY(data.caster)
        local real       speed   = CASTER_CHARGE_SPEED / (1 / TIMER_VAL)
        local real       dx      = x + speed * Cos(data.facing * bj_DEGTORAD)
        local real       dy      = y + speed * Sin(data.facing * bj_DEGTORAD)
        local real       sin     = Sin((data.facing - 180.) * bj_DEGTORAD)
        local real       cos     = Cos((data.facing - 180.) * bj_DEGTORAD)
        local integer    index   = 1
        
        call EnumDestructablesInCircle(DESTRUCTABLE_KILL_RADIUS, dx, dy, function ShadowCharge_KillDestructables)
        call SetUnitPosition(data.caster, dx, dy)
        loop
            exitwhen (index > CASTER_SHADOW_AMOUNT)
            set dx = x + (SHADOW_SPACING * index) * cos
            set dy = y + (SHADOW_SPACING * index) * sin
            call SetUnitX(data.shadows[index], dx)
            call SetUnitY(data.shadows[index], dy)
            set index = index + 1
        endloop
        if (data.x == x) and (data.y == y) then

            call data.destroy()
        else
            set data.x = x
            set data.y = y
        endif
    endfunction
    
    private function ShadowCharge_InitializeCharge takes nothing returns nothing
        local casterdata data   = casterdata.create()
        local unit       caster = GetTriggerUnit()
        local unit       target = GetSpellTargetUnit()
        local player     owner  = GetOwningPlayer(caster)
        local integer    id     = GetUnitTypeId(caster)
        local real       x      = GetUnitX(caster)
        local real       y      = GetUnitY(caster)
        local real       facing = bj_RADTODEG * Atan2(GetUnitY(target) - y, GetUnitX(target) - x)
        local timer      t      = NewTimer()
        local trigger    trig   = CreateTrigger()
        local integer    index  = 1        
        local real       sin    = Sin((facing - 180.) * bj_DEGTORAD)
        local real       cos    = Cos((facing - 180.) * bj_DEGTORAD)

        local real       dx
        local real       dy
        local integer    alpha           
        
        call PolledWait(.1)
        call PauseUnit(caster, true)
        call TriggerRegisterUnitInRange(trig, target, TARGET_RANGE_RADIUS, null)
        call TriggerAddCondition(trig, Condition(function ShadowCharge_ChargeFinished))

        set data.foodCost = GetUnitFoodUsed(caster)
        set data.caster   = caster
        set data.target   = target
        set data.facing   = facing
        set data.owner    = owner
        set data.expired  = t
        set data.trig     = trig
        call SetUnitFacing(caster, facing)
        call SetUnitPathing(caster, false) 
        loop
            exitwhen (index > CASTER_SHADOW_AMOUNT)            
            set dx = x + (SHADOW_SPACING * index) * cos
            set dy = y + (SHADOW_SPACING * index) * sin
            set data.shadows[index] = CreateUnit(owner, id, dx, dy, facing)
            call PauseUnit(data.shadows[index] ,true)
            call SetUnitX(data.shadows[index], dx)
            call SetUnitY(data.shadows[index], dy)
            call UnitAddAbility(data.shadows[index], 'Aloc')
            set alpha = PercentToInt(100. - (index * 100. / (CASTER_SHADOW_AMOUNT + 1.)), 255)
            call SetUnitVertexColor(data.shadows[index], 255, 255, 255, alpha)
            call SetPlayerState(owner, FOOD, GetPlayerState(owner, FOOD) - data.foodCost)
            call SetUnitAnimationByIndex(data.shadows[index], CASTER_MOVE_ANI_INDEX)
            call SetUnitTimeScale(data.shadows[index], CASTER_ANI_SPEED_INCREASE / 100)
            set index = index + 1
        endloop
        call SetUnitAnimationByIndex(caster, CASTER_MOVE_ANI_INDEX)
        call SetUnitTimeScale(caster, CASTER_ANI_SPEED_INCREASE / 100) 
        call SetCSData(t, data)
        call SetCSData(caster, data)        
        call TimerStart(t, TIMER_VAL, true, function ShadowCharge_MoveCharger)
        
        set owner = null
        set t     = null
        set trig  = null
    endfunction
    
    private function ShadowCharge_AbilityId takes nothing returns boolean
        return GetSpellAbilityId() == SHADOW_CHARGE_ID
    endfunction
    
    //===========================================================================
    function InitTrig_ShadowCharge takes nothing returns nothing
        set gg_trg_ShadowCharge = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_ShadowCharge, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_ShadowCharge, Condition( function ShadowCharge_AbilityId))
        call TriggerAddAction(gg_trg_ShadowCharge, function ShadowCharge_InitializeCharge)
    endfunction
endscope


Have fun!

Comments, feedback and criticism always appreciated!



Andrewgosu
 

Attachments

  • ShadowCharge_Andrewgosu_2008.w3x
    62.5 KB · Views: 362

Oninuva

You can change this now in User CP.
Reaction score
221
Similar to the Charge that Bara (Spirit Breaker) has from DotA?
 

Flare

Stops copies me!
Reaction score
662
Nice spell, good work. The after-image effect is pretty sweet

Similar to the Charge that Bara (Spirit Breaker) has from DotA?

Seems to be, other than the after-image thing (I don't recall any of Barathrum's spells having that, unless recently added)

EDIT: Why does the purple Murloc have the after-image effect as well?
 

Larcenist

REP: Respect, Envy, Prosperity?
Reaction score
211
Basically charges towards a unit, gaining bonus speed periodically while charging as well as turning somewhat transparent. When reaching the unit you wack them, stunning them for a short duration. This spell is way more awesome imo.
 

Tom_Kazansky

--- wraith it ! ---
Reaction score
157
I don't think it is "similar to the Charge that Bara (Spirit Breaker) has from DotA", well, the Charge of Spirit Breaker doesn't have the illusions

I have this kind of spell too, Diablo II Charge :D
 

Trollvottel

never aging title
Reaction score
262
JASS:
                call RemoveUnit(.shadows[index])


cant that cause problems in the handle stack or something like that? i think emilr3 said that once.
 

Andrewgosu

The Silent Pandaren Helper
Reaction score
716
Removing a unit cause handle stack?

It would be nice if you could like me the post.

Otherwise, no.
 

Viikuna

No Marlo no game.
Reaction score
265
I tested it and it bugged. Knight got stuck. Replay .
 

UndeadDragon

Super Moderator
Reaction score
447
What can I say, apart from Good Job?

Very nice spell. Looks great. +rep

EDIT: Looks like I have been repping you too much :p
 

Andrewgosu

The Silent Pandaren Helper
Reaction score
716
Well, I have never had problems with removing units.

If someone finds this to bug, report it.
 

Hatebreeder

So many apples
Reaction score
381
Hmmm... Why do you damage the Unit after it gets bashed back?
It's like... Hitting somone, and delaying damage =P
Also, I would make shadows of the bashed unit. A simple knockback with dust would lokk better, IMO.
Btw, great Spell =)
 

Andrewgosu

The Silent Pandaren Helper
Reaction score
716
Actually, the dummy stun is delivered when the caster impacts the target.

And, the unit being knocked back has shadows...
 

Hatebreeder

So many apples
Reaction score
381
JASS:
        local timer      t     = GetExpiredTimer()
        local casterdata data  = GetCSData(t)
        local real       x     = GetUnitX(data.target)
        local real       y     = GetUnitY(data.target)
        local real       dx    = x - data.startX
        local real       dy    = y - data.startY
        local real       speed = TARGET_KNOCKBACK_SPEED / (1 / TIMER_VAL)
        local integer    index = 1


You create a new Variable everytime the timer runs.
why don't you use the struct, and keep overwriting whenever the timer runs?
I don't think it affects preformance, but nonetheless (You got more of this stuff, such as index in your code, I just wanted to point this out :) )

Also, I think that the Dummy Stun is useless, because you move the targeted unit via triggers, and you are using this:
JASS:

                        call SetUnitX(data.shadows[index], dx)
call SetUnitY(data.shadows[index], dy)

which throws the units out of Map boundries.
So, if you use
JASS:
call SetUnitPosition(...)
then you give the targeted unit a stunning effect, and I doubt that the targeted unit will move out of Map boundries.
 

xenaris

New Member
Reaction score
2
i saw this skill and i saw how it was made, im learning JASS now and i do understand the basics of it but when i see that im losing all hope :(
 
Reaction score
456
> The spells creates replicas of the unit itself, meaning, if it is used (or the target) by a hero, it will create heroes which show up an interface icon.
Remove the issue by creating the shadows for Neutral Passive and then change the shadow's color to color of the owner of caster. Like:

(ShadowCharge_InitializeCharge)
JASS:
set data.shadows[index] = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), id, dx, dy, facing)
call SetUnitColor(data.shadows[index], GetPlayerColor(owner))

(ShadowCharge_ChargeFinished)
JASS:
set data.shadows[index] = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), id, dx, dy, data.facing + 180)
call SetUnitColor(data.shadows[index], GetPlayerColor(data.owner))
 
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