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

Sleight of Fist v1.22

Heavily based on Ember Spirit's Sleight of Fist from Dota 2. See youtube video of sleight of Fist in Dota 2.


Preview GIFs

151208d1450759456-sleight-fist-v1-00-sleight-fist.gif



JASS:
scope SleightOfFistSpell/* v1.22
                
                by Flux
    http://www.hiveworkshop.com/forums/members/flux/

  DESCRIPTION:

    Ember Spirit dashes around with blazing speed, attacking all 
    enemies in the targeted area of effect, then returning to his 
    start location. Deals bonus damage to heroes, and less damage 
    to creeps.
    
  NOTES:
    Required:
        - BonusMod 
          http://www.hiveworkshop.com/forums/graveyard-418/system-bonus-mod-setunitmaxstate-65622/
    optionally requires:
        - Table
          http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
        - MissileRecycler
          http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
        - SpellEffectEvent
          http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
    
  CREDITS:
    Earth-Fury: BonusMod
    Bribe:      Missile Recycler, SpellEffectEvent, Table
    ILH:        Imported Slash Effect
    Vexorian:   Attachable Dummy model

*/    
    native UnitAlive takes unit u returns boolean
    
    //==================================================================
    //======================  CONFIGURATION  ===========================
    //==================================================================
    globals
        //------------------------------------------------------
        // -----------------   RAWCODES   ----------------------
        //------------------------------------------------------
        //The Rawcode of the Spell
        private constant integer SPELL_ID = 'Asof'
        
        //The Rawcode of a Spell that will immediately ends 
        //Sleight of Fist when cast, e.g. Fire Remnant
        private constant integer SPELL_END_ID = 'A000'
        
        //While in Sleight of Fist, caster have maximum attack speed
        //provided by this ability
        private constant integer SPELL_BONUS_ATTACKSPEED = 'SfSB'
        
        //Deals 50% damage to non-hero units
            //Spell Book Rawcodes
        private constant integer DAMAGE_DEC = 'SfHL'
            //Command Aura based Ability
        private constant integer DECREASE_DAMAGE = 'SfLD'
            //Buff
        private constant integer DAMAGE_BUFF = 'Bsof'
        
        //Ability that transform the caster to a new unit with no
        //Attack Damage Point allowing him to attack instantly
        private constant integer SLEIGHT_OF_FIST_TRANSFORM = 'SfTr'
        //Ability that transform caster back to normal
        private constant integer SLEIGHT_OF_FIST_BACK = 'SfBa'
        
        //To prevent caster from attacking twice, his attack is temporarily
        //disable when waiting for the next jump
        private constant integer DISABLE_ATTACK = 'SfNA'
        
        //This spell creates a remnant at your location since the caster
        //will eventually return to that position after slashing all targets
        //Dummy unit for remnant
        private constant integer DUMMY_ID = 'dumi'
        
        //------------------------------------------------------
        // -------------- REMNANT PROPERTIES  ------------------
        //------------------------------------------------------
        //Remnant color
        private constant integer REMNANT_RED = 255
        private constant integer REMNANT_GREEN = 120
        private constant integer REMNANT_BLUE = 20
        private constant integer REMNANT_ALPHA = 220
        
        //Value between 0 and 255, where 0 means fully transparent
        //Opacity of the caster casting Sleight of Fist
        private constant integer OPACITY = 130
        
        //------------------------------------------------------
        // ----------------- VISUAL EFFECTS  -------------------
        //------------------------------------------------------
        
        //Units that will be attacked will be marked
        private constant string TARGET_MARKING = "Abilities\\Spells\\Orc\\TrollBerserk\\HeadhunterWEAPONSLeft.mdl"
        
        //Where the TARGET_MARKING will appear
        private constant string TARGET_MARKING_ATTACHMENT = "overhead"
        
        //Area of Effect Border
        private constant string AOE_BORDER = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
        
        //How far away AOE_BORDER are to each other
        private constant integer AOE_BORDER_SPACING = 64
        
        //While casting Sleight of Fist, caster will have an attached sfx
        private constant string CASTER_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl" 
        
        //When Sleight of Fist is cast, it leaves a remnant in the casting position
        private constant string REMNANT_MODEL = "Units\\Creeps\\FirePandarenBrewmaster\\FirePandarenBrewmaster.mdl"
        
        private constant string SLICED_SFX = "war3mapImported\\Slash.mdx"
        
        //The caster's animation when no unit is hit
        private constant string ANIMATION = "spell"
        
        
        //------------------------------------------------------
        // -----------  SPELL TIMING PROPERTIES  ---------------
        //------------------------------------------------------
        //How long the caster will wait before it attacks the next target.
        //Do not use values lower than 0.15 (based on Tested Values at Reaction Delay = 0)
        private constant real JUMP_INTERVAL = 0.2
        
        //The delay before attacking the first unit and before returning to the original location
        //Can be any value, the purpose is to create a tail in the Sfx
        private constant real FIRST_JUMP_DELAY = 0.05
        
        //How much faster the attack animation when in Sleight of Fist
        private constant real TIME_SCALE = 5.0
        
        //------------------------------------------------------
        // --------------  PRELOADING OPTION  ------------------
        //------------------------------------------------------
        //Preload abilities so that it won't lag the first time the spell is used
        private constant boolean PRELOAD = true
        
    endglobals
    
    //------------------------------------------------------------------
    // --------------------  SPELL PROPERTIES  -------------------------
    //------------------------------------------------------------------
    
    //Must match Object Editor Data
    private function AreaOfEffect takes integer lvl returns real
        return lvl*100.0 + 150.0
    endfunction
    
    //Bonus Damage to Units belonging in BonusFilter
    private function BonusDamage takes integer lvl returns integer
        return lvl*20   //20, 40, 60, 80
    endfunction
    
    //Determines what gets targeted
    private function TargetFilter takes player owner, unit target returns boolean
        return (UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and not IsUnitType(target, UNIT_TYPE_FLYING))
    endfunction
    
    //Determines what gets extra damage
    private function BonusFilter takes unit target returns boolean
        return IsUnitType(target, UNIT_TYPE_HERO)
    endfunction
    
    
    
    //Other properties can be configured in Object Editor
    
    //==================================================================
    //====================  END CONFIGURATION  =========================
    //==================================================================
    
    
    private struct spell
        
        //Handles
        private unit caster
        private unit remnant
        private player owner
        private group g
        private timer t
        private effect casterSfx1
        private effect casterSfx2
        private effect remnantModel
        
        private integer lvl
        private real x
        private real y
        private integer bonus
        private integer bonusDmg
        private boolean firstTarget
        
        private static thistype instance
        
        static if LIBRARY_Table then
            private static Table tb
        else
            private static hashtable hash = InitHashtable()
        endif
        
        private static real targetX
        private static real targetY
        private static real aoe
        private static boolean groupHasUnits
        
        private method destroy takes nothing returns nothing
            local unit u
            local integer id
            //Revert caster properties back to normal
            call UnitAddAbility(.caster, SLEIGHT_OF_FIST_BACK)
            call UnitRemoveAbility(.caster, SLEIGHT_OF_FIST_BACK)
            call UnitClearBonus(.caster, BONUS_DAMAGE)
            call SetUnitTimeScale(.caster, 1)
            //If all units were not attacked for whatever
            //reason, remove the markings on unattacked units
            loop
                set u = FirstOfGroup(.g)
                exitwhen u == null
                call GroupRemoveUnit(.g, u)
                //Remove Target Marking
                static if LIBRARY_Table then
                    set id = GetHandleId(u)
                    call DestroyEffect(tb.effect[id])
                    call tb.remove(id)
                else
                    set id = GetHandleId(u)
                    call DestroyEffect(LoadEffectHandle(hash, id, 0))
                    call FlushChildHashtable(hash, id)
                endif
            endloop
            //Clean Table/Hash
            static if LIBRARY_Table then
                call tb.remove(GetHandleId(.caster))
                call tb.remove(GetHandleId(.t))
            else
                call FlushChildHashtable(hash, GetHandleId(.caster))
                call FlushChildHashtable(hash, GetHandleId(.t))
            endif
            //Remove the Damage Buff
            call UnitRemoveAbility(.caster, DAMAGE_BUFF)
            //destroy handles
            call DestroyEffect(.casterSfx1)
            call DestroyEffect(.casterSfx2)
            call DestroyGroup(.g)
            call DestroyTimer(.t)
            call DestroyEffect(.remnantModel)
            static if LIBRARY_MissileRecycler then
                call RecycleMissile(.remnant)
                call SetUnitScale(.remnant, 0, 0, 0)
            else
                call KillUnit(.remnant)
            endif
            //not necessary, but I'll do it anyway
            set .casterSfx1 = null
            set .casterSfx2 = null
            set .remnantModel = null
            set .caster = null
            set .remnant = null
            set .owner = null
            set .g = null
            set .t = null
            //necessary
            call .deallocate()
        endmethod
        
        private static method delayedDestroy takes nothing returns nothing
            local integer id = GetHandleId(GetExpiredTimer())
            static if LIBRARY_Table then
                if tb.has(id) then
                    call thistype(tb[id]).destroy()
                endif
            else
                if HaveSavedInteger(hash, id, 0) then
                    call thistype(LoadInteger(hash, id, 0)).destroy()
                endif
            endif
        endmethod
        
        private static method removeAtk takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local integer id = GetHandleId(t)
            local thistype this
            //Only remove the attack if there is still an existing
            //instance of the spell
            static if LIBRARY_Table then
                if tb.has(id) then
                    set this = tb[id]
                    call UnitAddAbility(.caster, DISABLE_ATTACK)
                endif
            else
                if HaveSavedInteger(hash, id, 0) then
                    set this = LoadInteger(hash, id, 0)
                    call UnitAddAbility(.caster, DISABLE_ATTACK)
                endif
            endif
            call DestroyTimer(t)
            set t = null
        endmethod
        
        private static method periodic takes nothing returns nothing
            static if LIBRARY_Table then
                local thistype this = tb[GetHandleId(GetExpiredTimer())]
            else
                local thistype this = LoadInteger(hash, GetHandleId(GetExpiredTimer()), 0)
            endif
            local integer id
            local unit u
            local real x
            local real y
            local real angle
            local boolean exit = false
            local integer hid
            local timer delay
            local unit slicedSfx
            local fogmodifier fog
            loop
                set u = FirstOfGroup(.g)
                call GroupRemoveUnit(.g, u)
                if u == null then
                    //Return caster back to his casting location
                    call SetUnitX(.caster, .x)
                    call SetUnitY(.caster, .y)
                    call IssueImmediateOrderById(.caster, 851972)
                    call TimerStart(.t, FIRST_JUMP_DELAY, false, function thistype.delayedDestroy)
                    exitwhen true
                else
                    set x = GetUnitX(u)
                    set y = GetUnitY(u)
                    //Reveal Target
                    set fog = CreateFogModifierRadius(.owner, FOG_OF_WAR_VISIBLE, x, y, 150, false, false)
                    call FogModifierStart(fog)
                    //If the target happens to die, check agan using TargetFilter
                    if IsUnitVisible(u, .owner) and TargetFilter(.owner, u) then
                        //Enable the caster to attack
                        call UnitRemoveAbility(.caster, DISABLE_ATTACK)
                        //Altering Damage based on Target
                        if BonusFilter(u) then
                            if .bonus != 1 then
                                call UnitRemoveAbility(.caster, DAMAGE_DEC)
                                call UnitRemoveAbility(.caster, DAMAGE_BUFF)
                                call UnitSetBonus(.caster, BONUS_DAMAGE, .bonusDmg)
                                set .bonus = 1
                            endif
                        else
                            if .bonus != -1 then
                                call UnitClearBonus(.caster, BONUS_DAMAGE)
                                call UnitAddAbility(.caster, DAMAGE_DEC)
                                set .bonus = -1
                            endif
                        endif
                        
                        set angle = GetRandomReal(0, 360)   //random angle for the sfx
                        static if LIBRARY_MissileRecycler then
                            set slicedSfx = GetRecycledMissile(x, y, 0, angle)
                            call DestroyEffect(AddSpecialEffectTarget(SLICED_SFX, slicedSfx, "origin"))
                            call RecycleMissile(slicedSfx)
                        else
                            set slicedSfx = CreateUnit(.owner, DUMMY_ID, x, y, angle)
                            call DestroyEffect(AddSpecialEffectTarget(SLICED_SFX, slicedSfx, "origin"))
                            call UnitApplyTimedLife(slicedSfx, 'BTLF', 1.0)
                        endif
                        set slicedSfx = null
                        
                        //Position the caster where he doesn't need to turn.
                        set angle = -GetUnitFacing(.caster)*bj_DEGTORAD
                        call SetUnitX(.caster, x + 50*Cos(angle))
                        call SetUnitY(.caster, y + 50*Sin(angle))
                        //Order to attack
                        call IssueTargetOrderById(.caster, 851983, u)
                        //Remove Attack timer
                        set delay = CreateTimer()
                        static if LIBRARY_Table then
                            set tb[GetHandleId(delay)] = this
                        else
                            call SaveInteger(hash, GetHandleId(delay), 0, this)
                        endif
                        //0.103 is enough time to make the caster attack the target once
                        //See here http://www.hiveworkshop.com/forums/pastebin_data/scr8y9/Damage%20Delay.jpg
                        // ^Those results were consistent/precise.
                        call TimerStart(delay, 0.103, false, function thistype.removeAtk)
                        set delay = null
                        if .firstTarget then
                            set .firstTarget = false
                            call TimerStart(.t, JUMP_INTERVAL, true, function thistype.periodic)
                        endif
                        //Only exit the loop when a visible target is attacked
                        set exit = true
                    endif
                    call DestroyFogModifier(fog)
                    set fog = null
                    //Remove Target Marking
                    static if LIBRARY_Table then
                        set id = GetHandleId(u)
                        call DestroyEffect(tb.effect[id])
                        call tb.remove(id)
                    else
                        set id = GetHandleId(u)
                        call DestroyEffect(LoadEffectHandle(hash, id, 0))
                        call FlushChildHashtable(hash, id)
                    endif
                endif
                set u = null
                exitwhen exit
            endloop
        endmethod
        
        private static method enumGroup takes nothing returns nothing
            local unit u = GetEnumUnit()
            local thistype this = instance
            local fogmodifier fog
            if TargetFilter(.owner, u) and IsUnitInRangeXY(u, targetX, targetY, aoe) then
                set fog = CreateFogModifierRadius(.owner, FOG_OF_WAR_VISIBLE, GetUnitX(u), GetUnitY(u), 150, false, false)
                call FogModifierStart(fog)
                if not IsUnitInvisible(u, .owner) then
                    //Mark unit with SFX
                    static if LIBRARY_Table then
                        set tb.effect[GetHandleId(u)] = AddSpecialEffectTarget(TARGET_MARKING, u, TARGET_MARKING_ATTACHMENT)
                    else
                        call SaveEffectHandle(hash, GetHandleId(u), 0, AddSpecialEffectTarget(TARGET_MARKING, u, TARGET_MARKING_ATTACHMENT))
                    endif
                    if not groupHasUnits then
                        set groupHasUnits = true
                    endif
                else
                    call GroupRemoveUnit(.g, u)
                endif
                call DestroyFogModifier(fog)
                set fog = null
            else
                call GroupRemoveUnit(.g, u)
            endif
            set u = null
        endmethod
        
        private static method noHitAnimation takes nothing returns nothing
            local timer delay = GetExpiredTimer()
            local integer id = GetHandleId(delay)
            static if LIBRARY_Table then
                call SetUnitAnimation(tb.unit[id], ANIMATION)
                call tb.remove(id)
            else 
                call SetUnitAnimation(LoadUnitHandle(hash, id, 0), ANIMATION)
                call FlushChildHashtable(hash, id)
            endif
            call DestroyTimer(delay)
            set delay = null
        endmethod
        
        private static method onCast takes nothing returns boolean
            local thistype this = allocate()
            local integer id
            local real end = 2*bj_PI
            local real i = 0
            local timer delay
            set .caster = GetTriggerUnit()
            set id = GetHandleId(.caster)
            //If the caster is currently in Sleight of Fist, destroy it
            static if LIBRARY_Table then
                if tb.has(id) then
                    call thistype(tb[id]).destroy()
                endif
            else
                if HaveSavedInteger(hash, id, 0) then
                    call thistype(LoadInteger(hash, id, 0)).destroy()
                endif
            endif
            set .owner = GetTriggerPlayer()
            set .lvl = GetUnitAbilityLevel(caster, SPELL_ID)
            set targetX = GetSpellTargetX()
            set targetY = GetSpellTargetY()
            set .x = GetUnitX(.caster)
            set .y = GetUnitY(.caster)
            set aoe = AreaOfEffect(.lvl)
            set .firstTarget = true
            set .g = CreateGroup()
            //Group globals
            set instance = this
            set groupHasUnits = false
            call GroupEnumUnitsInRange(.g, targetX, targetY, aoe + 128.0, null)
            call ForGroup(.g, function thistype.enumGroup)
            if groupHasUnits then
                //Bonus Damage
                set .bonus = 0
                set .bonusDmg = BonusDamage(.lvl)
                //Create ATTACH_SFX
                set .casterSfx1 = AddSpecialEffectTarget(CASTER_SFX, .caster, "right,weapon")
                set .casterSfx2 = AddSpecialEffectTarget(CASTER_SFX, .caster, "left,weapon")
                //Transform
                call UnitAddAbility(.caster, SLEIGHT_OF_FIST_TRANSFORM)
                call UnitRemoveAbility(.caster, SLEIGHT_OF_FIST_TRANSFORM)
                //Add Bonus Attack Speed
                call UnitAddAbility(.caster, SPELL_BONUS_ATTACKSPEED)
                //Disable Attack
                call UnitAddAbility(.caster, DISABLE_ATTACK)
                //Disable Movement
                call UnitRemoveAbility(.caster, 'Amov')
                //Make the caster invulnerable
                call UnitAddAbility(.caster, 'Avul')
                //Other Properties
                call SetUnitPathing(.caster, false)
                call SetUnitTimeScale(.caster, TIME_SCALE)
                call SetUnitVertexColor(.caster, 255, 255, 255, OPACITY)
                //Create REMNANT_SFX in your caster's location
                static if LIBRARY_MissileRecycler then
                    set .remnant = GetRecycledMissile(x, y, 0, GetUnitFacing(.caster))
                    set .remnantModel = AddSpecialEffectTarget(REMNANT_MODEL, .remnant, "origin")
                    call SetUnitVertexColor(.remnant, REMNANT_RED, REMNANT_GREEN, REMNANT_BLUE, REMNANT_ALPHA)
                else
                    set .remnant = CreateUnit(.owner, DUMMY_ID, x, y, GetUnitFacing(.caster))
                    set .remnantModel = AddSpecialEffectTarget(REMNANT_MODEL, .remnant, "origin")
                    call SetUnitVertexColor(.remnant, REMNANT_RED, REMNANT_GREEN, REMNANT_BLUE, REMNANT_ALPHA)
                    call PauseUnit(.remnant, true)
                endif
                set .t = CreateTimer()
                //Store in Table/Hash
                static if LIBRARY_Table then
                    set tb[id] = this
                    set tb[GetHandleId(.t)] = this
                else
                    call SaveInteger(hash, id, 0, this)
                    call SaveInteger(hash, GetHandleId(.t), 0, this)
                endif
                //Start Timer
                call TimerStart(.t, FIRST_JUMP_DELAY, false, function thistype.periodic)
            else
                //When no unit is hit, perform some animation
                set delay = CreateTimer()
                static if LIBRARY_Table then
                    set tb.unit[GetHandleId(delay)] = .caster
                else
                    call SaveUnitHandle(hash, GetHandleId(delay), 0, .caster)
                endif
                call TimerStart(delay, 0.01, false, function thistype.noHitAnimation)
                set delay = null
                //Clean stuffs
                call DestroyGroup(.g)
                set .g = null
                set .owner = null
                set .caster = null
                call .deallocate()
            endif
            //Create AOE Border, i = 0
            loop
                exitwhen i > end
                call DestroyEffect(AddSpecialEffect(AOE_BORDER, targetX + aoe*Cos(i), targetY + aoe*Sin(i)))
                set i = i + AOE_BORDER_SPACING/aoe
            endloop

            return false
        endmethod
        
        private static method onEnd takes nothing returns boolean
            local thistype this
            local integer id = GetHandleId(GetTriggerUnit())
            static if LIBRARY_Table then
                if tb.has(id) then
                    call thistype(tb[id]).destroy()
                endif
            else
                if HaveSavedInteger(hash, id, 0) then
                    call thistype(LoadInteger(hash, id, 0)).destroy()
                endif
            endif
            return false
        endmethod
        
        static if not LIBRARY_SpellEffectEvent then
            private static method condition takes nothing returns boolean
                return (GetSpellAbilityId() == SPELL_ID and thistype.onCast() )
            endmethod
        endif
        
        static if not LIBRARY_SpellEffectEvent then
            private static method endCondition takes nothing returns boolean
                return (GetSpellAbilityId() == SPELL_END_ID and thistype.onEnd() )
            endmethod
        endif
        
        private static method onInit takes nothing returns nothing
            local integer i = 0
            static if PRELOAD then
                local unit u = CreateUnit(Player(0), DUMMY_ID, 0, 0, 0)
            endif
            static if LIBRARY_SpellEffectEvent then
                call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
                call RegisterSpellEffectEvent(SPELL_END_ID, function thistype.onEnd)
            else
                local trigger t1 = CreateTrigger()
                local trigger t2 = CreateTrigger()
                local player p
                loop
                    set p = Player(i)
                    call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
                    call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
                    set i = i + 1
                    exitwhen i == bj_MAX_PLAYER_SLOTS
                endloop
                call TriggerAddCondition(t1, Condition(function thistype.condition))
                call TriggerAddCondition(t2, Condition(function thistype.endCondition))
                set t1 = null
                set t2 = null
                set i = 0
            endif
            static if LIBRARY_Table then
                set tb = tb.create()
            endif
            loop
                call SetPlayerAbilityAvailable(Player(i), DAMAGE_DEC, false)
                set i = i + 1
                exitwhen i == bj_MAX_PLAYER_SLOTS
            endloop
            //Preload
            static if PRELOAD then
                call UnitAddAbility(u, DAMAGE_DEC)
                call UnitAddAbility(u, DISABLE_ATTACK)
                call UnitAddAbility(u, SPELL_BONUS_ATTACKSPEED)
                call UnitAddAbility(u, SLEIGHT_OF_FIST_TRANSFORM)
                call UnitAddAbility(u, SLEIGHT_OF_FIST_BACK)
                call RemoveUnit(u)
                set u = null
            endif
        endmethod
        
    endstruct

