• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Shadow Image Blink v1.4

Video
Spell Code
JASS:
//***************************************************************************
//***************************************************************************
//***************************************************************************
//      S H A D O W   I M A G E   B L I N K
//                                          By: Elphis (Nyuu)
//      Version: 1.0
//
//      Sepll Description: 
//  `                       - Use dark power, creating an illusion and a energy 
//                          ball shot to the selected point and after energy ball hit the target point, 
//                          it will explodes and stuns enemies in 2/2.5/3/3.5 seconds and cause 50/100/150/200 
//                          damage in 300 range.
//          - Installation:
//                                - Import/copy Shadow Image Blink code to your map
//                                - Import/copy the custom ability and unit to your map and change the SPELL_ID, DSPELL_ID, DUMMY_BOLT and DUMMY_CASTER if needed
//                                - You may view the raw ID of the objects by pressing CTRL+D in the object editor
//                                - You may play with the configurables below
//          - Credit:
//                      - Unleashthepower.mdx - http://www.hiveworkshop.com/forums/models-530/unleash-power-243024/?prev=search%3DPower%26d%3Dlist%26r%3D20
//                      - TerrainPathability - http://www.wc3c.net/showthread.php?t=103862
//
//
//***************************************************************************
//***************************************************************************
//***************************************************************************
library ShadowImageBlink uses TerrainPathability

    globals
        //Spell rawcode, change if needed
        private constant    integer         SPELL_ID        =   'A000'
        //
        private constant    integer         DSPELL_ID       =   'A001'
        //Dummy unit rawcode (Bolt model) change if needed
        private constant    integer         DUMMY_BOLT      =   'e000'
        //Dummy caster rawcode, change if needed
        private constant    integer         CASTER_DUMMY    =   'e001'
        //Crow Form ability
        private constant    integer         CROW_FORM       =   'Amrf'
        
        //Animation play when the caster casting this spell
        private constant    string          ANIMATION       =   "spell"
        //Effect of spell
        private constant    string          BLINK_CAST      =   "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        //Effect of spell when begin & end spell
        private constant    string          BLINK_END       =   "war3mapImported\\Unleash the power.mdx"
        //Attachment of the caster when spell end (Using BLINK_END effect) 
        private constant    string          ATTACHMENT      =   "chest"
        //Order id of dummy caster casting the dummy ability
        private constant    string          ORDER_ID        =   "thunderbolt"
        //Dummy owner
        private constant    player          DUMMY_OWNER     = Player(15)
        //******************************DAMAGE DATA SETTINGS******************************
        private constant    attacktype      ATTACK_TYPE     =   ATTACK_TYPE_HERO
        private constant    damagetype      DAMAGE_TYPE     =   DAMAGE_TYPE_DEATH
        private constant    weapontype      WEAPON_TYPE     =   WEAPON_TYPE_CLAW_HEAVY_SLICE
        //********************************************************************************
        
        //Damage radius
        private constant    real            DAMAGE_RADIUS   =   300.
        //Damage base of this spell
        private constant    real            DAMAGE_BASE     =   50.
        //Animation speed of the caster unit when this spell is used
        private constant    real            ANIMATION_SPEED =   0.43
        //Spell period
        private constant    real            PERIODIC        =   .031250000
        //Virtual caster speed separator
        private constant    real            SPERATION_SPEED =   20.
        //Max height of the ball power
        private constant    real            MAX_HEIGHT      =   1000.
        
                                /*Fade settings*/
        private constant    integer         FADE_COUNT      =   10
        private constant    integer         FADE_FLASH      =   85
        private constant    integer         FADE_SPEED      =   10
        //********************************************************
        
        //***************************Non - Configurable*****************************
        /*                                                                        */
        /**/private constant    group           G               =   CreateGroup()/**/
        /*                                                                        */
        /**/private             integer         MUI         =   -1               /**/
        /*                                                                        */
        /**/private             integer array   StructData                       /**/
        /*                                                                        */
        /**/private constant    timer           TIMER           =   CreateTimer()/**/
        //**************************************************************************
        /**/private             unit            Dummy
        //**************************************************************************
        
    endglobals

    //************************************DO NOT MODIFY ANYTHING BELOW************************************
    
    //*******************Damage setting***********************************
    /**/constant function getDamage takes integer lvl returns real       /**/
    /**/    return DAMAGE_BASE * lvl                                  /**/
    /**/endfunction                                                   /**/
    //********************************************************************
    
    //*******************Filter Function***********************************
    /**/function filterFunc takes player owner,unit filterUnit returns boolean
    /**/                                                         //Assure a better death check//
    /**/return not IsUnitType(filterUnit,UNIT_TYPE_DEAD) and /**/GetUnitTypeId(filterUnit) != 0/**/ and IsUnitEnemy(filterUnit,owner)
    /**/endfunction
    //********************************************************************
    
    private struct ShadowImageBlink
        
        unit caster
        unit dummy_1
        unit dummy_2
        unit dummy_3
        
        integer fade = 255
        integer lvl
        integer fade_count = FADE_COUNT
        
        boolean fade_done = false
        boolean subtract = true
        
        player owner
        
        real cos
        real sin
        real facing
        real total = -SPERATION_SPEED
        real angle
        real fade_seperation
        real targetx
        real targety
        real speration_speed
        real height
        real dmg
    
        static method onPeriodic takes nothing returns nothing
            
            local integer i = 0
            local thistype this
            local real x
            local real y
            local real o
            local real z
            local unit f = null
            
            loop
                exitwhen i > MUI
                
                set this = StructData[i]
                
                if subtract then
                
                    set x = GetUnitX(dummy_1) + cos
                    set y = GetUnitY(dummy_1) + sin
                    
                    set o = GetUnitX(dummy_2) - cos
                    set z = GetUnitY(dummy_2) - sin
                
                    if fade > fade_seperation then
                    
                        if not fade_done then
                            set fade = fade - FADE_SPEED
                        else
                            set fade = 0
                        endif
                        
                    elseif fade_done then
                    
                        set fade = FADE_FLASH
                        
                        if fade_count > 0 then
                            set fade_count = fade_count - 1
                        else
                            set subtract = false
                            set fade_done = false
                            set fade = 0
                            
                            set cos = Cos(angle)
                            set sin = Sin(angle)
                            
                            set x = targetx + total * cos
                            set y = targety + total * sin
                            
                            call SetUnitX(dummy_1,x)
                            call SetUnitY(dummy_1,y)
                            
                            call DestroyEffect(AddSpecialEffect(BLINK_CAST,x,y))
                            
                            set x = targetx - total * cos
                            set y = targety - total * sin
                            
                            call SetUnitX(dummy_2,x)
                            call SetUnitY(dummy_2,y)
                            
                            call DestroyEffect(AddSpecialEffect(BLINK_CAST,x,y))
                            
                            set cos = speration_speed*Cos(angle)
                            set sin = speration_speed*Sin(angle)
                            
                        endif
                        
                    else
                        set fade_done = true
                    endif
                    
                    set total = total + speration_speed
                    
                endif
                
                if not fade_done and not subtract then
                
                    set x = GetUnitX(dummy_1) - cos
                    set y = GetUnitY(dummy_1) - sin
                    
                    set o = GetUnitX(dummy_2) + cos
                    set z = GetUnitY(dummy_2) + sin
                    
                    if fade_count < FADE_COUNT then
                    
                        set fade_count = fade_count + 1
                        
                        if fade == 0 then
                            set fade = FADE_FLASH
                        else
                            set fade = 0
                        endif
                        
                        if fade_count == FADE_COUNT then
                            set fade = 0
                        endif
                        
                    elseif fade < 255 then
                        set fade = fade + FADE_SPEED
                    else
                        
                        set StructData[i] = StructData[MUI]
                        set StructData[MUI] = -2
                        set MUI = MUI - 1
                        
                        if MUI == -1 then
                            call PauseTimer(TIMER)
                        endif
                        
                        call DestroyEffect(AddSpecialEffectTarget(BLINK_END,caster,ATTACHMENT))
                        
                        call GroupEnumUnitsInRange(G,targetx,targety,DAMAGE_RADIUS,null)
                        
                        call SetUnitAbilityLevel(Dummy,DSPELL_ID,lvl)
                        
                            loop
                                set f = FirstOfGroup(G)
                                exitwhen f == null
                                if filterFunc(owner,f) then
                                    call SetUnitX(Dummy,GetUnitX(f))
                                    call SetUnitY(Dummy,GetUnitY(f))
                                    call IssueTargetOrder(Dummy,ORDER_ID,f)
                                    call UnitDamageTarget(caster,f,dmg,true,false,ATTACK_TYPE,DAMAGE_TYPE,WEAPON_TYPE)
                                endif
                                call GroupRemoveUnit(G,f)
                            endloop
                        
                        call SetUnitX(caster,targetx)
                        call SetUnitY(caster,targety)
                        
                        call ShowUnit(caster,true)
                        
                        call SetUnitTimeScale(caster,1.)
                        
                        if GetLocalPlayer() == owner then
                            call ClearSelection()
                            call SelectUnit(caster,true)
                        endif
                        
                        call RemoveUnit(dummy_1)
                        call RemoveUnit(dummy_2)
                        call RemoveUnit(dummy_3)
                        
                        set owner = null
                        set dummy_1 = null
                        set dummy_2 = null
                        set dummy_3 = null
                        set caster = null
                        
                        call destroy()
                    endif
                endif
                
                call SetUnitX(dummy_1,x)
                call SetUnitY(dummy_1,y)
                
                call SetUnitX(dummy_2,o)
                call SetUnitY(dummy_2,z)
                
                call SetUnitFlyHeight(dummy_3,GetUnitFlyHeight(dummy_3)-height,0.)
                
                call SetUnitVertexColor(dummy_1,255,255,255,fade)
                call SetUnitVertexColor(dummy_2,255,255,255,fade)
                
                set i = i + 1
            endloop
            
        endmethod
        
        static method onCast takes nothing returns boolean
            
            local thistype this
            local real x
            local real y
            local playercolor owner_color
            local integer i
            
            if GetSpellAbilityId() == SPELL_ID then
                set x = GetSpellTargetX()
                set y = GetSpellTargetY()
            
                if IsTerrainWalkable(x,y) then
                
                    set this = allocate()
                    
                    set MUI = MUI + 1
                    set StructData[MUI] = this
                    
                    set caster = GetTriggerUnit()
                    
                    set owner = GetTriggerPlayer()
                    
                    set owner_color = GetPlayerColor(owner)
                    
                    set lvl = GetUnitAbilityLevel(caster,SPELL_ID)
                    
                    set dmg = getDamage(lvl)
                         
                    call ShowUnit(caster,false)
                    
                    set targetx = x
                    set targety = y
                    
                    set fade_seperation = FADE_FLASH/3
                    
                    set x = GetUnitX(caster)
                    set y = GetUnitY(caster)
                    
                    set angle = 57.29583 * Atan2(targety - y, targetx - x) + 90.
                    set facing = angle - 90.
                    set angle = angle *.0174533
                    
                    set speration_speed = SPERATION_SPEED/FADE_SPEED
                    
                    set height = MAX_HEIGHT/(SPERATION_SPEED*FADE_SPEED/speration_speed-10.)
                    
                    set cos = speration_speed * Cos(angle)
                    set sin = speration_speed * Sin(angle)
                    
                    set i = GetUnitTypeId(caster)
                    
                    set dummy_1 = CreateUnit(DUMMY_OWNER,i,x,y,facing)
                    
                    if UnitAddAbility(dummy_1,'Aloc') then
                        call SetUnitPathing(dummy_1,false)
                        call SetUnitX(dummy_1,x)
                        call SetUnitY(dummy_1,y)
                    endif
                    
                    set dummy_2 = CreateUnit(DUMMY_OWNER,i,x,y,facing)
                    
                    if UnitAddAbility(dummy_2,'Aloc') then
                        call SetUnitPathing(dummy_2,false)
                        call SetUnitX(dummy_2,x)
                        call SetUnitY(dummy_2,y)
                    endif
                    
                    set dummy_3 = CreateUnit(DUMMY_OWNER,DUMMY_BOLT,targetx,targety,facing)
                    
                    if UnitAddAbility(dummy_3,CROW_FORM) then
                        call UnitRemoveAbility(dummy_3,CROW_FORM)
                        call SetUnitFlyHeight(dummy_3,MAX_HEIGHT,0.)
                    endif
                    
                    //********************************************
                    call SetUnitColor(dummy_1,owner_color)
                    call SetUnitColor(dummy_2,owner_color)
                    //********************************************
                    call SetUnitTimeScale(caster,ANIMATION_SPEED)
                    call SetUnitTimeScale(dummy_1,ANIMATION_SPEED)
                    call SetUnitTimeScale(dummy_2,ANIMATION_SPEED)
                    //********************************************
                    call SetUnitAnimation(caster,ANIMATION)
                    call SetUnitAnimation(dummy_1,ANIMATION)
                    call SetUnitAnimation(dummy_2,ANIMATION)
                    //********************************************
                    
                    if MUI == 0 then
                        call TimerStart(TIMER,PERIODIC,true,function thistype.onPeriodic)
                    endif
                    
                endif
            endif
            
            set owner_color = null
            
            return false
        endmethod

        static method onInit takes nothing returns nothing
        
            local integer i = 0
            local trigger t = CreateTrigger()
            
            loop
                exitwhen i > 15
                
                call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)
                
                set i = i + 1
            endloop
            
            call TriggerAddCondition(t,function thistype.onCast)
            
            set Dummy = CreateUnit(DUMMY_OWNER,CASTER_DUMMY,0.,0.,0.)
            
        endmethod
    
    endstruct
    
