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

[vJASS] Wrath of Zeus

Credits Distribution: Vexorian (dummy.mdx model), Pharaoh_ suggestions and support, RisingDusk GroupUtils (OPTIONAL!)

For GroupUtils click -> http://www.wc3c.net/showthread.php?t=104464

Version 1.3
Wrath of Zeus
Dedicated to Pharaoh_ from Greece! Show some love for the Greek Gods!
haha :)

Documentation

Unleashes a static thunder bolt towards a target enemy unit, dealing moderate damage. Upon impact, the bolt unleashes several lightning bolts towards all directions dealing secondary damage to units in their wake.
-------
Requires the JNGP to compile
You need the dummy.mdx model included in if you don't have it!
If you don't have a universal dummy unit i suggest you copy mine! (e001)
-------
Unfortunately i've dissalowed the user to config some vital code because i thought it would be better not to mess it up.
-------
Copy the map code and paste it on your map
Create the spell based on channel ability
If you don't have a dummy then make one
Configure the script at the top (globals)
Enjoy
Map Code
JASS:
library WrathOfZeus initializer init requires optional GroupUtils

    globals
        private constant integer ABILITY_ID = 'A000'  //Raw code of the triggering ability
        private constant integer DUMMY_ID = 'e001' //The dummy unit, please import dummy.mdl and make a proper dummy unit
        private constant integer STATIC_LINKS = 32 //The links that are created when the orb crashes
        private constant integer CHARGED_BOLTS_AMOUNT = 3 //The amount of orbiting missiles that are created around the caster when the ability is learned
        private constant real CHARGED_BOLTS_SIZE = .88 //The scale value of the orbiting missiles
        private constant real PRIMARY_BASE_DAMAGE = 130 //The base damage when the orb crashes
        private constant real PRIMARY_INC_DAMAGE = 75 // The increment damage of the orb
        private constant real SECONDARY_BASE_DAMAGE = 50 //The damage each static link deals
        private constant real SECONDARY_INC_DAMAGE = 25 // The increment damage of each static link
        private constant real BOLT_SCALE = 2.75 //The scale of the lightning bolt
        private constant real NORMAL_OFFSET = 145 //The default rotating offset of the orbiting bolts
        private constant real MAX_OFFSET = 425 //The maximum rotating offset the bolts can reach when overloading
        private constant real ENUM_OFFSET = 75 //the range that units are checked from each static link. You should decrease this if you use a LOT of static links!
        private constant string BALL_ART = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl" //The missiles that rotate around the cast
        private constant string TAIL_LIGHTNING = "CLPB" //The lightning tail of the bolt
        private constant string CHARGED_LIGHTNING = "CLSB" //The lightning tail of the secondary static links
        private constant string ORBITAL_LIGHTNING = "DRAB" //The lightning that links all the orbital missiles
        private constant string BOLT_LIGHTNING = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl" //The lightning bolt art
        private constant string PRIMARY_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" //The effect that is created upon impact
        private constant string SECONDARY_EFFECT = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldBuff.mdl" //The effect that is created on damaged units of secondary effect
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
        ///
        private constant boolean USE_GROUPUTILS = true //Whether to use Group utils library, if the library isn't found, it will CreateGroup() instead
        ///
    endglobals
    
    
    ////===============
    
    
    globals
        private location loc = Location(0, 0)
        private location loc2 = Location(0, 0)
        private integer instance = 0
        private boolexpr BE
        private constant group Group = CreateGroup()
        private constant integer Cbolts = CHARGED_BOLTS_AMOUNT - 1
        private real MINX
        private real MINY
        private real MAXX
        private real MAXY
    endglobals 
    
    /////=================
    //Extra functions
    
    //
    private function SafeX takes real x returns real
       local real rx=MINX+50
       if(x<rx)then
           return rx
       endif
       set rx=MAXX-50
       if(x>rx)then
           return rx
       endif
       return x
    endfunction

    private function SafeY takes real y returns real
       local real ry=MINY+50
       if(y<ry)then
           return ry
       endif
       set ry=MAXY-50
       if(y>ry)then
           return ry
       endif
       return y
    endfunction 
    
    /////////=======================================
    
    private struct spark
        unit u
        lightning l        
        real ang
        real sx
        real sy
        real x
        real y
        real time = 1.5
        real time2 = 2
        real dmg
        group damaged
        static integer tot = 0
        static spark array ar
        static timer stim = CreateTimer()
         
         static method SecondaryEffects takes unit u, unit t, real x, real y returns nothing
             local integer i = 1
             local spark data
             loop
             exitwhen i > STATIC_LINKS
                set data = spark.create()
                set data.u = u
                set data.sx = x
                set data.sy = y
                set data.x = x
                set data.y = y
                set data.ang = (360 / STATIC_LINKS) * i
                static if USE_GROUPUTILS then
                    static if LIBRARY_GroupUtils then
                        set data.damaged = NewGroup()
                    else
                        set data.damaged = CreateGroup()
                        debug call BJDebugMsg(LIBRARY_PREFIX+": Could not find GroupUtils")
                    endif
                else    
                    set data.damaged = CreateGroup()
                endif    
                if t != null then
                   call GroupAddUnit(data.damaged, t)
                endif   
                set data.dmg = SECONDARY_BASE_DAMAGE + (SECONDARY_INC_DAMAGE * (GetUnitAbilityLevel(data.u, ABILITY_ID) - 1))
                set data.l = AddLightning(CHARGED_LIGHTNING, true, x, y, x+5, y+5)
                call SetLightningColor(data.l, .7, .7, 1, .7)
                if spark.tot == 0 then
                    call TimerStart(spark.stim, .042, true, function spark.Loop)
                endif
                set spark.ar[spark.tot] = data
                set spark.tot = spark.tot + 1
                set i = i + 1
                endloop        
        endmethod
    
        static method Enum takes nothing returns boolean
            local spark data = instance
            local unit e = GetFilterUnit()
            if IsUnitInGroup(e, data.damaged) then
                set e = null
                return false
            else
                if (not(IsUnitType(e, UNIT_TYPE_STRUCTURE))) and (GetWidgetLife(e)>.405) and (IsUnitEnemy(e, GetOwningPlayer(data.u))) then
                    call DestroyEffect(AddSpecialEffectTarget(SECONDARY_EFFECT, e, "origin"))
                    call UnitDamageTarget(data.u, e, data.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                    call GroupAddUnit(data.damaged, e)
                endif    
            endif   
            set e = null
            return false
        endmethod
    
        static method Loop takes nothing returns nothing
            local spark data
            local integer i = 0
            local real p
            loop
            exitwhen i >= spark.tot
                set data = spark.ar[i]
                if data.time > 0 then
                    set data.time = data.time - .042
                    set data.x = SafeX(data.x + 30 * Cos(data.ang * bj_DEGTORAD))
                    set data.y = SafeX(data.y + 30 * Sin(data.ang * bj_DEGTORAD))
                    set p = SquareRoot((data.x - data.sx) * (data.x - data.sx) + (data.y - data.sy) * (data.y - data.sy))
                    if p > 450 then
                        set data.sx = data.sx + 30 * Cos(data.ang * bj_DEGTORAD)
                        set data.sy = data.sy + 30 * Sin(data.ang * bj_DEGTORAD)
                    endif
                    set instance = data
                    call GroupEnumUnitsInRange(Group, data.x, data.y, ENUM_OFFSET, BE)
                    call MoveLocation(loc, data.sx, data.sy)
                    call MoveLocation(loc2, data.x, data.y)
                    call MoveLightningEx(data.l, true, data.sx, data.sy, GetLocationZ(loc), data.x, data.y, GetLocationZ(loc2)) 
                else
                    if (SquareRoot((data.x - data.sx) * (data.x - data.sx) + (data.y - data.sy) * (data.y - data.sy)) > 50) and data.time2 > 0 then
                        set data.sx = data.sx + 30 * Cos(data.ang * bj_DEGTORAD)
                        set data.sy = data.sy + 30 * Sin(data.ang * bj_DEGTORAD)
                        call MoveLocation(loc, data.sx, data.sy)
                        call MoveLocation(loc2, data.x, data.y)
                        call MoveLightningEx(data.l, true, data.sx, data.sy, GetLocationZ(loc), data.x, data.y, GetLocationZ(loc2)) 
                        set instance = data
                        call GroupEnumUnitsInRange(Group, data.sx, data.sy, 125, BE)
                        set data.time2 = data.time2 - .04             
                    else
                        call data.destroy()
                        set spark.tot = spark.tot - 1
                        set spark.ar[i] = spark.ar[spark.tot]
                    endif
                endif
                set i = i + 1
            endloop
            if spark.tot == 0 then
                call PauseTimer(spark.stim)
            endif
        endmethod
        
        method onDestroy takes nothing returns nothing
            call DestroyLightning(.l)
            static if USE_GROUPUTILS then
                static if LIBRARY_GroupUtils then
                    call ReleaseGroup(.damaged)
                else
                    call DestroyGroup(.damaged)
                    debug call BJDebugMsg(LIBRARY_PREFIX+": Could not find GroupUtils")
                endif
            else    
                call DestroyGroup(.damaged)
            endif    
        endmethod    
    endstruct    
    
    
    private struct bolt
        unit u
        unit t
        unit d
        real damage
        lightning l
        effect art
        static integer total = 0
        static bolt array arr
        static timer tim = CreateTimer()
    
        static method create takes unit u, unit t returns bolt        
            local bolt data = bolt.allocate()
            local real x
            local real y
            local real x2
            local real y2
            local sound snd
            local string array s
            local integer r = GetRandomInt(0, 1)
            set s[0] = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.wav" 
            set s[1] = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBolt.wav" 
            set data.u = u
            set snd = CreateSound(s[r], false, false, true, 12700, 12700, "")
            set x = GetUnitX(data.u) + 100 * Cos(GetUnitFacing(data.u) * bj_DEGTORAD)
            set y = GetUnitY(data.u) + 100 * Sin(GetUnitFacing(data.u) * bj_DEGTORAD)
            set data.d = CreateUnit(GetOwningPlayer(data.u), DUMMY_ID, x, y, 0)
            call AttachSoundToUnit(snd, data.d)
            set x2 = GetUnitX(data.d) + 200 * Cos((GetUnitFacing(data.u)-180) * bj_DEGTORAD)
            set y2 = GetUnitY(data.d) + 200 * Sin((GetUnitFacing(data.u)-180) * bj_DEGTORAD)
            set data.t = t
            call UnitAddAbility(data.d, 'Amrf')
            call UnitRemoveAbility(data.d, 'Amrf')
            call SetUnitFlyHeight(data.d, 90, 550)
            call SetUnitTimeScale(data.d, 0)
            call SetUnitScale(data.d, BOLT_SCALE, BOLT_SCALE, BOLT_SCALE)
            set data.l = AddLightning(TAIL_LIGHTNING, true, x2, y2, x, y)
            call StartSound(snd)
            call KillSoundWhenDone(snd)
            set snd = null
            set data.damage = PRIMARY_BASE_DAMAGE + (PRIMARY_INC_DAMAGE * (GetUnitAbilityLevel(data.u, ABILITY_ID)-1))
            set data.art = AddSpecialEffectTarget(BOLT_LIGHTNING, data.d, "origin")
            if bolt.total == 0 then
                call TimerStart(bolt.tim, .0303, true, function bolt.Loop)
            endif
            set bolt.arr[bolt.total] = data
            set bolt.total = bolt.total + 1
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl", data.u, "weapon,right"))
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", data.d, "origin"))
            return data
        endmethod
    
        static method Loop takes nothing returns nothing
            local bolt data
            local integer i = 0
            local real p = 0
            local real a=0
            local real x=0
            local real y=0
            local real x2=0
            local real y2=0
            local real z=0
            local real d=0
            local real dx=0
            local real dy=0
            loop
            exitwhen i >= bolt.total
                set data = bolt.arr[i]
                set x = GetUnitX(data.d)
                set y = GetUnitY(data.d)
                set x2 = GetUnitX(data.t)
                set y2 = GetUnitY(data.t)
                set a = bj_RADTODEG * Atan2(y2 - y, x2 - x)
                call SetUnitFacing(data.d, a)
                if IsUnitInRange(data.d, data.t, 50) then
                    call DestroyEffect(data.art)
                    call KillUnit(data.d)
                    call DestroyEffect(AddSpecialEffectTarget(PRIMARY_EFFECT, data.t, "origin"))
                    call UnitDamageTarget(data.u, data.t, data.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                    call spark.SecondaryEffects(data.u, data.t, x2, y2)
                    call DestroyLightning(data.l)
                    set data.d = CreateUnit(Player(12), DUMMY_ID, x2, y2, 0)
                    call SetUnitScale(data.d, 3.5, 3.5, 3.5)
                    call SetUnitVertexColor(data.d, 170, 170, 255, 255)
                    call DestroyEffect(AddSpecialEffectTarget(PRIMARY_EFFECT, data.d, "origin"))
                    call KillUnit(data.d)
                    call data.destroy()
                    set bolt.total = bolt.total - 1
                    set bolt.arr[i] = bolt.arr[bolt.total]
                else     
                    set p = SquareRoot((x - x2) * (x - x2) + (y - y2) * (y - y2))
                    set d = 17 + (p * .012)
                ///------------------------------------
                    set dx = SafeX(x + d * Cos(a * bj_DEGTORAD))
                    set dy = SafeY(y + d * Sin(a * bj_DEGTORAD))
                    call SetUnitX(data.d, dx)
                    call SetUnitY(data.d, dy)
                ////-----------------------------------
                    set x = GetUnitX(data.d)
                    set y = GetUnitY(data.d)
                    set a = (a-180)
                    set dx = x + 200 * Cos(a * bj_DEGTORAD)
                    set dy = y + 200 * Sin(a * bj_DEGTORAD)
                    call MoveLocation(loc, dx, dy)
                    call MoveLocation(loc2, x, y)
                    call MoveLightningEx(data.l, true, dx, dy, (GetLocationZ(loc)+90), x, y, (GetLocationZ(loc2)+GetUnitFlyHeight(data.d)))
                endif
                set i = i + 1
            endloop
            if bolt.total == 0 then
                call PauseTimer(bolt.tim)
            endif
        endmethod  
    
    endstruct   
    
    // Charged bolts //
    private struct Charged
        unit u
        unit array mis[CHARGED_BOLTS_AMOUNT]
        real array fh[CHARGED_BOLTS_AMOUNT]
        real array fi[CHARGED_BOLTS_AMOUNT]
        real array x[CHARGED_BOLTS_AMOUNT]
        lightning array l[CHARGED_BOLTS_AMOUNT]
        lightning array l2[CHARGED_BOLTS_AMOUNT]
        boolean array fb[CHARGED_BOLTS_AMOUNT]
        effect array fx[CHARGED_BOLTS_AMOUNT]
        boolean dead = false //If the flag is turned on, the charged bolts dissapear until the hero is alive again
        real tick = 0
        real xtick = .94 //Defines the duration of the lightning rotation
        real rat = SquareRoot(NORMAL_OFFSET) / bj_PI
        real limit = NORMAL_OFFSET
        boolean charged = false
        static thistype array arr
        static integer total = 0
        static timer tim = CreateTimer()
        ////
        private static string str1 = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldBuff.mdl"
        ////
        static method create takes unit u returns thistype
            local thistype this = thistype.allocate()
            local integer i = 0
            local trigger t = CreateTrigger()
            local real x
            local real y
            local real cx = GetUnitX(u)
            local real cy = GetUnitY(u)
            set this.u = u
            loop
            exitwhen i > Cbolts
                set this.mis[i] = CreateUnit(GetOwningPlayer(u), 'e001', GetUnitX(u), GetUnitY(u), 0)
                set this.fx[i] = AddSpecialEffectTarget(BALL_ART , this.mis[i], "origin")
                set x = cx + 60 * Cos(( (360 / (Cbolts+1)) * (i+1) )  * bj_DEGTORAD)
                set y = cy + 60 * Sin(( (360 / (Cbolts+1)) * (i+1) )  * bj_DEGTORAD)
                call SetUnitX(this.mis[i], x)
                call SetUnitY(this.mis[i], y)
                call UnitAddAbility(this.mis[i], 'Amrf')
                call SetUnitTimeScale(this.mis[i], 0)
                call UnitRemoveAbility(this.mis[i], 'Amrf')
                set this.fh[i] = 30 * i
                call SetUnitFlyHeight(this.mis[i], this.fh[i], 0)
                call SetUnitScale(this.mis[i], CHARGED_BOLTS_SIZE, CHARGED_BOLTS_SIZE, CHARGED_BOLTS_SIZE)
                set this.fb[i] = true
                set this.x[i] = bj_RADTODEG * Atan2(y - cy, x - cx)
                set i = i + 1
            endloop    
            call TriggerAddCondition(t, Condition(function Charged.ready))
            call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_SPELL_CAST)
            if thistype.total == 0 then
                call TimerStart(thistype.tim, .047, true, function Charged.fly)
            endif
            set thistype.total = thistype.total + 1
            set thistype.arr[thistype.total - 1] = this
            return this
        endmethod
        
        static method ready takes nothing returns boolean
            local thistype this
            local integer i = 0
            local integer k = 0
            local sound snd
            if GetSpellAbilityId()!= ABILITY_ID then
                return false
            else
                set this = thistype.GetInstance(GetTriggerUnit())
                if this.charged then
                    return false
                endif    
                set snd = CreateSound("Units\\Orc\\StasisTotem\\StasisTotem.wav", false, false, true, 12700, 12700, "")
                call AttachSoundToUnit(snd, GetTriggerUnit())
                set this.charged = true
                set this.xtick = .8
                set this.rat = this.rat + SquareRoot(MAX_OFFSET)
                set this.limit = MAX_OFFSET
                call StartSound(snd)
                call KillSoundWhenDone(snd)
                set snd = null
                loop
                exitwhen i>Cbolts
                    set k = i + 1
                    if k > Cbolts then
                        set k = 0
                    endif         
                    set this.l[i] = AddLightning(CHARGED_LIGHTNING, true, GetUnitX(this.mis[i]), GetUnitY(this.mis[i]), GetUnitX(this.u), GetUnitY(this.u))
                    call SetLightningColor(this.l[i], 1, 1, 1, .6)
                    set this.l2[i] = AddLightning(ORBITAL_LIGHTNING, true, GetUnitX(this.mis[i]), GetUnitY(this.mis[i]),  GetUnitX(this.mis[k]), GetUnitY(this.mis[k]))
                    call SetLightningColor(this.l2[i], .3, .1, 1, 1)   
                    set i = i + 1            
                endloop    
            endif
            return false
        endmethod
        
        static method GetInstance takes unit u returns integer
            local thistype this
            local integer i = 0
            loop
            exitwhen i >= thistype.total
                set this = thistype.arr[i]
                if IsUnit(u, this.u) then
                    return this
                endif
                set i = i + 1
            endloop
            return 0
        endmethod    
                    
        static method fly takes nothing returns nothing
            local thistype this
            local integer i = 0
            local integer k = 0
            local real x =0
            local real y =0
            local real cx
            local real cy
            local integer j = 0
            loop
            exitwhen i >= thistype.total
                set this = thistype.arr[i]
                set k = 0
                set j = 0
                set cx = GetUnitX(this.u)
                set cy = GetUnitY(this.u)
                loop
                exitwhen k > Cbolts
                    set j = k + 1
                    if j > Cbolts then
                        set j = 0
                    endif  
                    if (not(this.dead)) and this.mis[k] != null then
                        if this.fb[k] then            
                            set this.fh[k] = this.fh[k] + 3
                        else
                            set this.fh[k] = this.fh[k] - 3
                        endif
                        if GetUnitFlyHeight(this.mis[k])<50 then
                            set this.fb[k] = true 
                        elseif GetUnitFlyHeight(this.mis[k]) > 275 then
                            set this.fb[k] = false
                        endif    
                        set this.tick = this.tick + .047
                        call SetUnitFlyHeight(this.mis[k], this.fh[k], 530)
                        set this.x[k] = this.x[k] + 4
                        if this.rat < this.limit then
                            set this.rat = this.rat + 2
                        elseif this.rat > this.limit then
                            set this.rat = this.rat - .5
                        endif    
                        set x = cx + this.rat * Cos(this.x[k] * bj_DEGTORAD) 
                        set y = cy + this.rat * Sin(this.x[k] * bj_DEGTORAD)
                        call SetUnitX(this.mis[k], x)
                        call SetUnitY(this.mis[k], y)
                        if this.charged then
                            if this.xtick > 0 then
                                set this.x[k] = this.x[k] + 6
                                call MoveLocation(loc, x, y)
                                call MoveLocation(loc2, GetUnitX(this.u), GetUnitY(this.u))
                                call MoveLightningEx(this.l[k], true, x, y, (GetLocationZ(loc)+GetUnitFlyHeight(this.mis[k])), cx, cy, (GetLocationZ(loc2)+GetUnitFlyHeight(this.u)+20))
                                call MoveLocation(loc2, GetUnitX(this.mis[j]), GetUnitY(this.mis[j]))
                                call MoveLightningEx(this.l2[k], true, x, y, (GetLocationZ(loc)+GetUnitFlyHeight(this.mis[k])), GetUnitX(this.mis[j]), GetUnitY(this.mis[j]), (GetLocationZ(loc2)+GetUnitFlyHeight(this.mis[j])))
                            else
                                call DestroyLightning(this.l[k])
                                call DestroyLightning(this.l2[k])
                            endif
                        endif    
                        if this.tick > 1.75 then
                            set this.tick = 0
                            call DestroyEffect(AddSpecialEffectTarget(thistype.str1 , this.mis[k], "origin"))
                        endif    
                    endif  
                    set k = k + 1
                endloop
                if this.charged then
                    if this.xtick > 0 then
                        set this.xtick = this.xtick - .05
                    else
                        set this.charged = false
                        set this.limit = NORMAL_OFFSET
                    endif
                endif    
                if GetWidgetLife(this.u)<.405 then
                    call this.hide()
                    set this.dead = true
                elseif GetWidgetLife(this.u)>=.405 and this.dead then
                     set this.dead = false
                     call this.show()
                 endif    
                set i = i + 1
            endloop
        endmethod
        
        method hide takes nothing returns nothing
            local integer i = 0
            loop
            exitwhen i > Cbolts
                if .mis[i] != null then
                    //call UnitRemoveAbility(.mis[i], 'Aloc')
                    call ShowUnit(.mis[i], false)
                endif
                set i = i + 1
            endloop
        endmethod    
        
        method show takes nothing returns nothing
            local integer i = 0
            loop
            exitwhen i > Cbolts
                if .mis[i] != null then
                    call ShowUnit(.mis[i], true)
                    call UnitAddAbility(.mis[i], 'Aloc')
                endif
                set i = i + 1
            endloop
        endmethod       
    endstruct    
    
    ///////===================\\\\\\\
    
    private function BoltStart takes nothing returns nothing
        call bolt.create(GetTriggerUnit(), GetSpellTargetUnit())
    endfunction
    
    private function IsBolt takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
    endfunction
    
    private function BoltCharge takes nothing returns boolean 
        if GetLearnedSkill() == ABILITY_ID then
            if GetUnitAbilityLevel(GetTriggerUnit(), ABILITY_ID)<2 then
                call Charged.create(GetTriggerUnit())
            endif
        endif
        return false
    endfunction    
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger ( )
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function IsBolt))
        call TriggerAddAction(t, function BoltStart)
        set t = CreateTrigger( )
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
        call TriggerAddCondition(t, Condition(function BoltCharge))
        set t = null
        set BE = Condition(function spark.Enum)
        set MINX = GetRectMinX(bj_mapInitialPlayableArea)
        set MINY = GetRectMinY(bj_mapInitialPlayableArea)
        set MAXX = GetRectMaxX(bj_mapInitialPlayableArea)
        set MAXY = GetRectMaxY(bj_mapInitialPlayableArea)
    endfunction