endscope

//The studied warrior must whip and weave through 
//its enemies, burning each without pause.


Changelog:
v1.00 - [22 December 2015]
- Initial Release

v1.10 - [26 December 2015]
- Fixed the Table optional requirements due to globals/endglobals block being parsed regardless of being within a static if.
- Fixed thistype(0) being destroyed, JassHelper debug messages no longer shows.
- Fixed a bug where bonus damage remains after Sleight of Fist has finished.
- Added a TIME_SCALE configuration which determines how fast the attack animation of the caster when in Sleight of Fist.

v1.20 - [10 January 2016]
- Fixed a Dummy unit not being removed when not using MissileRecycler.
- Uses tb.has() or HaveSavedInteger() as the check if a previous instance was running.
- Fixed some syntax error when not using SpellEffectEvent.
- Target Marking attachment point now configurable.
- Added an AOE Border Special Effect.
- Now uses IsUnitInRangeXY making it hit all units within AOE regardless of the target's collision size.

v1.21 - [16 January 2015]
- Ember Spirit won't turn invulnerable if no unit is hit.
- Optimized the scripting, removing unnecessary struct members.

v1.22 - [21 January 2015]
- Nulled 3 local variables not nulled in the previous version.
- Caster now performs an animation (configurable) when no unit is hit.
- Removed an unnecessary local variable.