endlibrary
Screen Shot
p10.jpg

p110.jpg

p210.jpg

p310.jpg

Changelog

v1.0: First release version.
v1.1: Code imporved, leaks fixed, minor change
v1.2: Use TerrainPathability (http://www.wc3c.net/showthread.php?t=103862)
v1.2b: Remove Dummy Init.
v1.3: Minor improvement.
v1.4: Minor improvement.
Keywords:
shadow,image,blink
Contents

Shadow Image Blink (Map)

Reviews
4th Apr 2016 Your resource has been reviewed by BPower. In case of any questions or for reconfirming the moderator's rating, please make use of the Quick Reply function of this thread. Review: Overall acceptable coding. Here and there is...

Moderator

M

Moderator

4th Apr 2016

General Info

Your resource has been reviewed by BPower.
In case of any questions or for reconfirming the moderator's rating,
please make use of the Quick Reply function of this thread.
Review:

Overall acceptable coding. Here and there is room for improvement!
Concept and effects are creative and the spell is fun to cast.

You set the casters position before using ShowUnit, therefore the unit appearance
sometimes doesn't match the missile impact point for crowded locations.
It's a visual and not a technical flaw.
A knockback implementation could eventually solve that issue.

Troubleshooting:

  • Your whitespaces appear random, thus hurt the read-ability. Honestly it was extremly hard to read your code.
    The code misses structuring or at least a few comments.
    ---
  • You are using magic numbers in your code which could preferably be replaced with named constants.
    For example: set angle = angle *.0174533 or set angle = 57.29583 * Atan2(targety - y, targetx - x) + 90.
    ---
  • After calling destroy and nulling members your code calls a few native functions which take nulled unit arguments.
    ---
  • I don't see the purpose of set StructData[MUI] = -2, because you don't run any debug checks at all.
    --
  • local unit f = null. Initializing f isn't required here.

Review changelog:
  1. Review 19th Mar 2014; BPower
 
Level 6
Joined
May 9, 2010
Messages
127
With the video, I noticed the orb goes sometimes too far, sometimes it reaches exact destination. I think you should make it so that if the person blinks 2 feet in front of him have the same animation length but the orb doesn't go too fast. On the other hand, I'd like the orb to go at the normal rate if it's used on max distance.

I love the animation you put into it, looks really good.
 
  • You should probably separate the configurable globals from the ones that aren't.
  • dummy_3 may leak becauseUnitApplyTimedLifedoesn't remove the unit.
  • I would consider dummy recycling. You can either handle it yourself or use a library like DummyUnitStack (would fix the dummy leak also).
  • The "Damage settings" part at the bottom needs to be placed at the top as a constant function.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
You are a creative mind and I really like the concept.

- Aslong as you don't filter out computer players you could simply use call TriggerRegisterAnyUnitEventBJ.
- I really dislike that you labeled your method onDamage, because on damage indicates actions performed on damage event.
- You could use a FoG, because it's faster the more units are picked.
- You should either use a seconds global block or move the non configurable block into the struct.
- 500 aoe doesn't fit to the effects at all, it is too large.
The "Damage settings" part at the bottom needs to be placed at the top as a constant function.
Indeed!
 
You have the small problem of the missile not going correctly from the starting point to the point where the caster appears. One solution would be to adjust the speed of the missile to make it go at the right speed. The other, which I think is computationally much less costly, is to go from the kinematic approach to a purely geometric one:

While the timer loop runs, have a variable t count up from 0 to 1 (so that it reaches 1 exactly when the timer has finished) and set the position of the missile as:
JASS:
set x1=GetUnitX(caster)
set y1=GetUnitY(caster)
set x2=targetx
set y2=targety
call SetUnitX(u,(1-t)*x1+t*x2)
call SetUnitY(u,(1-t)*y1+t*y2)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Yeah ...., is this a joke ^^?
JASS:
                    /**************************Damage settings**********************************/
                    /**/set dmg = damageSetting(GetUnitAbilityLevel(caster,SPELL_ID))        /**/
                    /***************************************************************************/
if not IsTerrainPathable(x,y, PATHING_TYPE_WALKABILITY) then --> You could use TerrainPathability by Rising Dusk or IsTerrainWalkable by Vexorian as requirement.

The effects are nice. The coding looks oke.

Edit:
- Move the timer into the non-configurable part
- Use just one dummy for the stun, there is no benefit in creating one per struct instance only cons.
- TargetFilter for damaged units should be part of the user configuration.
- playe is a horrible variable name --> owner??
- You pass null arguments into functions after destroying the instance. This is bad coding. Note that in many other cases the thread will crash in such a case.
Here it doesn't really matter, but please try to avoid it in future.

Optional:
- Player(15) could be stored
- function Damage ---> function GetDamage
- GetLocalPlayer could be stored.
- 'Aloc' could be stored, you do store 'Armf'
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
You check twice for boolean subtract. This could be merged.
The timer shouldn't be in the user configuration block
You should stick to one naming conventions. We prefer the JPAG --> fade_sep should be fadeSep.

List of horrible variables names and how they actually should be:

  • BLI_END --> BLINK_END
  • BLI_CAST --> BLINK_CAST
  • FAD_VIRTUAL --> FADE_VIRTUAL ???? I didn't know what this is, so I googled it. Apparently a DJ software.
  • FAD_SPEED --> FADE_SPEED
  • playe --> owner
  • ANI_SPEED --> ANIMATION_SPEED
  • SEP_SPEED --> SEPERATION_SPEED
  • all variables using _ --> Not mandatory, but it's logical to stick to the original Blizzard naming convention.

As the requirements in the Spell sections are not as high as in the JASS section, I'll tend to approve it later without these fixes done.
However I hope you do make those changes mentioned and care more about the naming convention in the future.
 

Wrda

Spell Reviewer
Level 28
Joined
Nov 18, 2012
Messages
1,993
JASS:
loop
  exitwhen i > 15                
  call TriggerRegisterPlayerUnitEvent(t,Player(i),EVENT_PLAYER_UNIT_SPELL_EFFECT,null)                
  set i = i + 1
endloop
What is this for?
JASS:
TriggerRegisterAnyUnitEventBJ
does the work, it's the same, so you should replace that with it.
Correct me if I'm wrong.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Old review. Posted for reference reasons.

07:23, 19th Mar 2014
BPower:
Very readable and efficient coding. Good job.
Concept and effects are very creative, which makes the spell really fun to cast.
The null arguments you pass in during the last loop is the only flaw I've found.

08:11, 17th Mar 2014
BPower:

Please fix the variable names in the configuration block and move the timer declaration to the bottom.
This may sound like peanuts to you, but in my eyes the user configuration has to be straightforward and clear.
There is no room for half-done variable names.
(While for instance AID for ability id is fair enough, ABILIT_ID isn't.)

The struct member variables could be fixed too, but it's not mandatory as the code is readable. Furthermore it is very well coded and a cool concept.
Also refer to my last post.
 
Top