endlibrary
Changes:
v1.1: Did some important tweaks and a small fix
v1.1c: More eye candy! more ear candy! Some optimizations
v1.2: Made some additional tweaks, Made even more constants add added some documentation next to them.
v1.3: Included GroupUtils as an optional library. Some various tweaks...
v1.4: Moved the functions within the structs

I suggest you turn the sound on if you haven't.

Keywords:
zeus, bolt, lightning, storm, spark, wrath, of, static, charge, charged
Contents

Wrath of Zeus (Map)

Reviews
00:04, 24th Aug 2010 TriggerHappy: Why is the trigger entitled Thundergod Bolt when the spells name is Wrath of Zeus? Your constant variables should be in all caps (LIKE_THIS). In programming langauges and even in blizzards jass that is the...

Moderator

M

Moderator

00:04, 24th Aug 2010
TriggerHappy:

Why is the trigger entitled Thundergod Bolt when the spells name is Wrath of Zeus?
  • Your constant variables should be in all caps (LIKE_THIS). In programming langauges and even in blizzards jass that is the convention which is used for constants.
  • You don't need to null struct members if each time a new instance is created the variable is going to get overwritten. So your onDestroy methods are useless.
  • IIRC boolexprs are stored in a table so they don't actually leak if they are used multiple times. Even if I am wrong why do you create a new boolexpr each time when you could just create a global one and re-use it?
  • You really should use group recycling.
  • You should have a custom .create method which sets all the struct data per instance instead of doing it directly. This allows for better organization and makes it easier for people to understand what's going on while reading your code.

