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

Charge of Darkness v1.5

  • Like
Reactions: Losam
A spell based from the game, DotA

"Barathrum fixes his sight on an enemy and starts charging through all objects. Upon leaving the shadows, Barathrum shocks his opponent into an immobile state for some time."

You may modify the textmacros in the configuration part to suit your need. These include the things you want to do the the enemies that will be passed by the caster.

This spell is fully configurable to suit your needs!
Beware, model/unit animation is almost confusing when configuring this spell. If you want some help, post it here.

Requires:
GetClosestWidget - Spinnaker
DummyCaster - Nestharus
RegisterPlayerUnitEvent - Magtheridon96
SpellEffectEvent - Bribe
Timer32 - Jesus4Lyf

Code:
JASS:
/*
* Charge of Darkness by jim7777
*   v1.5
*
*   Spell based from the game, Defense of the Ancients
*
*   The hero charges towards the target, knockbacking every enemy on its path, then stunning the target if the caster reaches it. 
*   
*   This spell is mostly configurable to suit your needs
*   Hope you enjoy it!
*
*   Requires:
*    GetClosestWidget - Spinnaker
*    DummyCaster - Nestharus
*    RegisterPlayerUnitEvent - Magtheridon96
*    SpellEffectEvent - Bribe
*    TimerUtils - Vexorian
*    Timer32 - Jesus4Lyf
*
********************************************************************/
scope ChargeOfDarkness
    globals
        private constant integer ABILITY_ID = 'A000' //ability id of Charge of Darkness ability.
        private constant integer FAERIE_ID = 'A001' //ability id of CoD_BuffPlacer ability
        private constant integer STUN_ID = 'A002' //ability id of CoD_StunPlacer ability, modify the ability to suit your needs
        private constant integer BUFF_ID = 'B000' //buff id of Charge of Darkness buff
        private constant integer ANIM_INDEX = 2 //The animation index from the model you'll be using on this spell, applicable if ANIM_TYPE is set to true
                                                //2 is the animation index for Walk of the model Spirit Walker
                                                //Read the Documentation on checking the animation index of a unit.
        private constant real DETECTOR_RANGE = 1500 //This value will tell if we are in range of the target and if we are, add the buff to the target
        private constant real STUN_RANGE = 100 //how far will we cast the stun? This is already a recommended value >:D
        private constant real RADIUS = 400 //range to detect enemies when last target is dead, only applicable if CHANGE_ONDEAD is set to true
        private constant real KNOCK_RADIUS = 200 //the radius within the caster that will be affected by knock backs, applicable if KNOCKBACK_PATH is set to true
        private constant real KNOCK_DISTANCE = 80 //the distance of the knockback
        private constant real KNOCK_DUR = 1.5 //duration of knockback
        private constant boolean CHANGE_ONDEAD = true //charge to the nearest target if target is dead? This will detect enemies within RADIUS range, setting this to false will just stop the caster from charging
        private constant boolean DISABLE_STOP = true //Should orders stop the spell?
        private constant boolean LOOPING_SFX = false //The LOOP_SFX will be created for every instance,if false, LOOP SFX will be created on spell start
        private constant boolean ANIM_TYPE = true //true: uses index, false: uses string animations
        private constant boolean KNOCKBACK_PATH = true //knockback enemies in its path
        private constant string ANIM_STR = "walk" //what animation to be applied on the caster, applicable if ANIM_TYPE is set to false.
                                                  //NOTE: I recommend using the INDEX because the native we are using here is quite buggy to some models like the Spirit Walker model
                                                  //I advise using the ANIM_INDEX variable instead of this variable if you'd like to see your spell animations smoother.
        private constant string TARGET = "Abilities\\Spells\\Other\\HowlOfTerror\\HowlTarget.mdl" //the effect that will appear on the target
        private constant string LOOP_SFX = "Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl" //the effect that will appear on the caster while charging
        private constant string KNOCK_SFX = "Abilities\\Spells\\Human\\FlakCannons\\FlakTarget.mdl" //the effect that will appear if a unit is knock backed
        private constant string ATTACH_POINT = "origin" //attchment point of LOOP_SFX
        private hashtable ht = InitHashtable()
        private group tmpGroup = null
    endglobals
    
    //! textmacro ChargeOfDarkness_OnChargeEnemyPath
            //the variable f is our target
            //this.caster is the unit who casted ChargeOfDarkness
            //this.owner is the player who owns our caster
            //modify this thing to suit your needs
            //This will only work if KNOCKBACK_PATH is set to true
            call DummyCaster[STUN_ID].castTarget(this.owner,1,OrderId("firebolt"),f)
    //! endtextmacro
    
    //! textmacro ChargeOfDarkness_IsOnKnockback
            //variable u is our target
            //it should match with the ChargeOfDarkness_OnChargeEnemyPath macro 
            //in this example, we check if the unit is stunned.
            //modify this thing to suit your needs
            //This will only work if KNOCKBACK_PATH is set to true
            local boolean b = /*
            only modify the following parts to your condition
            */ GetUnitAbilityLevel(u,'BSTN') > 0 
    //! endtextmacro
    
    private function CoD_Speed takes unit u, real lvl returns real
        //total speed is to be multiplied by the number of instances per second.
        //there are 33 instances per second
        return 15+(2*lvl)
    endfunction
    
    private struct SChaser
        unit caster
        unit target
        integer lvl
        effect eff
        player owner
        real speed
        real px
        real py
        group stacks
        static if not LOOPING_SFX then
            effect lpsfx
        endif
        private static thistype tmp
        
        private method destroy takes nothing returns nothing
            static if not LOOPING_SFX then
                call DestroyEffect(this.lpsfx)
            endif
            static if KNOCKBACK_PATH then
                call DestroyGroup(this.stacks)
            endif
            call RemoveSavedInteger(ht,GetHandleId(this.caster),0)
            call SetUnitPathing(this.caster,true)
            call SetUnitTimeScale(this.caster,1)
            call SetUnitAnimation(this.caster,"stand")
            call UnitShareVision(this.target,this.owner,false)
            call DestroyEffect(this.eff)
            call UnitRemoveAbility(this.target,BUFF_ID)
            call this.stopPeriodic()
            call this.deallocate()
        endmethod
        
        static method Filt takes nothing returns boolean
            return GetFilterUnit() != tmp.target and IsUnitEnemy(GetFilterUnit(),tmp.owner) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) and GetUnitAbilityLevel(GetFilterUnit(),'Aloc') <= 0
        endmethod
        
        static if KNOCKBACK_PATH then
            static method CheckStack takes nothing returns nothing
                local unit u = GetEnumUnit()
                //! runtextmacro ChargeOfDarkness_IsOnKnockback()
                if not b then
                    call GroupRemoveUnit(tmp.stacks,u)
                endif
                set u = null
            endmethod
        endif

        private method periodic takes nothing returns nothing
            local real ux = GetUnitX(this.caster)
            local real uy = GetUnitY(this.caster)
            local real tx = GetUnitX(this.target)
            local real ty = GetUnitY(this.target)
            local real dx = tx - ux
            local real dy = ty - uy
            local real dist = SquareRoot(dx * dx + dy * dy)
            local real angle = Atan2(dy, dx)
            local real x = ux + this.speed * Cos(angle)
            local real y = uy + this.speed * Sin(angle)
            local string str
            local boolean dest = false
            local unit f
            static if LOOPING_SFX then
                call DestroyEffect(AddSpecialEffectTarget(LOOP_SFX,this.caster,ATTACH_POINT))
            endif
            static if ANIM_TYPE then
                call SetUnitAnimationByIndex(this.caster,ANIM_INDEX)
            else
                call SetUnitAnimation(this.caster,ANIM_STR)
            endif
            if not IsUnitVisible(this.target,this.owner) and not IsUnitType(this.target,UNIT_TYPE_DEAD) then
                call UnitShareVision(this.target,this.owner,true)
            endif
            static if DISABLE_STOP then
                call SaveBoolean(ht,GetHandleId(this.caster),1,true) //we need to do this D:
                if not IssueImmediateOrder(this.caster,"holdposition") then //detect if the unit can do any orders, if not, 99% it is stunned or disabled.
                    call this.destroy()
                    set dest = true
                endif
                call RemoveSavedBoolean(ht,GetHandleId(this.caster),1)
            else
                call IssueImmediateOrder(this.caster,"holdposition")
            endif
            if IsUnitType(this.target,UNIT_TYPE_DEAD) then
                static if CHANGE_ONDEAD then
                    call UnitRemoveAbility(this.target,BUFF_ID)
                    call DestroyEffect(this.eff)
                    set tmp = this
                    set this.target = GetClosestUnitInRange(tx,ty,RADIUS,Filter(function thistype.Filt))
                    if this.target == null and not dest then
                        call IssueImmediateOrder(this.caster,"stop")
                        call this.destroy()
                        set dest = true
                    else
                        set str = TARGET
                        if not IsPlayerAlly(GetLocalPlayer(),this.owner) then 
                            set str = ""
                        endif
                        set this.eff = AddSpecialEffectTarget(str,this.target,"head")
                    endif
                elseif not dest then
                    call this.destroy()
                    set dest = true
                endif
            elseif (IsUnitType(this.caster,UNIT_TYPE_DEAD) or IsUnitPaused(this.caster)) and not dest then
                call this.destroy()
                set dest = true
            endif
            if not IsUnitType(this.target,UNIT_TYPE_DEAD) then
                call SetUnitX(this.caster,x)
                call SetUnitY(this.caster,y)
                call SetUnitFacing(this.caster,angle*bj_RADTODEG)
                static if KNOCKBACK_PATH then
                    set tmp = this
                    call ForGroup(this.stacks,function thistype.CheckStack)
                    call GroupEnumUnitsInRange(tmpGroup,x,y,KNOCK_RADIUS,null)
                    loop
                        set f = FirstOfGroup(tmpGroup)
                        exitwhen f == null
                        call GroupRemoveUnit(tmpGroup,f)
                        if not HaveSavedInteger(ht,GetHandleId(this.caster),0) and not IsUnitInGroup(f,this.stacks) and  f != this.target and IsUnitEnemy(f,this.owner) and not IsUnitType(f,UNIT_TYPE_DEAD) and not IsUnitType(f,UNIT_TYPE_MAGIC_IMMUNE) and GetUnitAbilityLevel(f,'Aloc') <= 0 then
                            set angle = Atan2(GetUnitY(f) - GetUnitY(this.caster), GetUnitX(f) - GetUnitX(this.caster))
                            //call KBS_BeginEx(f,KNOCK_DISTANCE,KNOCK_DUR,angle*bj_RADTODEG,KNOCK_SFX,100,false,true)
                            //! runtextmacro ChargeOfDarkness_OnChargeEnemyPath()
                            call GroupAddUnit(this.stacks,f)
                        endif
                    endloop
                endif
                if dist <= DETECTOR_RANGE and GetUnitAbilityLevel(this.target,BUFF_ID) == 0 then
                    call DummyCaster[FAERIE_ID].castTarget(this.owner,1,OrderId("faeriefire"),this.target)
                endif
                if dist <= STUN_RANGE then
                    call DummyCaster[STUN_ID].castTarget(this.owner,this.lvl,OrderId("firebolt"),this.target)
                    call IssueTargetOrder(this.caster,"attack",this.target)
                    if not dest then
                        call this.destroy()
                    endif
                endif
            endif
            set f = null
        endmethod
        implement T32x
        
        private static method start takes nothing returns boolean
            local thistype this = thistype.allocate()
            local string str = TARGET
            set this.caster = GetTriggerUnit()
            set this.target = GetSpellTargetUnit()
            set this.speed = CoD_Speed(this.caster,GetUnitAbilityLevel(this.caster,ABILITY_ID))
            set this.owner = GetOwningPlayer(this.caster)
            set this.px = GetUnitX(this.caster)
            set this.py = GetUnitY(this.caster)
            set this.lvl = GetUnitAbilityLevel(this.caster,ABILITY_ID)
            static if KNOCKBACK_PATH then
                set this.stacks = CreateGroup() //seriously
            endif
            if not IsPlayerAlly(GetLocalPlayer(),this.owner) then 
                set str = ""
            endif
            set this.eff = AddSpecialEffectTarget(str,this.target,"head")
            static if not LOOPING_SFX then
                set this.lpsfx = AddSpecialEffectTarget(LOOP_SFX,this.caster,ATTACH_POINT)
            endif
            call SetUnitTimeScale(this.caster,1.2)
            call UnitShareVision(this.target,this.owner,true)
            call SetUnitPathing(this.caster,false)
            call SaveInteger(ht,GetHandleId(this.caster),0,this)
            call this.startPeriodic()
            return false 
        endmethod
        
        static if DISABLE_STOP then 
            static method OnOrder takes nothing returns boolean
                local unit u = GetTriggerUnit()
                local thistype dat = LoadInteger(ht,GetHandleId(u),0)
                if not HaveSavedBoolean(ht,GetHandleId(u),1) and dat != 0 then
                    call dat.destroy()
                endif
                set u = null
                return false
            endmethod
        endif
        
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(ABILITY_ID,function thistype.start)
            static if DISABLE_STOP then 
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER,function thistype.OnOrder)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER,function thistype.OnOrder)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER,function thistype.OnOrder)
            endif
            call Preload(TARGET)
            call Preload(LOOP_SFX)
            call Preload(KNOCK_SFX)
            static if KNOCKBACK_PATH then //let us save memory!
                set tmpGroup = CreateGroup()
            endif
        endmethod
    endstruct