Keywords:
sleight of fist, sleight, ember spirit, ember, dota, slash, slice, fast, quick, attack
Contents

Just another Warcraft III map (Map)

Reviews
00:21, 24th Feb 2016 IcemanBo: The system worked actually. The documentation has been changed to prevent the in thread mentioned bug. Therefor the spell can be approved. It's a great addition in the section, and very well coded. Good job.

Moderator

M

Moderator

00:21, 24th Feb 2016
IcemanBo: The system worked actually. The documentation has been changed to prevent the in thread mentioned bug.
Therefor the spell can be approved. It's a great addition in the section, and very well coded. Good job.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
So, it looks like removing the 'Amov' ability after transforming doesn't mess up the movement of the unit when it transforms back? That is a neat trick.

Why not use the UnitDamageTarget native to avoid the need to transform and move your caster around in the first place? You could stun the caster for the duration of the spell and trigger its damage. Any visible movement would be performed by dummies.

Unless I'm mistaken, the variables in a globals/endglobals block are parsed regardless of if they are in a static if or not. You can only have optional variables with struct members or locals. Because of this, you may get a syntax error when not using the Table library - or create a bogus hashtable if you are.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Why not use the UnitDamageTarget native to avoid the need to transform and move your caster around in the first place? You could stun the caster for the duration of the spell and trigger its damage. Any visible movement would be performed by dummies.
But if I do that, it won't trigger attack effects like Bash, Critical Strike, Life Steal, etc.

