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

Chain Bolt v1.3b

Summons a hammer towards the target dealing damage, and stunning it then the hammer bounces to the nearest enemy unit then the hammer will return to the hero

Idea from the God of Thunder, Thor, from Norse Mythology

This is a chain lightning like spell but instead, it is a storm bolt kinda simple
Many people are looking for this and I need this for a hero in my map so I'd like to share this one.

This spell is written in vJass
Requires:
* GetClosestWidget (Spinnaker)
* TimerUtils (Vexorian)
* RegisterPlayerUnitEvent (Magtheridon96)
* SpellEffectEvent (Bribe)

Code:
JASS:
/*
* Chain Bolt spell v1.3b by jim7777
* 
*
* Copy the Chain Bolt(Thor) and ChainLightningBolt abilities
* Copy the libraries listed below then copy this trigger into your map
*
* Summons a hammer towards the target, dealing damage and stunning it then bounces to the nearest enemy
* 
* Requires:
*    GetClosestWidget (Spinnaker)
*    TimerUtils (Vexorian)
*    RegisterPlayerUnitEvent (Magtheridon96)
*    SpellEffectEvent (Bribe)
********************************************************************************************************/

scope ChainBolt
    globals
        private constant integer ABILITY_ID = 'A001' // ability id of Chain Bolt (Thor) ability
        private constant integer UNIT_ID = 'h001' // unit id of ChainLightningBolt unit
        private constant integer STUN_ID = 'A000' //ability id of ChainLightningBolt ability. Eyecandy and stun purposes
        private constant real INTERVAL = 0.03 //interval of movement of the hammer, better leave this alone
        private constant real SPEED = 25 //how fast should the hammer move every INTERVAL seconds, better leave this alone
        private constant real DETECTOR = 80 //range to detect if we will damage/stun the target, this should be a nice value
        private constant real RADIUS = 800 //range to detect possible targets when bouncing the hammer. Change this to your liking
        private constant integer MAX_BOUNCE = 4 //maximum number the hammer will bounce including the target
        private constant boolean BOUNCE_BACK = false //should the hammer bounce back? If it is true, it will only look for the nearest unit where the hammer last striked
        private constant string ORDER_STR = "thunderbolt" //order string of the STUN_ID, only change this if you aren't using Storm Bolt as base ability
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL //attack type of damage to be dealt
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // damage type of damage to be dealt
    endglobals
    
    //=== set the damage dealt here
    private function GetDmg takes real lvl returns real
        return lvl*25
    endfunction
    
//============== END OF CONFIGURABLES TOUCH THE CODES BELOW IF YOU KNOW WHAT YOU ARE DOING! ==========
    
    private struct CLightning
        unit caster
        unit target
        unit bolt
        real dmg
        player owner
        integer bounce
        group stack = null
        private static thistype tmpDat
        
        static method Filt takes nothing returns boolean
            return tmpDat.target != GetFilterUnit() and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(tmpDat.caster)) and IsUnitVisible(GetFilterUnit(),GetOwningPlayer(tmpDat.caster)) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) and GetFilterUnit() != tmpDat.target and not IsUnitInGroup(GetFilterUnit(),tmpDat.stack)
        endmethod
        
        static method ReturnHammer takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype dat = GetTimerData(t)
            local real ux = GetUnitX(dat.bolt)
            local real uy = GetUnitY(dat.bolt)
            local real tx = GetUnitX(dat.caster)
            local real ty = GetUnitY(dat.caster)
            local real dx = tx - ux
            local real dy = ty - uy
            local real x
            local real y
            local real angle 
            if SquareRoot(dx * dx + dy * dy) > DETECTOR then
                set angle = Atan2(dy, dx)
                set x = ux + SPEED * Cos(angle)
                set y = uy + SPEED * Sin(angle)
                call SetUnitX(dat.bolt,x)
                call SetUnitY(dat.bolt,y)
                call SetUnitFacing(dat.bolt,angle*bj_RADTODEG)
            else
                static if not BOUNCE_BACK then
                    call DestroyGroup(dat.stack)
                endif
                call RemoveUnit(dat.bolt)
                call ReleaseTimer(t)
                call dat.destroy()
            endif
            set t = null
        endmethod
        
        static method OnLoop takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local thistype dat = GetTimerData(t)
            local real ux = GetUnitX(dat.bolt)
            local real uy = GetUnitY(dat.bolt)
            local real tx = GetUnitX(dat.target)
            local real ty = GetUnitY(dat.target)
            local real dx = tx - ux
            local real dy = ty - uy
            local real x
            local real y
            local real angle 
            if not IsUnitType(dat.target,UNIT_TYPE_DEAD) and null != dat.target then
                if SquareRoot(dx * dx + dy * dy) > DETECTOR then
                    set angle = Atan2(dy, dx)
                    set x = ux + SPEED * Cos(angle)
                    set y = uy + SPEED * Sin(angle)
                    call SetUnitX(dat.bolt,x)
                    call SetUnitY(dat.bolt,y)
                    call SetUnitFacing(dat.bolt,angle*bj_RADTODEG)
                else
                    if IssueTargetOrder(dat.bolt,ORDER_STR,dat.target) then
                        call UnitDamageTarget(dat.caster,dat.target,dat.dmg,false,false,ATTACK_TYPE,DAMAGE_TYPE,null) 
                    endif
                    if dat.bounce < MAX_BOUNCE then
                        set dat.bounce = dat.bounce + 1
                        set tmpDat = dat
                        set dat.target = GetClosestUnitInRange(ux,uy,RADIUS,Filter(function thistype.Filt))
                        if dat.target == null then
                            call PauseTimer(t)
                            call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
                        else
                            static if not BOUNCE_BACK then
                                call GroupAddUnit(dat.stack,dat.target)
                            endif
                        endif
                    endif
                endif
            elseif dat.bounce < MAX_BOUNCE then
                set dat.bounce = dat.bounce + 1
                set tmpDat = dat
                set dat.target = GetClosestUnitInRange(ux,uy,RADIUS,Filter(function thistype.Filt))
                if dat.target == null then
                    call PauseTimer(t)
                    call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
                else
                    static if not BOUNCE_BACK then
                        call GroupAddUnit(dat.stack,dat.target)
                    endif
                endif
            endif
            if dat.bounce >= MAX_BOUNCE or null == dat.target then
                call PauseTimer(t)
                call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
            endif
            set t = null
        endmethod
        
        static method start takes nothing returns boolean
            local thistype dat
            local real angle
            local timer t = NewTimer()
            set dat = thistype.allocate()
            set dat.caster = GetTriggerUnit()
            set dat.target = GetSpellTargetUnit()
            set dat.dmg = GetDmg(GetUnitAbilityLevel(dat.caster,ABILITY_ID))
            set dat.bounce = 0
            static if not BOUNCE_BACK then
                set dat.stack = CreateGroup()
                call GroupAddUnit(dat.stack,dat.target)
            endif
            set angle = Atan2(GetUnitY(dat.target) - GetUnitY(dat.caster), GetUnitX(dat.target) - GetUnitX(dat.caster))
            set dat.owner = GetOwningPlayer(dat.caster)
            set dat.bolt = CreateUnit(dat.owner,UNIT_ID,GetUnitX(dat.caster),GetUnitY(dat.caster),bj_RADTODEG * angle)
            call UnitAddAbility(dat.bolt,STUN_ID)
            call SetUnitAbilityLevel(dat.bolt,STUN_ID,GetUnitAbilityLevel(dat.caster,ABILITY_ID))
            call SetTimerData(t,dat)
            call TimerStart(t,INTERVAL,true,function thistype.OnLoop)
            set t = null
            return false 
        endmethod
        
        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent(ABILITY_ID,function thistype.start)
        endmethod
    endstruct