endscope


*v1.5
- Removed requirement of Alloc
- Fixed an exploit that may cause Warcraft 3 to stop responding
- Fixed test map bugs
- Units who are already in Charge of Darkness will no longer be hit by stuns/knockbacks if those units are in a path of another unit who is casting Charge of Darkness
- Re-coded some part of the code.
*v1.4
- Now requires Timer32 instead of TimerUtils
- Now requires Alloc
- Actions to do when the caster passed an enemy is now configurable.
- Improved coding
*v1.3b
- Updated documentation
- Added some static ifs
*v1.3
- Improved coding
- Removed INTERVAL variable
- Spell will now only run 33 instances per second.
- Fixed animation bugs when struct is destroyed
- Fixed test map bugs
*v1.2c
- Fixed group stack problems wherein knock backed units can't be knock backed again
*v1.2b
- Added some static if to recent changes
*v1.2
- Fixed lag issues regarding the knockback being used multiple times
- Improved coding.
- Fixed test map only bugs.
*v1.1
- Removed wait time for checking if caster is disabled
- Fixed caster when it is auto attacking targets
- Improved coding
- Fixed functions regarding DISABLE_STOP variable.
*v1.0c
- Fixed CHANGE_ONDEAD not working
- Improved coding
*v1.0b
- Removed useless codes
- Fixed some spell bugs
- Improved coding
*v1.0
- Initial Release