I've only looked at about half the code.

17:16, 24th Aug 2010
The_Reborn_Devil:

The code looks good now ^^
0/5 :D?


Status: Approved
Rating Highly Recommended [0+Gender Bonus(5)]
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
This is a pretty awesome-looking spell.

I didn't really look at the code thoroughly, but I have some things I want to point out:

  • You should make your angles radians by default. Converting to degrees is only used for setting the unit's facing which happens less often then moving the unit.
  • Destroying boolexprs is bad. Just do
    JASS:
    call  GroupEnumUnitsInRange(Group, data.x, data.y, 125, Condition(function  SparkEnum))
    According to grim001 (#5) you don't need to use a variable (local/global) for that either.
  • Any reason for putting minus signs in front of the zeroes here?
    JASS:
    call SetUnitPosition(.mis[i], -0, -0)
  • You're could have simplified some of your ifs:
    JASS:
            if IsUnitInGroup(e, this.damaged) then
                ...
            else
                if (not(IsUnitType(e, UNIT_TYPE_STRUCTURE))) and (GetWidgetLife(e)>.405) and (IsUnitEnemy(e, GetOwningPlayer(this.u))) then
                    ...
                endif    
            endif
    ->
    JASS:
            if not IsUnitInGroup(e, this.damaged) then            
                if (not(IsUnitType(e, UNIT_TYPE_STRUCTURE))) and  (GetWidgetLife(e)>.405) and (IsUnitEnemy(e, GetOwningPlayer(this.u)))  then
                    ...
                endif    
            endif
    JASS:
                if GetSpellAbilityId()!= Ability_Id then
                     return false
                else
                    ...
                endif
    ->
    JASS:
                if GetSpellAbilityId()== Ability_Id then
                    ...
                endif
    JASS:
                    if GetWidgetLife(this.u)<.405 then
                        ...
                    elseif GetWidgetLife(this.u)>=.405 and this.dead then
                         ...
                     endif
    ->
    JASS:
                    if GetWidgetLife(this.u)<.405 then
                        ...
                    elseif this.dead then
                         ...
                     endif
There also seems to be a bug with the revolving balls. If the caster casts the spell multiple times, one of the balls never gets back in the correct position.

Edit:
I've attached two pictures, one where the balls are correctly placed while the second one shows what I mean.
 

Attachments

  • Normal.PNG
    Normal.PNG
    207.6 KB · Views: 327
  • TooClose.PNG
    TooClose.PNG
    227.8 KB · Views: 360
Level 22
Joined
Nov 14, 2008
Messages
3,256
The safe x and y functions, aren't they as same as you would have Vexorians BoundSentinel library?

Who am I to judge the code but wouldn't it be better to use a group recycler like GroupUtils instead of destroying?

I think also that attack type, damage type and weapon type should be changeable in the globals.

This is way much code for my head right now, giving me headdicks looking at it 500+ lines.

Looking good so far, guess the mods will find some more.
 
Level 7
Joined
Jun 6, 2010
Messages
224
The safe x and y functions, aren't they as same as you would have Vexorians BoundSentinel library?
no? Obvious functions, anyone can write one.
I think also that attack type, damage type and weapon type should be changeable in the globals.
isn't damaage type lightning quite the obvious?
Who am I to judge the code but wouldn't it be better to use a group recycler like GroupUtils instead of destroying?

Using grouputils would make it much slower since i gotta allocate, clear, sort a new group for each lightning instance. Trust me, i did my homework before even starting to think about this spell.
 
Level 7
Joined
Jun 6, 2010
Messages
224
Dunno, but why rewrite it instead of just using another library. Well doesn't matter.

Maybe but how about attack type?

That's true, just an option :)

Like I said, vJASS isn't my best side, only knows the basics.

your statements and answers were ok, i just had to answer.
but right now i need some feedback on to make this spell look cooler :p
and my target is to give it a directors cut because of it's awesomeness
 
Level 15
Joined
Jul 6, 2009
Messages
889
isn't damaage type lightning quite the obvious?

I think he means having constant configurables at the top with all your other global variables =)