Unless I'm mistaken, the variables in a globals/endglobals block are parsed regardless of if they are in a static if or not. You can only have optional variables with struct members or locals. Because of this, you may get a syntax error when not using the Table library - or create a bogus hashtable if you are.
Hmm, you're right. I just tested it. But it works when it was used as a static member of the struct.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated to v1.10.
Fixed some issues mentioned by Bribe and Empirean.

Forgot to do:
- Use tb.has() or its Hashtable equivalent instead of prevThis > 0
- Test if fogmodifier is even needed.

EDIT: Waiting for other criticism so I can have it all in one update.
 
Last edited:
Pretty neat you make many requirements optional.
Me personly probably would not have the nerves for it anymore. :d

As it works like now at the moment, with not moving at all if there is no enemy,
some steps for registration/destroy may be avoided if it is instantly checked
onCast trigger if there are any valid enemies in affected area.
This will put a bit more code onCast, but would avoid some unneeded actions.

The TargetMarking attachment point should be configurable.

Do you know GroupUtils? It does same behaviour as you seem to apply here.
As each instance uses it's own group, they could be recyled, as well, in case you need.

^Same about timers and TimerUtils.

onInit function there are some syntax problems:

JASS:
call TriggerRegisterPlayerUnitEvent(t1 p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t2 p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
-> (comma added)
JASS:
call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)