endscope

Feel free for suggestions and feedbacks!


*v1.3b
- Fixed bug dealing damage to units with spell immunity
*v1.3
- Hammer will now return to the hero after the spell ends
- Fixed some spell bugs
*v1.2
- No longer requires GroupUtils
- Improved code
*v1.1b
- Removed recycling version
*v1.1
- Improved coding
*v1.0
- Initial Release


Keywords:
storm, bolt, chain, lightning, jim7777, stun, thor
Contents

Chain Bolt v1.3b (Map)

Reviews
14th Jan 2012 Bribe: timer time doesn't need to be a struct member. You control its release based on GetExpiredTimer so you can achieve the same via a local timer t. Instead of a group per struct you could use a hashtable that indexes by the unit...

Moderator

M

Moderator

14th Jan 2012
Bribe: timer time doesn't need to be a struct member. You control its release based on GetExpiredTimer so you can achieve the same via a local timer t.

Instead of a group per struct you could use a hashtable that indexes by the unit handle ID and the struct ID.

Approved 3.5/5.
 
Level 10
Joined
Sep 19, 2011
Messages
527
set dat.owner = GetOwningPlayer(dat.caster)

->

set dat.owner = GetTriggerPlayer()

If you've short periods, use T32 or CTL (but, if you not want to change it, change the interval to 0.03125).

timer time

You don't need this, use a local instead.

Filter(function thistype.Filt)

Maybe you can use a loop filter, is faster than this.

JASS:
call RemoveUnit(dat.bolt)
                call ReleaseGroup(dat.stack)
                call ReleaseTimer(dat.time)
                call dat.destroy()

You should create the destroy method and put this actions on it.
Then, the only you need to do is "call dat.destroy()" :).

Amm... don't use GroupUtils it's deprecated.

Greetings, nice spell :)
 
Level 10
Joined
Sep 19, 2011
Messages
527
JASS:
local unit j

call GroupEnumUnitsInRange(GROUP, x, y, aoe, null)

loop
    set j = FirstOfGroup(GROUP)
    exitwhen j == null
    call GroupRemoveUnit(GROUP, j)

    // your actions here...
endloop

By this way you don't need tmpDat.

JASS:
local thistype dat
set dat = thistype.allocate()

-

local thistype dat = thistype.allocate()

You forget the another corrections.

Greetings.

EDIT:
well i guess i won't be using filter.. i thought ForGroup is running in another thread right? or i'm wrong

I'm not sure, but with the loop is more faster.
 
Level 10
Joined
Sep 19, 2011
Messages
527
Last edited:
Level 5
Joined
May 13, 2012
Messages
66
The spell I have in mind has Mjolnir (storm bolt) fly in a straight line and knocks back anyone it hits. Also you can choose to make Mjolnir return to Thor before spell finishes otherwise it just drops to the ground. Also you can't attack or cast spells until Mjolnir returns back to you.

Edit: I'd reserve this spell for Captain America however.
 
Top