The ability is nice visually =) But is there an option to allow the user to change how many links are made o_O"? Like, the orbs rotating. =) Hehe, I love the lightning beind used a missile itself. <3
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
creating new objects like groups/lightnings/timers will probably be slower then recycling them (did not test it though)

really nice spell
only a few very minor things to improve
not really worth to mention:
GetRectMaxX/Y in SafeX/Y could be saved in a global or smthn
no need to save the boolexpr since it will be recycled automatically
but don't destroy and don't null them since that will make them leak a lot
SquareRoot(x) > y can be written as x > y*y which is a little bit faster

but I don't think that it will get directiors cut because mods don't give directors cut for spells
(only remember 2 systems which got directios cut....ever)
 
Level 7
Joined
Jun 6, 2010
Messages
224
creating new objects like groups/lightnings/timers will probably be slower then recycling them (did not test it though)

really nice spell
only a few very minor things to improve
not really worth to mention:
GetRectMaxX/Y in SafeX/Y could be saved in a global or smthn
no need to save the boolexpr since it will be recycled automatically
but don't destroy and don't null them since that will make them leak a lot
SquareRoot(x) > y can be written as x > y*y which is a little bit faster

but I don't think that it will get directiors cut because mods don't give directors cut for spells
(only remember 2 systems which got directios cut....ever)

