Spell Phantom Dash

WolfieeifloW

WEHZ Helper
Reaction score
372
Phantom Dash
Created By: WolfieeifloW

Requires NewGen and TimerUtils
Optionally can use GTrigger
Version:
1.0


Credits:
Spell editing, idea, and triggers were created by WolfieeifloW.


If you take the time to download / look at these, please at least leave a comment.
Don't forget to rate also (using the stars
rating_5.gif
) !

phantomdashpicture.jpg

Screenshots serve no justice, try the spell out ingame.


Features:
-Leakless
-Lagless
-Eye-candy
-MUI


Spell Description
Dash to a target point, dealing damage to all enemies hit. Leaves an image at each enemy behind to confuse opponents.
Level 1: 50 damage.
Level 2: 100 damage.
Level 3: 150 damage.
Level 4: 200 damage.
Cooldown: 20/18/16/14 seconds.


Spell Code
JASS:
// +---------------------------------------------------+
// |          -=-=- Phantom Dash [v1.0] -=-=-          |
// |            -=-=- By WolfieeifloW -=-=-            |
// |        Requires JASS NewGen and TimerUtils        |
// |            Optionally can use GTrigger            |
// +---------------------------------------------------+
// |   Dash to a target point, dealing damage and      |
// |    creating an illusion for each enemy hit        |
// +---------------------------------------------------+
// |   -=-=- How To Implement -=-=-                    |
// |      1. Copy TimerUtils into your map             |
// |       b. Copy GTrigger into your map              |
// |      2. Copy this trigger into your map           |
// |      3. Copy the Phantom Dash, and Phantom Dash   |
// |          (Dummy) abilities into your map          |
// |      4. Make sure the 'Rawcodes' in the trigger   |
// |          match your codes in the Object Editor    |
// |      5. Customize the spell                       |
// |      6. Enjoy!                                    |
// +---------------------------------------------------+
// |   -=-=- Credits -=-=-                             |
// |      Credits are not needed, but appreciated      |
// |       Just don't claim this as yours              |
// +---------------------------------------------------+
// |   -=-=- Version History -=-=-                     |
// |      Version 1.0                                  |
// |       - Initial release                           |
// +---------------------------------------------------+

library PhantomDash initializer Init requires TimerUtils optional GTrigger

    globals
// +-----------------------------------+
// |      -=-=- MODIFY HERE -=-=-      |
// |      -=-=- MODIFY HERE -=-=-      |
// |      -=-=- MODIFY HERE -=-=-      |
// +-----------------------------------+
        private constant integer ABILITY_ID = 'PHAN' //Rawcode of the "Phantom Dash" ability
        private constant integer ILLUSION_ID = 'ILLU' //Rawcode of the "Phantom Dash (Dummy)" ability
        private constant integer DUMMY_ID = 'dUMM' //Rawcode of the dummy unit
        private constant integer ORDER_ID = 852274 //Order Id of the "Wand of Illusion" item
        private constant real SPEED = 1500. //Speed the unit moves at during the dash
        private constant real RADIUS = 125. //Radius the unit hits units in - Don't go below 100
        private constant string EFFECT = "Abilities\\Spells\\Items\\AIam\\AIamTarget.mdl"
        private constant string ATTACH = "origin"
        private constant string ANIMATION = "walk" //Animation to play during Phantom Dash
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MELEE //Attack Type of Phantom Dash
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL //Damage Type of Phantom Dash
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS //Weapon Type of Phantom Dash
    endglobals
    
    private function Damage takes integer level returns real
        return level * 50. //Damage that Phantom Dash deals
    endfunction