exitwhen index == bj_MAX_PLAYER_SLOTS
->
exitwhen i == bj_MAX_PLAYER_SLOTS

With using the ObjectMerger the user still needs to create/copy custom objects. (?)
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Pretty neat you make many requirements optional.
Me personly probably would not have the nerves for it anymore. :d
It's for all the haters out there who is irritated by enormous amount of requirements.

As it works like now at the moment, with not moving at all if there is no enemy,
some steps for registration/destroy may be avoided if it is instantly checked
onCast trigger if there are any valid enemies in affected area.
This will put a bit more code onCast, but would avoid some unneeded actions.
I'm mimicking Dota's Sleight of Fist and as far as I know, the Hero still goes invulnerable for a very short duration even if no enemies are hit
The TargetMarking attachment point should be configurable.
They are ;)

Do you know GroupUtils? It does same behaviour as you seem to apply here.
As each instance uses it's own group, they could be recyled, as well, in case you need.[

^Same about timers
Yes, but that will cost a requirement. I also think recycling the group/timer hardly makes a difference.

IcemanBo;2775183 onInit function there are some syntax problems: [code=jass said:
call TriggerRegisterPlayerUnitEvent(t1 p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t2 p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)[/code]
-> (comma added)
JASS:
call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)

exitwhen index == bj_MAX_PLAYER_SLOTS
->
exitwhen i == bj_MAX_PLAYER_SLOTS
Woah, is the script from the map also like that?
Weird..