You are both right and wrong there.

Create those objects is slower than trying to allocate through 20 instances to recycle them

We can have more than 50 instances to allocate through, so it makes allocation slower than creating an object and assign it as a member.

------------------
GetRectMaxX/Y in SafeX/Y could be saved in a global or smthn
yeap, i propably forgot to do that.

no need to save the boolexpr since it will be recycled automatically
but don't destroy and don't null them since that will make them leak a lot

i'm not destroying the boolexpr, i only save it once in the init and re-use it without saving it or null it. Don't know what are you talking about there.

SquareRoot(x) > y can be written as x > y*y which is a little bit faster

Since when is SquareRoot() that slow? Are common maths intensively slow in jass?
 
Level 19
Joined
Feb 4, 2009
Messages
1,313
You are both right and wrong there.

Create those objects is slower than trying to allocate through 20 instances to recycle them

We can have more than 50 instances to allocate through, so it makes allocation slower than creating an object and assign it as a member.

------------------

yeap, i propably forgot to do that.



i'm not destroying the boolexpr, i only save it once in the init and re-use it without saving it or null it. Don't know what are you talking about there.



Since when is SquareRoot() that slow? Are common maths intensively slow in jass?

just make something like this then:

JASS:
globals
    private integer max = 0
    private timer array//don't know how to initialize arrays, usually I use these udg_ones :P
endglobals

function RecycleTimer takes timer t returns nothing
    call PauseTimer(t)
    set max = max + 1
    set timer[max] = t
endfunction

function NewTimer takes nothing returns timer
    if max < 1 then
        return CreateTimer()
    endif
    set max = max - 1
    return timer[max+1]
endfunction
should be pretty fast (probably forgot something, I don't use jass frequently)

I did not want to say that you did that with the boolexpr
just wanted to say that it IS like that IF you do it
sorry if you got me wrong xD

unfortunately arithmetics in jass are very slow
x*x*x*x*x*x*x*x*x*x is even slower than Pow(x, 10) although it probably compiles to something with for or while loops and many more lines
but for small numbers it still works
difference is really small though
 
Level 7
Joined
Jun 6, 2010
Messages
224
I don't think it's complicated with external libraries. You just import them, save and done :D

It's not just that... If you config the spell to spawn for than 24 lightning effects, it makes allocating of new groups a little bit slower than just creating one.

i tried it and the damage was a few milliseconds offset with 100+ lightnings spawned.

I'm aware that recycling group is way better but this is just overhead...
 
Level 22
Joined
Dec 31, 2006
Messages
2,216
Using TimerUtils isn't necessary, lol. Just use something like this:

JASS:
library TimerRecycler
    globals
        private integer N = 0
        private timer array Timers
    endglobals

    function ReleaseTimer takes timer t returns nothing
        debug if t != null then
            call PauseTimer(t)
            set Timers[N] = t
            set N = N + 1
        debug else
            debug call BJDebugMsg("Tried to release a null timer!")
        debug endif
    endfunction

    function NewTimer takes nothing returns timer
        if N == 0 then
            return CreateTimer()
        endif
        set N = N - 1
        return Timers[N]
    endfunction
endlibrary
 
Level 7
Joined
Jun 6, 2010
Messages
224
Very nice spell 4/5


I found 1 bug. When you cast the spell on a unit or on yourself that is close to the place where the maps end. The lightning bugs. I think it has something with collision to do.

Pictures http://s1031.photobucket.com/albums/y375/Arvid_Kronosjo/?action=view&current=bug2.jpg
http://s1031.photobucket.com/albums/y375/Arvid_Kronosjo/?action=view&current=bug1.jpg

it's not a bug, just the lightning doesn't go outside boundaries so you won't see some random lightning across your map. ;P

We love wc3
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
this is useless
private constant boolean USE_GROUPUTILS = true //Whether to use Group utils library, if the library isn't found, it will CreateGroup() instead

and giving that option is useless as well. If they have GroupUtils, use it >.>.

use static if LIBRARY_libName instead (was commented on before)

This should be inside of a struct so that it doesn't exist unless you actually use it
private constant group Group = CreateGroup()

constants are always uppercase >.>

JASS:
private boolexpr BE

private real MINX
private real MINY
private real MAXX
private real MAXY

variables are all lowercase with camelcasing. If you want the feel of a constant do like-
JASS:
private constant method operator MAX_X takes nothing returns real
	return maxX
endmethod

private struct spark alone will add a lot of extra stuff you may not need :\.
I would honestly inline my allocate into the set data = spark.create() and deallocate into the call data.destroy()

This one is a major wtf
method onDestroy takes nothing returns nothing

All the stuff with the stuct that doesn't extend array and non inlining of allocate applies
to the bolt struct as well.

Also
JASS:
private struct spark
private struct bolt
private struct Charged

See this chaos of a convention so far? It's uppercase on the first letter and then
camelcase for structs

for this.xtick, the this. is not needed.

call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)

can inline

JASS:
set MINX = GetRectMinX(bj_mapInitialPlayableArea)
        set MINY = GetRectMinY(bj_mapInitialPlayableArea)
        set MAXX = GetRectMaxX(bj_mapInitialPlayableArea)
        set MAXY = GetRectMaxY(bj_mapInitialPlayableArea)

Use GetWorldBounds() instead or w/e that native is.

Keep in mind that I briefly looked over your code for the most common problems ; ).

3/5 for the code
5/5 for visual effects ; )

so 4/5
 
Level 7
Joined
Jun 6, 2010
Messages
224
uhm, can you re-evalute your review
half of it looks dumb -.-

since structs are private, the name matters not.
you worry TOO much about upper case and lower case and if it's beautiful.
it's private.

Using 3 structs so users can actually learn something instead of fighting with interfaces and child structs.
And yea i allocate and de-allocate with
JASS:
spark.create() spark.destroy()
whats wrong with you?

Why would GetWorldBounds() be any better to use than the bj ?
it also leaks a rect

=====
Since a moderator gave a rating of 5/5 it means that the code was better deserving of 3/5 ...

i suggest you re-evalute your review by looking again and again to find the "why"

Reborn_Devil was also confused till he read it multiple times.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Why would GetWorldBounds() be any better to use than the bj ?
it also leaks a rect

Then destroy the rect after making it. Keep in mind playable map area is based on initial camera bounds, which may not cover the entire map at the very start. Because of this you must always use GetWorldBounds(). If you don't, your system will fail with maps that have initial camera bounds that don't cover the entire map.

My review of red shift was 1/5, so you did improve a lot. I didn't vote on the rating because your resource still looks like a rough draft to me, so I kind of regard it as need polishing ; ).
 
Level 7
Joined
Jun 6, 2010
Messages
224
if you destroy a rect you may not use it again.
and i hardly know any map that has a different initiable playable map area

the spell may indeed need improvements in the near future, as if making some sort of interface to allow users to interact on the events of the lightning bolt.

As for now it deserves those 5/5 =)
 
Level 2
Joined
Aug 17, 2010
Messages
10
3/5 for the code
5/5 for visual effects ; )

so 4/5

This spell deserves 6/5

You gotta admit , you've never seen something like it before.
Uniqueness - 5/5
Effect - 10/5 ?
Sounds - 5/5
Idea - 5/5
User Friendly - 4/5 (well it got the most constants but it could use more ...)

have you seen the effect with 60 bolts and 360 static lightnings? OMFG
 
Level 7
Joined
Sep 8, 2009
Messages
90
Was just bored these days and I decided to check the hive when I noticed this spell.Nice sound effects ;)
Can`t say anything for the coding since I don`t understand jass but from what I`ve seen its pretty nice spell.I`ll give it 5/5 + rep because of it and because some of the effects remind me of my old arcane bolt spell.Mighta start making GUI junk spells again :)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I had a gigantic post telling you a lot of great advice on how to optimize this map and the server gave me a "hiccupping" error. Long story short, I have made a TON of VERY NEEDED optimizations and I strongly recommend you implement this version I've cut out for you (sorry it's uncommented);