// +----------------------------------------------+
// |      -=-=- DO NOT TOUCH PAST HERE -=-=-      |
// |      -=-=- DO NOT TOUCH PAST HERE -=-=-      |
// |      -=-=- DO NOT TOUCH PAST HERE -=-=-      |
// +----------------------------------------------+    
    struct data
        unit caster
        real casterX
        real casterY
        real targetX
        real targetY
        real facing
        effect sfx
        group hit = CreateGroup()
        timer t
        
        method destroy takes nothing returns nothing
            call ReleaseTimer(.t)
            call GroupClear(.hit)
            call .deallocate()
        endmethod
    endstruct
    
    globals
        private constant integer HIT_RANGE = 50
        private constant real PERIOD = 0.03125
        private constant real DSPEED = SPEED * PERIOD
        private group GROUP = CreateGroup()
        private location loc
        private real x
        private real y
        private integer CurrentInstance
    endglobals
    
    private function Conditions takes nothing returns boolean
        local unit u = GetFilterUnit()
        local unit dummy
        local data d = CurrentInstance
        
        if IsUnitType(u, UNIT_TYPE_STRUCTURE) == false and GetWidgetLife(u) >= 0.405 and IsUnitEnemy(u, GetOwningPlayer(d.caster)) and IsUnitInGroup(u, d.hit) == false then
            call UnitDamageTarget(d.caster, u, Damage(GetUnitAbilityLevel(d.caster, ABILITY_ID)), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
            set dummy = CreateUnit(GetOwningPlayer(d.caster), DUMMY_ID, GetUnitX(u), GetUnitY(u), bj_UNIT_FACING)
            call UnitAddAbility(dummy, ILLUSION_ID)
            call SetUnitAbilityLevel(dummy, ILLUSION_ID, GetUnitAbilityLevel(d.caster, ABILITY_ID))
            call IssueTargetOrderById(dummy, ORDER_ID, d.caster)
            call UnitApplyTimedLife(dummy, 'BTLF', 1.)
            call GroupAddUnit(d.hit, u)
        endif
        set dummy = null
        set u = null
        return false
    endfunction
    
    private function Callback takes nothing returns nothing
        local data d = GetTimerData(GetExpiredTimer())
        
        set d.casterX = GetUnitX(d.caster)
        set d.casterY = GetUnitY(d.caster)
        set x = d.targetX - d.casterX
        set y = d.targetY - d.casterY
        if SquareRoot(x * x + y * y) >= HIT_RANGE then
            set d.facing = Atan2(d.targetY - d.casterY, d.targetX - d.casterX)
            set d.casterX = d.casterX + DSPEED * Cos(d.facing)
            set d.casterY = d.casterY + DSPEED * Sin(d.facing)
            call SetUnitX(d.caster, d.casterX)
            call SetUnitY(d.caster, d.casterY)
            set CurrentInstance = d
            call GroupEnumUnitsInRange(GROUP, GetUnitX(d.caster), GetUnitY(d.caster), RADIUS, Condition(function Conditions))
        else
            call SetUnitAnimation(d.caster, "stand")
            call PauseUnit(d.caster, false)
            call DestroyEffect(d.sfx)
            call d.destroy()
        endif
    endfunction
    
    private function Actions takes nothing returns boolean
        local data d
        
        if GetSpellAbilityId() == ABILITY_ID then
            set d = data.create()
            set d.caster = GetTriggerUnit()
            set loc = GetSpellTargetLoc()
            set d.casterX = GetUnitX(d.caster)
            set d.casterY = GetUnitY(d.caster)
            set d.targetX = GetLocationX(loc)
            set d.targetY = GetLocationY(loc)
            set d.sfx = AddSpecialEffectTarget(EFFECT, d.caster, ATTACH)
            set d.t = NewTimer()
            call SetTimerData(d.t, d)
            call SetUnitAnimation(d.caster, ANIMATION)
            call PauseUnit(d.caster, true)
            call TimerStart(d.t, PERIOD, true, function Callback)
            set loc = null
        endif
        return false
    endfunction
        
    private function Init takes nothing returns nothing
        local trigger trig = CreateTrigger()
        
        static if(LIBRARY_GTrigger) then
            call TriggerAddCondition(GT_RegisterStartsEffectEvent(trig, ABILITY_ID), Condition(function Actions))
        else
            call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(trig, Condition(function Actions))
        endif
        set trig = null
    endfunction
    
endlibrary

Test map included.
Constructive criticism is welcomed, flaming is not.
This spell will only be submitted to TH.net.
Do not redistribute or modify without permission.
Credits are not needed but are appreciated.
Just don't claim the spell as yours.

Hope you enjoy it!​

Changelog
v1.0
- Initial release
 

Attachments

  • opt-Phantom_Dash_v10.w3x
    49.6 KB · Views: 460

BlackRose

Forum User
Reaction score
239
[LJASS]private constant real RADIUS = 125. //Radius the unit hits units in - Don't go below 100[/LJASS]

Some explanation would be nice. I've seen your other thread, but a simple statement explaining why users shouldn't go below the 100 radius value is nice.

• You don't comment the EFFECT and ATTACH globals? I have no idea what effect it is for.
• Function Damage should be a constant function so it is inlineable.
• You are leaking groups. You are creating them on every spell cast, but you aren't destroying them - only clearing them. May I suggest you use GroupUtils or recycle them properly?
• You should make use of the create method that structs provide. That way, you can avoid the use of having to a local integer. Also, it's better encapsulation practice.
• In some patch, I can't remember, natives [LJASS]GetSpellTargetX[/LJASS] and [LJASS]GetSpellTargetY[/LJASS] were added. You should make use of them instead of using locations.
• Why are variables x and y not local to the Callback function? Any speed difference should be neglible. In addition, you're only using them within that function.
• In function Callback, that distance comparison line should square both sides of the equation, so you don't have to call a [LJASS]SquareRoot[/LJASS]. That is, it should be [LJASS](x * x + y * y) >= HIT_RANGE * HIT_RANGE[/LJASS] - However, [LJASS]HIT_RANGE * HIT_RANGE[/LJASS] should be a new constant variable.
• You should perform the GroupEnum call before you offset d.casterX. That way, you won't have to call GetUnitX and GetUnitY twice in that function. Alternatively, if you want the GroupEnum to be centered directly on the caster, store the caster's x into a variable.
• Pausing units for spells isn't recommended. What if another script unpauses it during operation?
• You have x and y as globals, but you don't have u or dummy in Conditions as a global? What is this. Pick one.
• You don't need to declare data d, just use CurrentInstance directly, saves a local integer declaration.
• The spell will adjust to the casting unit's ability level for stats (like damage). It should take the level at the time of casting as it's argument. Store level as a struct member, or store damage as a struct member.
• Speed and radius should also be configurable functions. Users may want leveling Phantom Dash to increase the movement speed.
• HIT_RANGE should be configurable. Users may make the Phantom Dash have a ranged effect once the dash is nearby. Consider all scenarios.
• Consider using Jesus4Lyf's DummyCaster. Don't create a dummy caster each time.

[DEL]I haven't actually tested your spell out, so I can't say anything on that.[/DEL]

EDIT: I have now tested the spell out. My comments are as follows:
• As a suggestion, you should trigger automatic selection of the hero on map initialization. This is for convenience
• The visual effects are not very well executed. I have considered that things are configurable - but I still wish to comment on what you have presented. The hero doesn't dash towards the targeted point as the spell tooltip says. What happens instead is the hero is simply being moved across in a straight line. It doesn't look like a dash at all. I mean, the hero even plays it's random stand animations during it's so called "dash". In addition, the attached effect does not last on the caster; the visual effect ends before the caster is done moving for slower speeds. Also, the "spell" animation the hero throws doesn't particularly look good too. If it's a Phantom Dash, why does the hero lift his arms up?
• Going back on a previous point, the pause mechanic of the spell is horrible! What if I wanted to cast something like Thunder Clap during that dash? I wouldn't be able to because of the pause from the code and the pause from Channel. In your ability in the Object Editor, "Data - Disable Other Abilities" is true, how come?
• Why are the illusions being summoned really far with lots of variation from the targets hit? I'm getting illusions spawned from all angles on the targets when the hero dashes through them. This is due to the "Stats - Area of Effect" field in the Wand of Illusion ability in the Object Editor. It's offsetting the illusions by that amount.
• The "Stats - Target Allowed" field in the Phantom Dash (Dummy) ability should be changed so that it can target all types of units. I broke Phantom Dash by making the caster invulnerable; no illusions were being made when I used Phantom Dash.
• My hero died when I cast Phantom Dash, but it kept moving. This looks weird, I suggest you stop the dash if the caster dies/is removed by other code. I don't know about if the hero is to be hidden with [LJASS]ShowUnit[/LJASS], it would look weird if upon showing it again, it was suddenly displaced from the point it was hidden.
• The ability tooltip could do with a bit more - the numerical stat values could be added, but I don't think it's too important.
 

NoobImbaPro

You can change this now in User CP.
Reaction score
60
Where did you get this "•" ?? It's very nice for pointing things.

• Spell Effect is lost during movement, I meant you should kill it when your target reaches final point, the trails left from effect will only cover 500 wc3 meter units and not the whole distance. Find another more eye candy effect.
• Give more time to illusions as you can predict easily who is the real deal.
• On demo map give less hp to units, users like to kill creeps :D
JASS:

• Don't initialize the kind of non-static struct members, you create about 8000 empty and useless and leakful groups now on map initialization.
Use "set local.hit = CreateGroup" on struct create method and use "call DestroyGroup(this.hit)" on destroy method
• Use onDestroy method omg to avoid any struct instance problem.
 

inevit4ble

Well-Known Member
Reaction score
38
I like it! :D
I think its quite cool :thup:

But I did notice that it doesn't cast if you try to "shift+cast" (action queue).

But nice gj :)
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top