With using the ObjectMerger the user still needs to create/copy custom objects. (?)
I don't get what you're trying to say.
 
JASS:
if TargetFilter(.owner, u) then
    //Mark unit with SFX
    static if LIBRARY_Table then
        set tb.effect[GetHandleId(u)] = AddSpecialEffectTarget(TARGET_MARKING, u, "overhead")
    else
        call SaveEffectHandle(hash, GetHandleId(u), 0, AddSpecialEffectTarget(TARGET_MARKING, u, "overhead"))
    endif
else
    call GroupRemoveUnit(.g, u)
endif
No, "overhead" is hardcoded.

I don't get what you're trying to say.

Does creating objects work for you?
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
local fomodifiers should also be nulled, and so should be local timer delay, slicedFx.

This one has no meaning in your code.
JASS:
            static if not LIBRARY_Table then
                local effect e
            endif

I had no idea Sleight of Fist is that complicated to code. Very impressing.
I didn't play DotA2, but in DotA1 I was quite good. I cursed Icefrog when this hero came out.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
local fomodifiers should also be nulled, and so should be local timer delay, slicedFx.
Yeah, sorry, I forgot to null those.
EDIT: Done

This one has no meaning in your code.
JASS:
            static if not LIBRARY_Table then
                local effect e
            endif