Feel free to post your comments and suggestions to improve the code!
Additional Notes:
*If the current code tends to cause some desync or fatal errors, please let me know. This spell is tested with two user players.
*Amulet of Spell Shield may block the buff placer. I suggest you change the base ability of Charge of Darkness to suit your needs or you may use a triggered Amulet of Spell Shield since the base of this spell is mostly Channel (no mini stun effects or anything like that and that's how the ability in DotA works) but anyways, the buff placer will just be cast again if the buff doesn't exist on the target. Same way goes with the stun.
*I know that the knockback should work like Greater Bash but.......

Keywords:
charge, of, darkness, spirit, breaker, walker, barathrum, dota, jim7777
Contents

Charge of Darkness v1.5 (Map)

Reviews
30th Jan 2012 Bribe: Thanks for making the updates. Rating upgraded to 4.25/5 (Recommended).

Moderator

M

Moderator

30th Jan 2012
Bribe: Thanks for making the updates. Rating upgraded to 4.25/5 (Recommended).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
You are getting double-frees because there are some cases where you are destroying the instance/releasing the timer 2x.

JASS:
            static if DISABLE_STOP then
                set dat.wait = dat.wait + INTERVAL
                if dat.wait >= 0.20/*good value*/ and GetUnitCurrentOrder(dat.caster) == OrderId("holdposition") then
                    call SaveBoolean(ht,GetHandleId(dat.caster),1,true) //we need to do this D:
                    if not IssueImmediateOrder(dat.caster,"holdposition") then //detect if the unit can do any orders, if not, 99% it is stunned or disabled.
                        @call ReleaseTimer(dat.time)
                        call dat.destroy()@
                    endif
                    call RemoveSavedBoolean(ht,GetHandleId(dat.caster),1)
                    set dat.wait = 0
                endif
            else
                call IssueImmediateOrder(dat.caster,"holdposition")
            endif
            if IsUnitType(dat.target,UNIT_TYPE_DEAD) then
                static if CHANGE_ONDEAD then
                    call UnitRemoveAbility(dat.target,BUFF_ID)
                    call DestroyEffect(dat.eff)
                    set tmp = dat
                    set dat.target = GetClosestUnitInRange(tx,ty,RADIUS,Filter(function thistype.Filt))
                    if dat.target == null then
                        @call ReleaseTimer(dat.time)
                        call dat.destroy()@
                    else
                        set str = TARGET
                        if not IsPlayerAlly(GetLocalPlayer(),dat.owner) then 
                            set str = ""
                        endif
                        set dat.eff = AddSpecialEffectTarget(str,dat.target,"head")
                    endif
                else
                    @call ReleaseTimer(dat.time)
                    call dat.destroy()@
                endif
            elseif IsUnitType(dat.caster,UNIT_TYPE_DEAD) or IsUnitPaused(dat.caster) then
                @call ReleaseTimer(dat.time)
                call dat.destroy()@
            endif
            if not IsUnitType(dat.target,UNIT_TYPE_DEAD) then
                call SetUnitX(dat.caster,x)
                call SetUnitY(dat.caster,y)
                call SetUnitFacing(dat.caster,angle*bj_RADTODEG)
                static if KNOCKBACK_PATH then
                    set tmp = dat
                    call GroupEnumUnitsInRange(tmpGroup,x,y,KNOCK_RADIUS,Filter(function thistype.Filt))
                    call ForGroup(tmpGroup,function thistype.KnockBack)
                endif
                if dist <= DETECTOR_RANGE and GetUnitAbilityLevel(dat.target,BUFF_ID) == 0 then
                    call DummyCaster[FAERIE_ID].castTarget(dat.owner,1,OrderId("faeriefire"),dat.target)
                endif
                if dist <= STUN_RANGE then
                    call DummyCaster[STUN_ID].castTarget(dat.owner,dat.lvl,OrderId("firebolt"),dat.target)
                    call IssueTargetOrder(dat.caster,"attack",dat.target)
                    @call ReleaseTimer(dat.time)
                    call dat.destroy()@
                endif
            endif

I also recommend using FirstOfGroup loop with a null filter, instead of using the filter. This saves RAM, processor and HD space.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Pro tip: instead of adding units to a group and checking if they are in the group, just use a hashtable and do this: call SaveBoolean(ht, this, GetHandleId(unit), true), instead of GroupAddUnit. Instead of GroupRemoveUnit: call RemoveSavedBoolean(ht, this, GetHandleId(unit)). Instead of DestroyGroup: call FlushChildHashtable(ht, this).

If you are a "pro user" you can also do this using a TableArray, instead.
 
Level 10
Joined
May 27, 2009
Messages
494
perhaps i might just use the hashtable instead of tablearray
since some people still use the old table, and just for this spell, they need to freaking change everything they have

and on the CTL, i can't understand how to use that >:D
I need a demo map LOL

lemme update soon, just taking photographs of some sort for a while
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Level 10
Joined
May 27, 2009
Messages
494
eh
How to stop CTL timer hmm is it like the ReleaseTimer?? 'cause i'm wondering if I can really use it since i need to stop the timer sometimes via OnOrder method

meanwhile, why does facebook edit thumbnail not working D:

still gonna fix the group stack... What if the knockbacked unit blinks in front of the caster :ogre_hurrhurr:

EDIT
Updated to fix the group stack problem
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
You should enclose the declaration line of lpsfx with this static if: "static if not LOOPING_SFX then", so it would look like:

JASS:
        @static if not LOOPING_SFX then@
            effect lpsfx = null
        @endif@

It is easy to use CTL:

1) Make your struct one that extends array
2) Implement the CTL modules.
3) Your .deallocate() and .allocate() methods act as "ReleaseTimer/NewTimer". Seriously.
 