It is less code but with the exact same functionality.

JASS:
library WrathOfZeus initializer init requires optional GroupUtils, optional Recycle

    globals
        private constant integer ABILITY_ID = 'A000'        //Raw code of the triggering ability
        private constant integer DUMMY_ID = 'e001'          //The dummy unit, please import dummy.mdl and make a proper dummy unit
        private constant integer STATIC_LINKS = 32          //The links that are created when the orb crashes
        private constant integer CHARGED_BOLTS_AMOUNT = 3   //The amount of orbiting missiles that are created around the caster when the ability is learned
        private constant real CHARGED_BOLTS_SIZE = 0.88     //The scale value of the orbiting missiles
        private constant real PRIMARY_BASE_DAMAGE = 130.0   //The base damage when the orb crashes
        private constant real PRIMARY_INC_DAMAGE = 75.0     // The increment damage of the orb
        private constant real SECONDARY_BASE_DAMAGE = 50.0  //The damage each static link deals
        private constant real SECONDARY_INC_DAMAGE = 25.0   // The increment damage of each static link
        private constant real BOLT_SCALE = 2.75             //The scale of the lightning bolt
        private constant real NORMAL_OFFSET = 145.0         //The default rotating offset of the orbiting bolts
        private constant real MAX_OFFSET = 425.0            //The maximum rotating offset the bolts can reach when overloading
        private constant real ENUM_OFFSET = 75.0            //the range that units are checked from each static link. You should decrease this if you use a LOT of static links!
        private constant string BALL_ART = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl"                             //The missiles that rotate around the caster
        private constant string TAIL_LIGHTNING = "CLPB"     //The lightning tail of the bolt
        private constant string CHARGED_LIGHTNING = "CLSB"  //The lightning tail of the secondary static links
        private constant string ORBITAL_LIGHTNING = "DRAB"  //The lightning that links all the orbital missiles
        private constant string BOLT_LIGHTNING = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"   //The lightning bolt art
        private constant string PRIMARY_EFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"                                     //The effect that is created upon impact
        private constant string SECONDARY_EFFECT = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldBuff.mdl"           //The effect that is created on damaged units of secondary effect
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
        ///
    endglobals
    
    
    ////===============
    
        native UnitAlive takes unit id returns boolean
    
    ////===============
    
    
    globals
        private location loc = Location(0.00, 0.00)
        private boolexpr BE
        private constant group GROUP = CreateGroup()
        private constant integer CHARGED_BOLTS = CHARGED_BOLTS_AMOUNT - 1
        private real MINX
        private real MINY
        private real MAXX
        private real MAXY
    endglobals 
    
    /////=================
    //Extra functions
    
    //
    private function SafeX takes real x returns real
       if (x < MINX) then
           set x = MINX
       elseif (x > MAXX) then
           set x = MAXX
       endif
       return x
    endfunction

    private function SafeY takes real y returns real
       if (y < MINY) then
           set y = MINY
       elseif (y > MAXY) then
           set y = MAXY
       endif
       return y
    endfunction 
    
    /////////=======================================
    
    private struct spark
        unit u
        lightning l        
        real ang
        real sx
        real sy
        real x
        real y
        real time = 1.5
        real time2 = 2.0
        real dmg
        group damaged
        static spark instance
        static integer tot = 0
        static spark array ar
        static code LoopCode
        static timer stim = CreateTimer()
         
         static method SecondaryEffects takes unit u, unit t, real x, real y returns nothing
             local integer i = 1
             local spark dat
             loop
                set dat = spark.create()
                set dat.u = u
                set dat.sx = x
                set dat.sy = y
                set dat.x = x
                set dat.y = y
                set dat.ang = (6.28319 / STATIC_LINKS) * i
                static if (LIBRARY_GroupUtils) then
                    set dat.damaged = NewGroup()
                elseif (LIBRARY_Recycle) then
                    set dat.damaged = Group.get()
                else
                    set dat.damaged = CreateGroup()
                endif    
                if t != null then
                   call GroupAddUnit(dat.damaged, t)
                endif   
                set dat.dmg = SECONDARY_BASE_DAMAGE + SECONDARY_INC_DAMAGE * (GetUnitAbilityLevel(dat.u, ABILITY_ID) - 1)
                set dat.l = AddLightning(CHARGED_LIGHTNING, true, x, y, x + 5.0, y + 5.0)
                call SetLightningColor(dat.l, 0.70, 0.70, 1.00, 0.70)
                if (spark.tot == 0) then
                    call TimerStart(spark.stim, 0.042, true, LoopCode)
                endif
                set spark.ar[spark.tot] = dat
                set spark.tot = spark.tot + 1
                exitwhen i == STATIC_LINKS
                set i = i + 1
            endloop        
        endmethod
    
        static method Enum takes nothing returns boolean
            local unit e = GetFilterUnit()
            if (not IsUnitInGroup(e, instance.damaged) and not IsUnitType(e, UNIT_TYPE_STRUCTURE) and UnitAlive(e) and IsUnitEnemy(e, GetOwningPlayer(instance.u))) then
                call DestroyEffect(AddSpecialEffectTarget(SECONDARY_EFFECT, e, "origin"))
                call UnitDamageTarget(instance.u, e, instance.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                call GroupAddUnit(instance.damaged, e)
            endif   
            set e = null
            return false
        endmethod
    
        static method Loop takes nothing returns nothing
            local spark dat
            local integer i = 0
            local real p
            local real tempReal
            loop
                exitwhen i >= spark.tot
                set dat = spark.ar[i]
                if dat.time > 0 then
                    set dat.time = dat.time - 0.042
                    set dat.x = SafeX(dat.x + 30.0 * Cos(dat.ang))
                    set dat.y = SafeY(dat.y + 30.0 * Sin(dat.ang))
                    set p = SquareRoot((dat.x - dat.sx) * (dat.x - dat.sx) + (dat.y - dat.sy) * (dat.y - dat.sy))
                    if (p > 450.0) then
                        set dat.sx = dat.sx + 30.0 * Cos(dat.ang)
                        set dat.sy = dat.sy + 30.0 * Sin(dat.ang)
                    endif
                    set instance = dat
                    call GroupEnumUnitsInRange(GROUP, dat.x, dat.y, ENUM_OFFSET, BE)
                    call MoveLocation(loc, dat.sx, dat.sy)
                    set tempReal = GetLocationZ(loc)
                    call MoveLocation(loc, dat.x, dat.y)
                    call MoveLightningEx(dat.l, true, dat.sx, dat.sy, tempReal, dat.x, dat.y, GetLocationZ(loc))
                    
                elseif (SquareRoot((dat.x - dat.sx) * (dat.x - dat.sx) + (dat.y - dat.sy) * (dat.y - dat.sy)) > 50.0) and dat.time2 > 0.00 then
                    set dat.sx = dat.sx + 30.0 * Cos(dat.ang)
                    set dat.sy = dat.sy + 30.0 * Sin(dat.ang)
                    call MoveLocation(loc, dat.sx, dat.sy)
                    set tempReal = GetLocationZ(loc)
                    call MoveLocation(loc, dat.x, dat.y)
                    call MoveLightningEx(dat.l, true, dat.sx, dat.sy, tempReal, dat.x, dat.y, GetLocationZ(loc)) 
                    set instance = dat
                    call GroupEnumUnitsInRange(GROUP, dat.sx, dat.sy, 125.0, BE)
                    set dat.time2 = dat.time2 - 0.040
                else
                    call dat.destroy()
                    set spark.tot = spark.tot - 1
                    set spark.ar[i] = spark.ar[spark.tot]
                endif
                set i = i + 1
            endloop
            if (spark.tot == 0) then
                call PauseTimer(spark.stim)
            endif
        endmethod
        
        private static method onInit takes nothing returns nothing
            set LoopCode = function spark.Loop
        endmethod
        
        private method onDestroy takes nothing returns nothing
            call DestroyLightning(this.l)
            static if (LIBRARY_GroupUtils) then
                call ReleaseGroup(this.damaged)
            elseif (LIBRARY_Recycle) then
                call Group.release(this.damaged)
            else
                call GroupClear(this.damaged)
                call DestroyGroup(this.damaged)
            endif    
        endmethod    
    endstruct    
    
    
    private struct bolt
        unit u
        unit t
        unit d
        real damage
        lightning l
        effect art
        static integer total = 0
        static bolt array arr
        static timer tim = CreateTimer()
    
        static method Loop takes nothing returns nothing
            local bolt dat
            local integer i = 0
            local real a
            local real x
            local real y
            local real x2
            local real y2
            local real distance
            local real tempReal
            loop
                exitwhen i >= bolt.total
                set dat = bolt.arr[i]
                set x = GetUnitX(dat.d)
                set y = GetUnitY(dat.d)
                set x2 = GetUnitX(dat.t)
                set y2 = GetUnitY(dat.t)
                set a = Atan2(y2 - y, x2 - x)
                call SetUnitFacing(dat.d, a * bj_RADTODEG)
                if (IsUnitInRange(dat.d, dat.t, 50.0)) then
                    call DestroyEffect(dat.art)
                    call KillUnit(dat.d)
                    call DestroyEffect(AddSpecialEffectTarget(PRIMARY_EFFECT, dat.t, "origin"))
                    call UnitDamageTarget(dat.u, dat.t, dat.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                    call spark.SecondaryEffects(dat.u, dat.t, x2, y2)
                    call DestroyLightning(dat.l)
                    set dat.d = CreateUnit(Player(15), DUMMY_ID, x2, y2, 0)
                    call SetUnitScale(dat.d, 3.5, 3.5, 3.5)
                    call SetUnitVertexColor(dat.d, 170, 170, 255, 255)
                    call DestroyEffect(AddSpecialEffectTarget(PRIMARY_EFFECT, dat.d, "origin"))
                    call KillUnit(dat.d)
                    call dat.destroy()
                    set bolt.total = bolt.total - 1
                    set bolt.arr[i] = bolt.arr[bolt.total]
                else     
                    set distance = 17.0 + SquareRoot((x - x2) * (x - x2) + (y - y2) * (y - y2)) * 0.012
                ///------------------------------------
                    set x2 = SafeX(x + distance * Cos(a))
                    set y2 = SafeY(y + distance * Sin(a))
                    call SetUnitX(dat.d, x2)
                    call SetUnitY(dat.d, y2)
                ////-----------------------------------
                    set x = GetUnitX(dat.d)
                    set y = GetUnitY(dat.d)
                    set a = (a - bj_PI)
                    set x2 = x + 200.0 * Cos(a)
                    set y2 = y + 200.0 * Sin(a)
                    call MoveLocation(loc, x2, y2)
                    set tempReal = GetLocationZ(loc) + 90.0
                    call MoveLocation(loc, x, y)
                    call MoveLightningEx(dat.l, true, x2, y2, tempReal, x, y, GetLocationZ(loc) + GetUnitFlyHeight(dat.d))
                endif
                set i = i + 1
            endloop
            if (bolt.total == 0) then
                call PauseTimer(bolt.tim)
            endif
        endmethod  
    
        static method create takes unit u returns bolt        
            local bolt dat = bolt.allocate()
            local real angle = GetUnitFacing(u) * bj_DEGTORAD
            local real x
            local real y
            local real x2
            local real y2
            local sound snd
            local string s
            if (GetRandomInt(0, 1) == 1) then
                set s = "Abilities\\Spells\\Orc\\LightningShield\\LightningShieldTarget.wav" 
            else
                set s = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBolt.wav" 
            endif
            set snd = CreateSound(s, false, false, true, 12700, 12700, "")
            set dat.u = u
            set x = GetUnitX(u) + 100.0 * Cos(angle)
            set y = GetUnitY(u) + 100.0 * Sin(angle)
            set dat.d = CreateUnit(GetTriggerPlayer(), DUMMY_ID, x, y, 0)
            call AttachSoundToUnit(snd, dat.d)
            call StartSound(snd)
            call KillSoundWhenDone(snd)
            set snd = null
            set x2 = GetUnitX(dat.d) + 200.0 * Cos(angle - bj_PI)
            set y2 = GetUnitY(dat.d) + 200.0 * Sin(angle - bj_PI)
            set dat.t = GetSpellTargetUnit()
            call UnitAddAbility(dat.d, 'Amrf')
            call UnitRemoveAbility(dat.d, 'Amrf')
            call SetUnitFlyHeight(dat.d, 90.0, 550.0)
            call SetUnitTimeScale(dat.d, 0.00)
            call SetUnitScale(dat.d, BOLT_SCALE, BOLT_SCALE, BOLT_SCALE)
            set dat.l = AddLightning(TAIL_LIGHTNING, true, x2, y2, x, y)
            set dat.damage = PRIMARY_BASE_DAMAGE + PRIMARY_INC_DAMAGE * (GetUnitAbilityLevel(u, ABILITY_ID) - 1)
            set dat.art = AddSpecialEffectTarget(BOLT_LIGHTNING, dat.d, "origin")
            if bolt.total == 0 then
                call TimerStart(bolt.tim, 0.0303, true, function bolt.Loop)
            endif
            set bolt.arr[bolt.total] = dat
            set bolt.total = bolt.total + 1
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl", u, "weapon,right"))
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", dat.d, "origin"))
            return dat
        endmethod
    
    endstruct   
    
    // Charged bolts //
    private struct Charged
        unit u
        unit array mis[CHARGED_BOLTS_AMOUNT]
        real array fh[CHARGED_BOLTS_AMOUNT]
        real array fi[CHARGED_BOLTS_AMOUNT]
        real array x[CHARGED_BOLTS_AMOUNT]
        lightning array l[CHARGED_BOLTS_AMOUNT]
        lightning array l2[CHARGED_BOLTS_AMOUNT]
        boolean array fb[CHARGED_BOLTS_AMOUNT]
        effect array fx[CHARGED_BOLTS_AMOUNT]
        boolean dead = false //If the flag is turned on, the charged bolts dissapear until the hero is alive again
        real tick = 0.00
        real xtick = 0.94 //Defines the duration of the lightning rotation
        real rat = SquareRoot(NORMAL_OFFSET) / bj_PI
        real limit = NORMAL_OFFSET
        boolean charged = false
        static thistype array arr
        static integer total = 0
        static timer tim = CreateTimer()
        
        static method GetInstance takes unit u returns thistype
            local integer i = 0
            loop
                exitwhen i >= thistype.total
                if (u == thistype.arr[i].u) then
                    return thistype.arr[i]
                endif
                set i = i + 1
            endloop
            return 0
        endmethod 
        
        static method ready takes nothing returns boolean
            local thistype dat
            local integer i = 0
            local integer k
            local sound snd
            local real x
            local real y
            if (GetSpellAbilityId() == ABILITY_ID) then
                set dat = GetInstance(GetTriggerUnit())
                if (not dat.charged) then
                    set snd = CreateSound("Units\\Orc\\StasisTotem\\StasisTotem.wav", false, false, true, 12700, 12700, "")
                    call AttachSoundToUnit(snd, GetTriggerUnit())
                    call StartSound(snd)
                    call KillSoundWhenDone(snd)
                    set snd = null
                    set dat.charged = true
                    set dat.xtick = 0.80
                    set dat.rat = dat.rat + SquareRoot(MAX_OFFSET)
                    set dat.limit = MAX_OFFSET
                    loop
                        set k = i + 1
                        if (k > CHARGED_BOLTS) then
                            set k = 0
                        endif
                        set x = GetUnitX(dat.mis[i])
                        set y = GetUnitY(dat.mis[i])
                        set dat.l[i] = AddLightning(CHARGED_LIGHTNING, true, x, y, GetUnitX(dat.u), GetUnitY(dat.u))
                        call SetLightningColor(dat.l[i], 1.00, 1.00, 1.00, 0.60)
                        set dat.l2[i] = AddLightning(ORBITAL_LIGHTNING, true, x, y,  GetUnitX(dat.mis[k]), GetUnitY(dat.mis[k]))
                        call SetLightningColor(dat.l2[i], 0.30, 0.10, 1.00, 1.00)   
                        exitwhen i == CHARGED_BOLTS
                        set i = i + 1            
                    endloop    
                endif
            endif
            return false
        endmethod
        
        method hide takes nothing returns nothing
            local integer i = 0
            loop
                if this.mis[i] != null then
                    //call UnitRemoveAbility(.mis[i], 'Aloc')
                    call ShowUnit(this.mis[i], false)
                endif
                exitwhen i == CHARGED_BOLTS
                set i = i + 1
            endloop
        endmethod    
        
        method show takes nothing returns nothing
            local integer i = 0
            loop
                if this.mis[i] != null then
                    call ShowUnit(this.mis[i], true)
                    call UnitAddAbility(this.mis[i], 'Aloc')
                endif
                exitwhen i == CHARGED_BOLTS
                set i = i + 1
            endloop
        endmethod
        
        static method fly takes nothing returns nothing
            local thistype this
            local integer i = 0
            local integer k = 0
            local real x =0
            local real y =0
            local real cx
            local real cy
            local real tempReal
            local real tempX
            local real tempY
            local integer j = 0
            loop
                exitwhen i >= thistype.total
                set this = thistype.arr[i]
                set k = 0
                set j = 0
                set cx = GetUnitX(this.u)
                set cy = GetUnitY(this.u)
                loop
                    set j = k + 1
                    if j > CHARGED_BOLTS then
                        set j = 0
                    endif  
                    if (not(this.dead)) and this.mis[k] != null then
                        if this.fb[k] then            
                            set this.fh[k] = this.fh[k] + 3.00
                        else
                            set this.fh[k] = this.fh[k] - 3.00
                        endif
                        set tempReal = GetUnitFlyHeight(this.mis[k])
                        if tempReal < 50.0 then
                            set this.fb[k] = true 
                        elseif tempReal > 275.0 then
                            set this.fb[k] = false
                        endif    
                        set this.tick = this.tick + .047
                        call SetUnitFlyHeight(this.mis[k], this.fh[k], 530.0)
                        set this.x[k] = this.x[k] + 0.0698
                        if (this.rat < this.limit) then
                            set this.rat = this.rat + 2.00
                        elseif (this.rat > this.limit) then
                            set this.rat = this.rat - 0.50
                        endif    
                        set x = cx + this.rat * Cos(this.x[k]) 
                        set y = cy + this.rat * Sin(this.x[k])
                        call SetUnitX(this.mis[k], x)
                        call SetUnitY(this.mis[k], y)
                        if (this.charged) then
                            if (this.xtick > 0.00) then
                                set this.x[k] = this.x[k] + 0.10472
                                call MoveLocation(loc, x, y)
                                set tempReal = GetLocationZ(loc) + GetUnitFlyHeight(this.mis[k])
                                call MoveLocation(loc, cx, cy)
                                call MoveLightningEx(this.l[k], true, x, y, tempReal, cx, cy, GetLocationZ(loc) + GetUnitFlyHeight(this.u) + 20.0)
                                set tempX = GetUnitX(this.mis[j])
                                set tempY = GetUnitY(this.mis[j])
                                call MoveLocation(loc, tempX, tempY)
                                call MoveLightningEx(this.l2[k], true, x, y, tempReal, tempX, tempY, GetLocationZ(loc) + GetUnitFlyHeight(this.mis[j]))
                            else
                                call DestroyLightning(this.l[k])
                                call DestroyLightning(this.l2[k])
                            endif
                        endif    
                        if this.tick > 1.75 then
                            set this.tick = 0
                            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Orc\\LightningShield\\LightningShieldBuff.mdl", this.mis[k], "origin"))
                        endif    
                    endif
                    exitwhen k == CHARGED_BOLTS
                    set k = k + 1
                endloop
                if this.charged then
                    if this.xtick > 0.00 then
                        set this.xtick = this.xtick - 0.05
                    else
                        set this.charged = false
                        set this.limit = NORMAL_OFFSET
                    endif
                endif
                if not UnitAlive(this.u) then
                    call this.hide()
                    set this.dead = true
                elseif this.dead then
                    set this.dead = false
                    call this.show()
                endif
                set i = i + 1
            endloop
        endmethod
             
        static method create takes unit u returns thistype
            local thistype dat = thistype.allocate()
            local integer i = 0
            local real x
            local real y
            local real cx = GetUnitX(u)
            local real cy = GetUnitY(u)
            set dat.u = u
            loop
                set dat.mis[i] = CreateUnit(GetTriggerPlayer(), 'e001', cx, cy, 0.00)
                set dat.fx[i] = AddSpecialEffectTarget(BALL_ART , dat.mis[i], "origin")
                set x = cx + 60.0 * Cos(6.2831853 / (CHARGED_BOLTS + 1) * (i + 1))
                set y = cy + 60.0 * Sin(6.2831853 / (CHARGED_BOLTS + 1) * (i + 1))
                call SetUnitX(dat.mis[i], x)
                call SetUnitY(dat.mis[i], y)
                call UnitAddAbility(dat.mis[i], 'Amrf')
                call SetUnitTimeScale(dat.mis[i], 0.00)
                call UnitRemoveAbility(dat.mis[i], 'Amrf')
                set dat.fh[i] = 30.0 * i
                call SetUnitFlyHeight(dat.mis[i], dat.fh[i], 0.00)
                call SetUnitScale(dat.mis[i], CHARGED_BOLTS_SIZE, CHARGED_BOLTS_SIZE, CHARGED_BOLTS_SIZE)
                set dat.fb[i] = true
                set dat.x[i] = Atan2(y - cy, x - cx)
                exitwhen i == CHARGED_BOLTS
                set i = i + 1
            endloop
            if thistype.total == 0 then
                call TimerStart(thistype.tim, 0.047, true, function Charged.fly)
            endif
            set thistype.total = thistype.total + 1
            set thistype.arr[thistype.total - 1] = dat
            return dat
        endmethod
             
    endstruct    
    
    ///////===================\\\\\\\
    
    private function OnCast takes nothing returns boolean
        if (GetSpellAbilityId() == ABILITY_ID) then
            call bolt.create(GetTriggerUnit())
        endif
        return false
    endfunction
    
    private function BoltCharge takes nothing returns boolean 
        if (GetLearnedSkill() == ABILITY_ID and GetLearnedSkillLevel() == 1) then
            call Charged.create(GetTriggerUnit())
        endif
        return false
    endfunction    
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function OnCast))
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_CAST)
        call TriggerAddCondition(t, Condition(function Charged.ready))
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
        call TriggerAddCondition(t, Condition(function BoltCharge))
        set BE = Condition(function spark.Enum)
        set MINX = GetRectMinX(bj_mapInitialPlayableArea) + 50.0
        set MINY = GetRectMinY(bj_mapInitialPlayableArea) + 50.0
        set MAXX = GetRectMaxX(bj_mapInitialPlayableArea) - 50.0
        set MAXY = GetRectMaxY(bj_mapInitialPlayableArea) - 50.0
    endfunction
endlibrary
 
Top