Forgot to remove that too, will get right on it.
EDIT: Done

I had no idea Sleight of Fist is that complicated to code. Very impressing.
I didn't play DotA2, but in DotA1 I was quite good. I cursed Icefrog when this hero came out.
Icefrog for some reason likes to give Panda Heroes a lot of mobility.
 
Last edited:
I noticed there's a lot of hard coded values which don't need to/shouldn't be
This incudes: Order Ids, some Ability Ids, attachment point(s), revealed fog of war radius, caster offset (used so they don't need to turn) and a few other things

There's no point to keep the variable nulling that you state isn't needed for self-evident reasons: all it does is add extra processing. You could also remove test code that's been commented out (though this isn't necessary).
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I noticed there's a lot of hard coded values which don't need to/shouldn't be
This incudes: Order Ids, some Ability Ids, attachment point(s), revealed fog of war radius, caster offset (used so they don't need to turn) and a few other things

There's no point to keep the variable nulling that you state isn't needed for self-evident reasons: all it does is add extra processing. You could also remove test code that's been commented out (though this isn't necessary).

There's no point in putting the order ids in a variable.
The same goes for the 'Amov' and 'Avul'. About the attachment point left and right, I decided to hard code it since it comes in pair. and the only alternatives (I know) are left and right weapon. I honestly don't think the left and right attachments don't matter much.


@IcemanBo, I'll take a look whenever I can but I'm pretty sure it works for me.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Can you substitute the transformation process so we can preserve abilities added with the hide trick method (i.e. orb effects)?
 
Top