Level 10
Joined
May 27, 2009
Messages
494
Oh okay.. lemme try
btw how to highlight jass codes O.O so amazzziinnnggg LOL

meh... i don't like importing stuffs


23ifl3a.jpg



EDIT
ohh.. it is just showing member redeclared: destroy
O.O helpppp
I'm not in the mood to make some logic. >:D

EDIT 2
Updated to v1.3
Meh, i won't use CTL for now
 
Last edited:
Level 11
Joined
Mar 27, 2011
Messages
293
Jim7777 Dear, I Have Some Suggestions for your Enchantment (Not that it was good, just my opnioes).

Tip:

JASS:
When an enemy unit collides with any hero who uses this Enchantment This unit is pushed due to the collision and moreover, it is flawed and is dizzy.

Peace and God's grace be with you all!

:goblin_good_job:
 
Level 6
Joined
Jan 22, 2012
Messages
198
Hmm it gives me some error messages when i try to save map and the spell doesn't work when i try to use it....btw i suggest u try making the spell so u don't have to target the unit to activate it...would be enough just targetting on ground ;) I might suggest adding this to a custom game called Warlock, but i need to try it first hehe wondering if it stops/crashes into it's target or if it goes straight through....and i do hope u can adjust damage and maybe even speed? :p
 
Top