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

Static Bomb v1.3c

Hi, this is the first spell I've uploaded, and I hope you like it!
Feel free to leave your comments and feedbacks :)


Gathers static energy and transforms them into lightning orbs which circles around the epicenter, preventing any unit from passing through the circumference. Each orb are able to attack enemy units which come too close to them, dealing a very small amount of damage each hit. After some time, the orbs stop orbiting and moves in towards the center, pulling along any units caught within the circle and then explode, dealing a large amount of damage to nearby enemy units.

Level 1 - Each orb deals 12 damage and has a cooldown of 2 seconds. Explosion deals 125 damage. Lasts 4 seconds.
Level 2 - Each orb deals 14 damage and has a cooldown of 1.5 seconds. Explosion deals 175 damage. Lasts 5 seconds.
Level 3 - Each orb deals 16 damage and has a cooldown of 1 second. Explosion deals 225 damage. Lasts 6 seconds.


JASS:
library StaticBomb initializer Init requires TimerUtils, optional TerrainPathability

//====================================================================================
/*                       STATIC BOMB   by F1ashB0nd                           */
//
// How to implement:
//
//      Step 1:
//    Create a new trigger and name it whatever you want (preferably "Static Bomb")
//    and then go to Edit->Convert to Custom Text. Replace everything in the newly
//    created trigger with this code.
//
//      Step 2:
//    Now you have to implement TimerUtils into your map. If you already have it, 
//    just skip this step. If you don't have it, either copy and paste TimerUtils from
//    the test map or find it at http://www.wc3c.net/showthread.php?t=101322
//    (Note: TimerUtils is made by Vexorian. Give him credits if you use this system.)
//
//    You can also implement TerrainPathability into your map. TerrainPathability is
//    made by Rising_Dusk so give credits if you implement TerrainPathability.
//
//      Step 3:
//    Import dummy.mdx into your map. You can export the dummy.mdx file and then
//    import it into your map.
//    (Again, dummy.mdx is also by Vexorian, so give credits when used.)
//
//      Step 4:
//    If you don't have any dummy units that uses Vexorian's dummy.mdx, you can copy
//    and paste the dummy from the test map.
//
//      Step 5:
//    Create a new ability and configure the settings below (including the raw code of
//    the dummy unit and the raw code of the ability.) If you are lazy, feel free to 
//    copy and paste the ability in the test map but you still have to configure the
//    dummy raw code and ability raw code in the settings below.

//====================================================================================



//================================================
//     BASIC SETTINGS

    globals
    
        private constant integer  ABILITY_ID                = 'A000'
        //The Raw Code of the ability.
        
        private constant integer  DUMMY_ID                  = 'dumy'
        //The Raw Code of the dummy unit.
        
        private constant boolean  NO_TARGET                 = false
        //Set to true if the spell has no target. It will create the spell centered around the casting unit.
        
        private constant real     DAMAGE_BASE               = 75
        //The base damage of the final explosion.
        
        private constant real     DAMAGE_INCREMENT          = 50
        //The additional damage of the final explosion per level.
        
        private constant boolean  ORB_ATTACK                = true
        //Determines whether if the orbs can have their own individual attacks.
        //   If set to false, ignore ORB_DAMAGE, ORB_COOLDOWN and ORB_RANGE.
        //If you prefer a more lagless way, give the dummy unit a real ranged attack and set this to false, but you can not adjust
        //the damage and cooldown of the dummy unit every level :(
        
        private constant real     ORB_DAMAGE_BASE           = 10
        //The base damage of every orb.
        
        private constant real     ORB_DAMAGE_INCREMENT      = 2
        //The additional damage of every orb per level.
        
        private constant real     ORB_COOLDOWN_BASE         = 2.5
        //The base cooldown every attack by each orb.
        
        private constant real     ORB_COOLDOWN_INCREMENT    = -0.5
        //The additional cooldown per level.
        
        private constant real     ORB_RANGE_BASE            = 250.00
        //The base range of the orbs.
        
        private constant real     ORB_RANGE_INCREMENT       = 0.00
        //The additional range of the orbs every level.
        //The total range of the orb should not be a big value (i.e. > 500.00) because when there is more than 40 units within range, the map
        //will lag ALOT, and multiple instances of the same spell will increase the lag exponentially. This is because individual unit groups
        //for each orbs are created and that means looping through (Number of Units, dead or alive, including orbs) x (Number of Orbs) units
        //every interval (by default it's 1/50 which is 0.02 seconds) when the cooldown is over. THAT is scary.
        
        private constant real     TIME_BASE                 = 3
        //The base duration of the orbs after fading in and before moving inwards.
        
        private constant real     TIME_INCREMENT            = 1
        //The additional time base every level. A negative value means a decrease in time base and vise versa.
        //If Total Time is less than zero, it will be automatically set to 0.
        
        private constant integer  ORB_BASE                  = 24
        //The base number of orbs created.
        
        private constant integer  ORB_INCREMENT             = 0
        //The additional number of orbs created per level.
        
        private constant real     RADIUS_BASE               = 400.00
        //The base distance every orb is away from the center.
        
        private constant real     RADIUS_INCREMENT          = 0.00
        //The additional radius per level.
        //Total radius should not be less than or equal to 0.
        
        private constant real     DAMAGE_AREA_BASE          = 200.00
        //The base area of effect where filtered units are to be damaged.
        
        private constant real     DAMAGE_AREA_INCREMENT     = 0.00
        //The additional area of effect every level.
        
        private constant real     SMOOTHNESS                = 50
        //The smoothness and accuracy of the skill effects. Suggested Value: (Lowest Recommended) 20, 25, 33, 50 or 100 (Highest Recommended)
        //If there are units moving at maximum speed, change this value to 100 for maximum smoothness in exchange for some FPS.
        //Only used for calculating INTERVAL. Leave it if you want to set INTERVAL manually.
        
    endglobals
    
        // Calculation: Total = Base value + (Increment * Ability Level)
        //e.g. DAMAGE_BASE = 200, DAMAGE_INCREMENT = 75. Damage at Ability Level 2 = 200 + (75 * 2) = 350.
        //     TIME_BASE = 8, TIME_INCREMENT = -2. Time before the orbs move in at Ability Level 3 = 8 + (-2 * 3) = 2 seconds.
        
        
//================================================
//     ADVANCED SETTINGS

    globals
    
        private constant real     FADE_TIME_BASE            = 0.6
        //The base time required for the orbs to fully fade in.
        
        private constant real     FADE_TIME_INCREMENT       = 0
        //The additional time every level for the orbs to fully fade in.
        //If the Total Fade Time is less than 0, it will be automatically set to 0.
        
        private constant real     FADE_DISTANCE_BASE        = 50
        //The base distance covered while fading.
        
        private constant real     FADE_DISTANCE_INCREMENT   = 0
        //The additional distance covered every level.
        
        private constant real     SPEED_BASE                = 550
        //The speed at which the orbs move after the specified time in units per second.
        
        private constant real     SPEED_INCREMENT           = 0
        //The additional speed every level in units per second.
        
        private constant real     ORBIT_SPEED_BASE          = 50
        //The base angle change at which the orbs orbit around the center in degrees per second.
        
        private constant real     ORBIT_SPEED_INCREMENT     = 0
        //The additional angle change every level in degrees per second.
        
        private constant boolean  CLOCKWISE                 = false
        //Determines if the orbs orbit with a clockwise movement.
        
        private constant real     ORB_WIDENESS_BASE         = 140
        //The base wideness of the area where the orbs occupy (the area where units cannot pass through if IMPASSABLE = true).
        
        private constant real     ORB_WIDENESS_INCREMENT    = 0
        //The additional wideness of the area per level.
        
        private constant real     ORB_SIZE_BASE             = 150
        //The base size of the orb in percent.
        
        private constant real     ORB_SIZE_INCREMENT        = 0
        //The additional size of the orb every level.
        
        private constant real     MIN_DISTANCE              = 50
        //The minimum distance between the center and the unit before the unit stops being dragged.
        
        private constant boolean  IMPASSABLE                = true
        //Determines whether if units are able to pass through the wall of orbs.
        
        private constant boolean  DRAG                      = true
        //Determines whether if units are dragged into the center when the orbs move in.
        
        private constant boolean USE_IsTerrainPathable      = false
        //Determines whether if IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) is used instead of IsTerrainWalkable.
        // NOTE:            In order to use IsTerrainWalkable, you need to have the TerrainPathability library by Rising_Dusk.
        //If set to false and TerrainPathability is not found, uses IsTerrainPathable instead.
        
        private constant real     INTERVAL                  = 1 / SMOOTHNESS
        //The interval between each timer tick. Set it to something (0.01, 0.02, 0.03, etc.) if you don't like using "SMOOTHNESS" to configure.
        
    endglobals
    
//====================================================
//     DO NOT TOUCH THE CODES WITHIN THE ARROW BELOW!                              !
// ============================================================================== >>>
    globals
    
        private hashtable ht = InitHashtable()
        //Creating a hashtable for storing dummy units, their cooldown and their effects.
        
        private integer dataToBePassed
        //An integer variable for passing struct ids to filters.
        
        private integer sb_instances = 0
        //To keep track of the number of instances.
        
        private group sb_g = CreateGroup()
        //This is the main unit group to be used and recycled in the code.
        
    endglobals
    
    
    //Retrieves the number of instances of the spell.
    public function GetInstances takes nothing returns integer
        return sb_instances
    endfunction
//                                                                                 !
//=============================================================================== <<<
//
/*    -------------------                                                      */
//     M A I N   C O D E
//
//Lightning Effect Setup

    globals
    //Variables for the lightning effect
        private integer array lgIndex
        private integer lgIndex_Size = 0
        private integer lgMax_Index = 0
        private lightning array lgLightning
        private real array lgRemaining
        private unit array lgSource
        private unit array lgTarget
        private timer lgTimer
        private constant real LG_DURR = 0.40 //Duration of lightning
        private constant real LG_INTR = 0.05 //Interval to check lightning duration left
        
    endglobals
    
    //The handling of lightning effects, used by ATTACK_SpecialEffets
        private function Mui_Lightning takes nothing returns nothing
            local integer current = 1
            local integer i
            loop
            exitwhen current > lgIndex_Size
                set i = lgIndex[current]
                call MoveLightningEx(lgLightning[i], true, GetUnitX(lgSource[i]), GetUnitY(lgSource[i]), GetUnitFlyHeight(lgSource[i]), GetUnitX(lgTarget[i]), GetUnitY(lgTarget[i]), GetUnitFlyHeight(lgTarget[i]))
                set lgRemaining[i] = lgRemaining[i] - LG_INTR
                if lgRemaining[i] <= 0 then
                    call DestroyLightning(lgLightning[i])
                    
                    set lgIndex[current] = lgIndex[lgIndex_Size]
                    set lgIndex[lgIndex_Size] = i
                    set lgIndex_Size = lgIndex_Size - 1
                    set current = current - 1
                    if lgIndex_Size == 0 then
                        call PauseTimer(lgTimer)
                    endif
                    
                endif
            set current = current + 1
            endloop
        endfunction
//End of Lightning Effect Setup
   
   
    private struct StaticBomb
        timer t
        unit caster
        player owner
        integer level
        real centreX
        real centreY
        real x
        real y
        real damage
        real damageAOE
        real orbDamage
        real orbCooldown
        real orbRange
        real timeLeft
        real fadeTime
        real fadeSpeed
        real orbitSpeed
        real transparency
        real transparencySpeed
        real radius
        real angleOffset
        real distance
        real orbWidth
        real orbSize
        real orbs
        real speed
        unit unitToBeAttacked
        real rangeOfUnit
        integer dummyIndex
        
        
        //Edit the filter if you want only certain units to be blocked by the orbs if IMPASSABLE = true.
        //Use u as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method impassable_Filter takes unit u returns boolean
            local thistype this = dataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(u) != DUMMY_ID and GetWidgetLife(u) > 0.405 and GetOwningPlayer(u) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and u != .caster
            //The above condition filters any unit who is alive, is not the dummy unit or the caster and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be dragged by the orbs if DRAG = true.
        //Use u as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method drag_Filter takes unit u returns boolean
            local thistype this = dataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(u) != DUMMY_ID and GetWidgetLife(u) > 0.405 and GetOwningPlayer(u) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE)
            //The above condition filters any unit who is alive, is not the dummy unit and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use u as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method damage_Filter takes unit u returns boolean
            local thistype this = dataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(u, .owner) == false and GetWidgetLife(u) > 0.405 and GetUnitTypeId(u) != DUMMY_ID and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic and is not a structure.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use u as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method attack_Filter takes unit u returns boolean
            local thistype this = dataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(u, .owner) == false and GetWidgetLife(u) > 0.405 and GetUnitTypeId(u) != DUMMY_ID and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitInvisible(u, .owner)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic, is not a structure and is not invisible.
        endmethod
        
        
        //Edit this method to change it to your own special effect upon damage if you wish.
        //Use .centreX and .centreY for the X and Y of the target point, .caster as the caster and .owner as the owner of the caster.
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method damage_SpecialEffects takes nothing returns nothing
            call AddCSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", .centreX, .centreY, 100, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl", .centreX, .centreY, 180, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", .centreX, .centreY, 130, 70, 255, 255, 255, 255, 0, 1)
        endmethod
        
        
        //Edit this method if you want special effects to appear on the units attacked by the orbs.
        //Use "target" for the attacked unit, "orb" for the attacking orb, centreX and centreY, .caster for the caster and .owner for the owner of the caster.
        //You can also implement additional effects on the unit (e.g. Transfer X amount of mana from target to caster)
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method attack_SpecialEffects takes unit target, unit orb returns nothing
            //Lightning effect :)
            local integer i
            
            if lgIndex_Size == 0 then
                call TimerStart(lgTimer, LG_INTR, true, function Mui_Lightning)
            endif
            
            set lgIndex_Size = lgIndex_Size + 1
            if lgIndex_Size > lgMax_Index then
                set lgMax_Index = lgIndex_Size
                set lgIndex[lgIndex_Size] = lgIndex_Size
            endif
            set i = lgIndex[lgIndex_Size]
            
            set lgLightning[i] = AddLightningEx("CLPB", true, GetUnitX(orb), GetUnitY(orb), GetUnitFlyHeight(orb), GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target))
            set lgRemaining[i] = LG_DURR
            set lgSource[i] = orb
            set lgTarget[i] = target
            
            //The special effect on the target
            call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", GetUnitX(target), GetUnitY(target)))
        endmethod
        
        //You can edit this method to determine when does a unit stops being moved about (e.g. dragged, prevented from entering, etc) 
        private method isPositionPathable takes real x, real y returns boolean
            static if LIBRARY_TerrainPathability then
                return IsTerrainWalkable(x, y)
            endif
            return not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
        endmethod
        
//      =====================
//        O P T I O N A L
//      =====================

        //Again, .caster for caster, .owner for owner of caster, u for damaged unit and dummy(i) for the damaging orb.
        private method attack_Damage takes unit u, integer i returns nothing
            call attack_SpecialEffects(u, dummy(i))
            //Do not edit the above line!
            
            call UnitDamageTarget(caster, u, orbDamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //Edit the attack and damage type if you want.
        endmethod
        
        //The method for retrieving the movement speed of units.
        private static method getMoveSpeed takes unit u returns real
            return GetUnitMoveSpeed(u)
            //Edit the above line if you have custom movement speed systems that might clash with this spell. 
        endmethod
        

/*===================================================/*====================================================================*/

        //Function for retrieving the dummy unit.
        private method dummy takes integer i returns unit
            return LoadUnitHandle(ht, this, i)
        endmethod
        
        //Function for retrieving the dummy effect.
        private method dummyAttach takes integer i returns effect
            return LoadEffectHandle(ht, this, i + R2I(orbs))
        endmethod
        
        //Function for saving the unit data of the dummy.
        private method setDummy takes unit u, integer i returns nothing
            call SaveUnitHandle(ht, this, i, u)
        endmethod
        
        //Function for saving the effect data of the dummy.
        private method setDummyAttach takes effect e, integer i returns nothing
            call SaveEffectHandle(ht, this, i + R2I(orbs), e)
        endmethod
        
        //Function for saving the lightning cooldown of the orb.
        private method setDummyCooldown takes real r, integer i returns nothing
            call SaveReal(ht, this, i + 2 * R2I(orbs), r)
        endmethod
        
        //Function for retrieving the lightning cooldown of the orb.
        private method dummyCooldown takes integer i returns real
            return LoadReal(ht, this, i + 2 * R2I(orbs))
        endmethod
        
        //Function for flushing the dummy data of the struct.
        private method flushDummy takes nothing returns nothing
            call FlushChildHashtable(ht, this)
        endmethod
        
        //The custom method for creating fully customizable special effects.
        private method AddCSpecialEffect takes string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration returns nothing
            local unit u = CreateUnit(owner, DUMMY_ID, x, y, angle)
            call SetUnitScale(u, scale * 0.01, 0, 0)
            call SetUnitVertexColor(u, red, green, blue, alpha)
            //Adds and removes the Storm Crow Form ability.
            if UnitAddAbility(u, 'Arav') then
                call UnitRemoveAbility(u, 'Arav')
            endif
            
            call SetUnitFlyHeight(u, height, 0)
            call SetUnitX(u, x)
            call SetUnitY(u, y)
            call DestroyEffect(AddSpecialEffectTarget(ModelPath, u, "origin"))
            call UnitApplyTimedLife(u, 'BTLF', duration)
            set u = null
        endmethod
        
        //Pick units which pass the custom filter and damages them.
        private method damage_EnumUnits takes nothing returns nothing
            local unit u
            set dataToBePassed = this
            
            call GroupEnumUnitsInRange(sb_g, centreX, centreY, damageAOE, null)
            
            loop
                set u = FirstOfGroup(sb_g)
                exitwhen u == null
                call GroupRemoveUnit(sb_g, u)
                
                if damage_Filter(u) then
                
                    call UnitDamageTarget(caster, u, damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                    //Edit the attack and damage type if you want.
                    call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", u, "origin"))
                    //Edit the special effect if you want.
                    
                endif
                
            endloop
                
            call damage_SpecialEffects()
        endmethod

        
        //Pick units which pass the custom filter and calls the orbs to "attack" the closest unit.
        private method attack_EnumUnits takes integer i returns nothing
            local unit du = dummy(i)
            local unit u
            local real X
            local real Y
            local real dist
            
            set dataToBePassed = this
            set dummyIndex = i
            set rangeOfUnit = orbRange
            call GroupEnumUnitsInRange(sb_g, GetUnitX(du), GetUnitY(du), orbRange, null)
            
            loop
                set u = FirstOfGroup(sb_g)
                exitwhen u == null
                call GroupRemoveUnit(sb_g, u)
                
                if attack_Filter(u) then
                    
                    set X = GetUnitX(u) - GetUnitX(dummy(dummyIndex))
                    set Y = GetUnitY(u) - GetUnitY(dummy(dummyIndex))
                    set dist = SquareRoot(X * X + Y * Y)
                    
                    if dist < rangeOfUnit then
                        set unitToBeAttacked = u
                        set rangeOfUnit = dist
                    endif
                    
                endif
                
            endloop
            
            if unitToBeAttacked != null then
                call setDummyCooldown(orbCooldown, i)
                call attack_Damage(unitToBeAttacked, i)
            endif
            set du = null
            set unitToBeAttacked = null
        endmethod
        
        
        //The orbs begin moving towards the center after the specified time is up.
        private static method callback_MoveIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            local unit u
            local real X
            local real Y
            local real dist
            local real angle
            
            loop
            exitwhen i > orbs
                set x = centreX + distance * Cos((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                set y = centreY + distance * Sin((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
            set i = i + 1
            endloop
            
            if distance <= 0 then
                call damage_SpecialEffects()
                call damage_EnumUnits()
                call ReleaseTimer(GetExpiredTimer())
                call clearUp()
            endif
            
            set distance = distance - speed * INTERVAL
            if DRAG then
            
                set dataToBePassed = this
                call GroupEnumUnitsInRange(sb_g, centreX, centreY, distance, null)
                loop
                    set u = FirstOfGroup(sb_g)
                    exitwhen u == null
                    call GroupRemoveUnit(sb_g, u)
                    
                    if drag_Filter(u) then
                        
                        set X = GetUnitX(u) - centreX
                        set Y = GetUnitY(u) - centreY
                        set dist = SquareRoot(X * X + Y * Y)
                        set angle = bj_RADTODEG * Atan2(Y, X)
                        
                        //If the unit reaches MIN_DISTANCE, it stops being pulled.
                        if dist > MIN_DISTANCE then
                            if dist <= distance and dist > distance - orbWidth / 2 then
                                set X = centreX + (distance - orbWidth / 2) * Cos(angle * bj_DEGTORAD)
                                set Y = centreY + (distance - orbWidth / 2) * Sin(angle * bj_DEGTORAD)
                    
                                if USE_IsTerrainPathable then
                    
                                    if isPositionPathable(X, Y) then
                                        call SetUnitX(u, X)
                                        call SetUnitY(u, Y)
                                    endif
                        
                                else
                    
                                    if not IsTerrainPathable(X, Y, PATHING_TYPE_WALKABILITY) then
                                        call SetUnitX(u, X)
                                        call SetUnitY(u, Y)
                                    endif
                        
                                endif
                    
                            endif
                
                        endif
                        
                    endif
                    
                endloop
                
            endif
        endmethod
        
        //If IMPASSABLE = true, pick units that pass the custom filter and then call the function to prevent units from passing.
        private method impassable_EnumUnits takes nothing returns nothing
            local unit u
            local real X
            local real Y
            local real dist
            local real angle
            set dataToBePassed = this
            
            call GroupEnumUnitsInRange(sb_g, centreX, centreY, distance + orbWidth / 2, null)
            
            loop
                set u = FirstOfGroup(sb_g)
                
                exitwhen u == null
                call GroupRemoveUnit(sb_g, u)
                
                if impassable_Filter(u) then
                    
                    set X = GetUnitX(u) - centreX
                    set Y = GetUnitY(u) - centreY
                    set dist = SquareRoot(X * X + Y * Y)
                    set angle = bj_RADTODEG * Atan2(Y, X)
                    
                    if dist <= distance and dist > distance - orbWidth / 2 then
                        set X = GetUnitX(u) - (getMoveSpeed(u) * INTERVAL /*distance - orbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                        set Y = GetUnitY(u) - (getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                    
                        if USE_IsTerrainPathable then
                
                            if isPositionPathable(X, Y) then
                                call SetUnitX(u, X)
                                call SetUnitY(u, Y)
                            endif
                    
                        else
                
                            if not IsTerrainPathable(X, Y, PATHING_TYPE_WALKABILITY) then
                                call SetUnitX(u, X)
                                call SetUnitY(u, Y)
                            endif
                    
                        endif
                
                    elseif dist > distance then
                        set X = GetUnitX(u) + (getMoveSpeed(u) * INTERVAL /*distance + orbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                        set Y = GetUnitY(u) + (getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                
                        if USE_IsTerrainPathable then
                
                            if isPositionPathable(X, Y) then
                                call SetUnitX(u, X)
                                call SetUnitY(u, Y)
                            endif
                    
                        else
                
                            if not IsTerrainPathable(X, Y, PATHING_TYPE_WALKABILITY) then
                                call SetUnitX(u, X)
                                call SetUnitY(u, Y)
                            endif
                    
                        endif
                
                    endif
                    
                endif
                
            endloop
        endmethod
        
        //orbit around the center and blocks units if IMPASSABLE = true.
        private static method callback_Orbit takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > orbs
                set x = centreX + distance * Cos((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                set y = centreY + distance * Sin((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                
                if ORB_ATTACK then
                    if dummyCooldown(i) <= 0 then
                        call attack_EnumUnits(i)
                    else
                        call setDummyCooldown(dummyCooldown(i) - INTERVAL, i)
                    endif
                endif
                
            set i = i + 1
            endloop
            
            if timeLeft <= 0 then
                call TimerStart(GetExpiredTimer(), INTERVAL, true, function thistype.callback_MoveIn)
            endif
            
            if CLOCKWISE then
                set angleOffset = angleOffset - orbitSpeed * INTERVAL
            else
                set angleOffset = angleOffset + orbitSpeed * INTERVAL
            endif
            
            if IMPASSABLE then
                call impassable_EnumUnits()
            endif
            
            set timeLeft = timeLeft - INTERVAL
        endmethod
        
        //Fading in while orbiting slowly.
        private static method callback_FadeIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > orbs
                set x = centreX + distance * Cos((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                set y = centreY + distance * Sin((i * (360 / orbs) + angleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                call SetUnitVertexColor(dummy(i), 255, 255, 255, R2I(255 - transparency * 2.55))
            set i = i + 1
            endloop
            
            if fadeTime <= 0 then
                call TimerStart(GetExpiredTimer(), INTERVAL, true, function thistype.callback_Orbit)
            endif
            
            if CLOCKWISE then
                set angleOffset = angleOffset - orbitSpeed * INTERVAL
            else
                set angleOffset = angleOffset + orbitSpeed * INTERVAL
            endif
            
            set fadeTime = fadeTime - INTERVAL
            set distance = distance - fadeSpeed * INTERVAL
            set transparency = transparency - transparencySpeed * INTERVAL
        endmethod
        
        //Activates on Spell Effect
        method startEffect takes nothing returns nothing
            local integer i = 1
            local real fadeDistance
            local real x
            local real y
            
            //Increases the current number of instances of the spell.
            set sb_instances = sb_instances + 1
            
            //Initialize the values of the variables.
            set angleOffset = 0
            set transparency = 100
            set damage = DAMAGE_BASE + (DAMAGE_INCREMENT * level)
            set damageAOE = DAMAGE_AREA_BASE + (DAMAGE_AREA_INCREMENT * level)
            set orbDamage = ORB_DAMAGE_BASE + (ORB_DAMAGE_INCREMENT * level)
            set orbCooldown = ORB_COOLDOWN_BASE + (ORB_COOLDOWN_INCREMENT * level)
            set orbRange = ORB_RANGE_BASE + (ORB_RANGE_INCREMENT * level)
            set orbs = ORB_BASE + (ORB_INCREMENT * level)
            set orbWidth = ORB_WIDENESS_BASE + (ORB_WIDENESS_INCREMENT * level)
            set orbSize = ORB_SIZE_BASE + (ORB_SIZE_INCREMENT * level)
            set speed = SPEED_BASE + (SPEED_INCREMENT * level)
            set fadeDistance = FADE_DISTANCE_BASE + (FADE_DISTANCE_INCREMENT * level)
            set orbitSpeed = ORBIT_SPEED_BASE + (ORBIT_SPEED_INCREMENT * level)
            
            //If TimeLeft is less than or equal to zero, set it to one interval's value so that callback_Orbit only runs once.
            set timeLeft = TIME_BASE + (TIME_INCREMENT * level)
            if timeLeft <= 0 then
                set timeLeft = INTERVAL
            endif
            
            //If fade is less than or equal to zero, set it to one interval's value so that callback_FadeIn only runs once.
            set fadeTime = FADE_TIME_BASE - (FADE_TIME_INCREMENT * level)
            if fadeTime <= 0 then
                set fadeTime = INTERVAL
            endif
            set fadeSpeed = fadeDistance / fadeTime
            set transparencySpeed = fadeSpeed * (100 / fadeDistance)
            
            //If radius is a negative, set it to zero.
            set radius = RADIUS_BASE + (RADIUS_INCREMENT * level)
            debug   if radius < 0 then
            debug       call BJDebugMsg("WARNING: radius is less than or equal to 0.")
            debug       set radius = 0
            debug   endif
            set distance = radius + fadeDistance
            
            //Creates the orbs. (Dummy Unit + Special Effects)
            loop
            exitwhen i > orbs
                set x = centreX + distance * Cos((i * (360 / orbs)) * bj_DEGTORAD)
                set y = centreY + distance * Sin((i * (360 / orbs)) * bj_DEGTORAD)
                call setDummy(CreateUnit(owner, DUMMY_ID, x, y, 0), i)
                call setDummyAttach(AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", dummy(i), "origin"), i)
                call setDummyCooldown(orbCooldown, i)
                call SetUnitScale(dummy(i), orbSize * 0.01, 0, 0)
                
                //Adds and removes the Storm Crow Form ability.
                if UnitAddAbility(dummy(i), 'Arav') then
                    call UnitRemoveAbility(dummy(i), 'Arav')
                endif
                
                call SetUnitFlyHeight(dummy(i), 70, 0)
                call SetUnitVertexColor(dummy(i), 255, 255, 255, R2I(255 - transparency * 2.55))
            set i = i + 1
            endloop
            
            call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.callback_FadeIn)
        endmethod
        
        //Clears up the remaining stuff.
        private method clearUp takes nothing returns nothing
            local integer i = 1
            
            set caster = null
            set owner = null
            
            loop
            exitwhen i > orbs
                call DestroyEffect(dummyAttach(i))
                call RemoveUnit(dummy(i))
            set i = i + 1
            endloop
            
            call flushDummy()
            call destroy()
            
            //Decreases the current number of instances of the spell.
            set sb_instances = sb_instances - 1
            
        endmethod
    endstruct
        
    //The function for the action of the spell
    private function Action takes nothing returns boolean
        local StaticBomb s
        
        if GetSpellAbilityId() == ABILITY_ID then
        
            set s = StaticBomb.create()
            set s.caster = GetTriggerUnit()
        
            if NO_TARGET then
                set s.centreX = GetUnitX(s.caster)
                set s.centreY = GetUnitY(s.caster)
            else
                set s.centreX = GetSpellTargetX()
                set s.centreY = GetSpellTargetY()
            endif
            
            set s.owner = GetOwningPlayer(s.caster)
            set s.level = GetUnitAbilityLevel(s.caster, ABILITY_ID)
            call s.startEffect()
            
        endif
        return true
        
    endfunction
    
    //Create a trigger and register the event and actions. (Initialize)
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function Action)
        
        //Initialize the timer for the lightning effects
        set lgTimer = NewTimer()
    endfunction
endlibrary

This spell requires TimerUtils and the dummy.mdx model by Vexorian, which is provided within the test map. It also requires an optional library called TerrainPathability by Rising_Dusk.

Credits to Vexorian and Rising_Dusk for their extremely useful resources.

Special thanks to Magtheridon96 for his suggestions on optimizing the whole code and also for spotting mistakes in the code :)

v1.3c
- Fixed the isTerrainPathable method. I forgot to add a not to the return function :(

v1.3b
- Implemented the recommended changes.
- Might be the final version. Tell me if there is a mistake in the code and I will try to fix it as soon as I'm free.
- Re-uploaded, to update description.

v1.3
- Fixed the changes as the mod suggested.

v1.2
- Added an option to include the library TerrainPathability and a configuration to either use it or not.
- Changed the indexing style of the lightning effects.
- Minor modifications: the caster himself is not affected by the orbs anymore. He can move freely in and out. Orbs will no longer damage unseen invisible units. Orb default attack range is increased to 250 for better effect.

v1.1b
- Fixed some bugs and changed the code to clear the global group instead of creating and destroying it over and over again.

v1.1
- Added a check for terrain pathability.

v1.0
- First Release.


Keywords:
vJass, Static, Orb, Bomb, Lightning, Shock, Blue, Thunder, Explosion, Orbit, Block, Pull, Effect, Electric, Center, Circle, Blast
Contents

Just another Warcraft III map (Map)

Reviews
19:44, 16th Jul 2012 Magtheridon96: Merge the actions and the conditions and use only conditions. Instead of locations, use coordinates. (GetSpellTargetLoc() -> GetSpellTargetX() and GetSpellTargetY()) In the casting function, you don't...

Moderator

M

Moderator

19:44, 16th Jul 2012
Magtheridon96:


  • Merge the actions and the conditions and use only conditions.
  • Instead of locations, use coordinates. (GetSpellTargetLoc() -> GetSpellTargetX() and GetSpellTargetY())
  • In the casting function, you don't need all that nonsense.
    GetSpellTargetX() and GetSpellTargetY() will return the coordinates correctly for all those cases except for the "Instant (No target)" case.
  • When you set the flying height of a unit, using 0 as a rate will set it instantly.
  • When you scale a unit, the Y and Z parameters are useless.
    call SetUnitScale(unit, value, value, value) does the same
    thing as call SetUnitScale(unit, value, 0, 0)
  • It would be better to trash most of the BJs and just inline them.
    For example, there's SetUnitVertexColorBJ and SetUnitScalePercent.
  • You don't need to pause the timer before you release it.
    ReleaseTimer(t) already does that.
  • You're leaking hashtables. Once you create a hashtable, there's
    no way to destroy it. Flushing it only flushes the data inside the hashtable. Only call InitHashtable() once on map init for your hashtable, and use flush to clear the data so you can reuse it.
  • JASS:
    set t = NewTimerEx(this)
                call TimerStart(t, INTERVAL, true, function thistype.callbackFadeIn)
    ->
    call TimerStart(NewTimerEx(this), INTERVAL, true, function thistype.callbackFadeIn)
  • DEBUG_MODE should only be used in /static/ ifs. Only.
  • Last time I checked, the pathing natives actually return the opposite of what it is true o_O
  • I would totally recommend reading this. It will teach you about how JASS code should be written. (Convention)
  • Rathing than setting a global special effect variable to the special effect value, then destroying that variable, you could simply do this:
    call DestroyEffect(AddSpecialEffect(...))


There are a lot of things that could be improved here and there and I have not noted all
of them, but if you solve the current issues, I will be able to point out the remaining ones.

edit
Excellent, now that those errors are behind us, this spell can be approved, but there are
only a couple more things that I would totally recommend:

  • JASS:
    call ForGroup(sb_g, function thistype.damage_Deal)
                call ForGroup(sb_g, function thistype.damage_TargetSpecialEffects)
    This can probably be merged into one ForGroup call if you merge the functions.
  • GroupEnumUnitsInRange clears the group automatically, so you don't need to do it ;)
  • Instead of a group enumeration and then a ForGroup call, what would be incredibly
    efficient (as a function of the number of units in the group), is a FirstOfGroup loop.
    JASS:
    local unit u
    call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, range, null)
    loop
        set u = FirstOfGroup(bj_lastCreatedGroup)
        exitwhen u == null
        call GroupRemoveUnit(bj_lastCreatedGroup, u)
    
        if filter(u) then
            // do actions here using u as the picked unit.
        endif
    endloop
  • In the above point, I would like to point out that all you have to do is take your current
    filters and make them take a unit parameter and use that instead of GetFilterUnit().

This is a really good spell, and I recommend it to anyone making an AoS.
4.2/5. (Yeah, I put the rating now, because I can trust that you will update it.)


edit
Approved.

Just a tip: WEAPON_TYPE_WHOKNOWS == null ;)
 
Level 5
Joined
Sep 28, 2010
Messages
75
sure.
Code:
library StaticBomb initializer init requires TimerUtils

//====================================================================================
/*                       STATIC BOMB   by F1ashB0nd                           */
//
// How to implement:
//
//      Step 1:
//    Create a new trigger and name it whatever you want (preferably "Static Bomb")
//    and then go to Edit->Convert to Custom Text. Replace everything in the newly
//    created trigger with this code.
//
//      Step 2:
//    Now you have to implement TimerUtils into your map. If you already have it, 
//    just skip this step. If you don't have it, either copy and paste TimerUtils from
//    the test map or find it at http://www.wc3c.net/showthread.php?t=101322
//    (Note: TimerUtils is made by Vexorian. Give him credits if you use this system.)
//
//      Step 3:
//    Import dummy.mdx into your map. You can export the dummy.mdx file and then
//    import it into your map.
//    (Again, dummy.mdx is also by Vexorian, so give credits when used.)
//
//      Step 4:
//    If you don't have any dummy units that uses Vexorian's dummy.mdx, you can copy
//    and paste the dummy from the test map.
//
//      Step 5:
//    Create a new ability and configure the settings below (including the raw code of
//    the dummy unit and the raw code of the ability.) If you are lazy, feel free to 
//    copy and paste the ability in the test map but you still have to configure the
//    dummy raw code and ability raw code in the settings below.

//====================================================================================



//================================================
//     BASIC SETTINGS

    globals
    
        private constant integer  ABILITY_ID                = 'A000'
        //The Raw Code of the ability.
        
        private constant integer  DUMMY_ID                  = 'dumy'
        //The Raw Code of the dummy unit.
        
        private constant integer  TARGET_TYPE               = 3
        //The target type of the ability, which is Instant (No Target), Unit Target, Point Target and Unit or Point Target.
        // 0 is No Target, 1 is Unit Target, 2 is Point Target and any other value is Unit or Point Target.
        
        private constant real     DAMAGE_BASE               = 75
        //The base damage of the final explosion.
        
        private constant real     DAMAGE_INCREMENT          = 50
        //The additional damage of the final explosion per level.
        
        private constant boolean  ORB_ATTACK                = true
        //Determines whether if the orbs can have their own individual attacks.
        //   If set to false, ignore ORB_DAMAGE, ORB_COOLDOWN and ORB_RANGE.
        //If you prefer a more lagless way, give the dummy unit a real ranged attack and set this to false, but you can not adjust
        //the damage and cooldown of the dummy unit every level :(
        
        private constant real     ORB_DAMAGE_BASE           = 10
        //The base damage of every orb.
        
        private constant real     ORB_DAMAGE_INCREMENT      = 2
        //The additional damage of every orb per level.
        
        private constant real     ORB_COOLDOWN_BASE         = 2.5
        //The base cooldown every attack by each orb.
        
        private constant real     ORB_COOLDOWN_INCREMENT    = -0.5
        //The additional cooldown per level.
        
        private constant real     ORB_RANGE_BASE            = 200.00
        //The base range of the orbs.
        
        private constant real     ORB_RANGE_INCREMENT       = 0.00
        //The additional range of the orbs every level.
        //The total range of the orb should not be a big value (i.e. > 500.00) because when there is more than 40 units within range, the map
        //will lag ALOT, and multiple instances of the same spell will increase the lag exponentially. This is because individual unit groups
        //for each orbs are created and that means looping through (Number of Units, dead or alive, including orbs) x (Number of Orbs) units
        //every interval (by default it's 1/50 which is 0.02 seconds) when the cooldown is over. THAT is scary.
        
        private constant real     TIME_BASE                 = 3
        //The base duration of the orbs after fading in and before moving inwards.
        
        private constant real     TIME_INCREMENT            = 1
        //The additional time base every level. A negative value means a decrease in time base and vise versa.
        //If Total Time is less than zero, it will be automatically set to 0.
        
        private constant integer  ORB_BASE                  = 24
        //The base number of orbs created.
        
        private constant integer  ORB_INCREMENT             = 0
        //The additional number of orbs created per level.
        
        private constant real     RADIUS_BASE               = 400.00
        //The base distance every orb is away from the center.
        
        private constant real     RADIUS_INCREMENT          = 0.00
        //The additional radius per level.
        //Total radius should not be less than or equal to 0.
        
        private constant real     DAMAGE_AREA_BASE          = 200.00
        //The base area of effect where filtered units are to be damaged.
        
        private constant real     DAMAGE_AREA_INCREMENT     = 0.00
        //The additional area of effect every level.
        
        private constant real     SMOOTHNESS                = 50
        //The smoothness and accuracy of the skill effects. Suggested Value: (Lowest Recommended) 20, 25, 33, 50 or 100 (Highest Recommended)
        //If there are units moving at maximum speed, change this value to 100 for maximum smoothness in exchange for some FPS.
        //Only used for calculating INTERVAL. Leave it if you want to set INTERVAL manually.
        
    endglobals
    
        // Calculation: Total = Base value + (Increment * Ability Level)
        //e.g. DAMAGE_BASE = 200, DAMAGE_INCREMENT = 75. Damage at Ability Level 2 = 200 + (75 * 2) = 350.
        //     TIME_BASE = 8, TIME_INCREMENT = -2. Time before the orbs move in at Ability Level 3 = 8 + (-2 * 3) = 2 seconds.
        
        
//================================================
//     ADVANCED SETTINGS

    globals
    
        private constant real     FADE_TIME_BASE            = 0.6
        //The base time required for the orbs to fully fade in.
        
        private constant real     FADE_TIME_INCREMENT       = 0
        //The additional time every level for the orbs to fully fade in.
        //If the Total Fade Time is less than 0, it will be automatically set to 0.
        
        private constant real     FADE_DISTANCE_BASE        = 50
        //The base distance covered while fading.
        
        private constant real     FADE_DISTANCE_INCREMENT   = 0
        //The additional distance covered every level.
        
        private constant real     SPEED_BASE                = 550
        //The speed at which the orbs move after the specified time in units per second.
        
        private constant real     SPEED_INCREMENT           = 0
        //The additional speed every level in units per second.
        
        private constant real     ORBIT_SPEED_BASE          = 50
        //The base angle change at which the orbs orbit around the center in degrees per second.
        
        private constant real     ORBIT_SPEED_INCREMENT     = 0
        //The additional angle change every level in degrees per second.
        
        private constant boolean  CLOCKWISE                 = false
        //Determines if the orbs orbit with a clockwise movement.
        
        private constant real     ORB_WIDENESS_BASE         = 140
        //The base wideness of the area where the orbs occupy (the area where units cannot pass through if IMPASSABLE = true).
        
        private constant real     ORB_WIDENESS_INCREMENT    = 0
        //The additional wideness of the area per level.
        
        private constant real     ORB_SIZE_BASE             = 150
        //The base size of the orb in percent.
        
        private constant real     ORB_SIZE_INCREMENT        = 0
        //The additional size of the orb every level.
        
        private constant real     MIN_DISTANCE              = 50
        //The minimum distance between the center and the unit before the unit stops being dragged.
        
        private constant boolean  IMPASSABLE                = true
        //Determines whether if units are able to pass through the wall of orbs.
        
        private constant boolean  DRAG                      = true
        //Determines whether if units are dragged into the center when the orbs move in.
        
        private constant real     INTERVAL                  = 1 / SMOOTHNESS
        //The interval between each timer tick. Set it to something (0.01, 0.02, 0.03, etc.) if you don't like using "SMOOTHNESS" to configure.
        
    endglobals
    
//================================================
//     DO NOT TOUCH THE CODES WITHIN THE ARROW BELOW!                              !
// ============================================================================== >>>
    globals
    
        private hashtable ht = InitHashtable()
        //Creating a hashtable for storing dummy units, their cooldown and their effects.
        
        private integer DataToBePassed
        //An integer variable for passing struct ids to filters.
        
        private integer SB_INSTANCES = 0
        //To keep track of the number of instances.
        
        private group SB_G
        //This is the main unit group to be used and recycled in the code.
        
        private effect SB_E
        //This is the main effect variable to be used and recycled.
        
    endglobals
    
    
    //Retrieves the number of instances of the spell.
    public function GetInstances takes nothing returns integer
        return SB_INSTANCES
    endfunction
//                                                                                 !
//=============================================================================== <<<
//
/*    -------------------                                                      */
//     M A I N   C O D E
//
//Lightning Effect Setup

    globals
    //Variables for the lightning effect
        private lightning array lgRecycle
        private real array lgRemaining
        private unit array lgSource
        private unit array lgTarget
        private timer lgTimer
        private boolean array lgOccupied
        private integer lgCount = 0
        private constant real LG_DURR = 0.40 //Duration of lightning
        private constant real LG_INTR = 0.05 //Interval to check lightning duration left
        
    endglobals
    
    //The handling of lightning effects, used by ATTACK_SpecialEffets
        private function MUI_lightning takes nothing returns nothing
            local integer i = 1
            loop
            exitwhen i > lgCount
                if lgOccupied[i] then
                    call MoveLightningEx(lgRecycle[i], true, GetUnitX(lgSource[i]), GetUnitY(lgSource[i]), GetUnitFlyHeight(lgSource[i]), GetUnitX(lgTarget[i]), GetUnitY(lgTarget[i]), GetUnitFlyHeight(lgTarget[i]))
                    set lgRemaining[i] = lgRemaining[i] - LG_INTR
                    if lgRemaining[i] <= 0 then
                        call DestroyLightning(lgRecycle[i])
                        set lgRemaining[i] = 0
                        set lgRecycle[i] = null
                        set lgSource[i] = null
                        set lgTarget[i] = null
                        set lgOccupied[i] = false
                        if i == lgCount then
                            set lgCount = lgCount - 1
                        endif
                    endif
                endif
            set i = i + 1
            endloop
            if lgCount == 0 then
                call PauseTimer(lgTimer)
            endif
        endfunction
//End of Lightning Effect Setup
   
   
    private struct StaticBomb
        timer t
        unit caster
        player owner
        integer level
        real centreX
        real centreY
        real x
        real y
        real Damage
        real DamageAOE
        real OrbDamage
        real OrbCooldown
        real OrbRange
        real TimeLeft
        real FadeTime
        real FadeSpeed
        real OrbitSpeed
        real Transparency
        real TransparencySpeed
        real Radius
        real AngleOffset
        real Distance
        real OrbWidth
        real OrbSize
        real Orbs
        real Speed
        unit UnitToBeAttacked
        real RangeOfUnit
        integer DummyIndex
        
        
        //Edit the filter if you want only certain units to be blocked by the orbs if IMPASSABLE = true.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method IMPASSABLE_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and GetWidgetLife(GetFilterUnit()) > 0.405 and GetOwningPlayer(GetFilterUnit()) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters any unit who is alive, is not the dummy unit and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be dragged by the orbs if DRAG = true.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method DRAG_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and GetWidgetLife(GetFilterUnit()) > 0.405 and GetOwningPlayer(GetFilterUnit()) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters any unit who is alive, is not the dummy unit and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method DAMAGE_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(GetFilterUnit(), .owner) == false and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic and is not a structure.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method ATTACK_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(GetFilterUnit(), .owner) == false and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic and is not a structure.
        endmethod
        
        
        //Edit this method to change it to your own special effect upon damage if you wish.
        //Use .centreX and .centreY for the X and Y of the target point, .caster as the caster and .owner as the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method DAMAGE_SpecialEffects takes nothing returns nothing
            call AddCSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", .centreX, .centreY, 100, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl", .centreX, .centreY, 180, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", .centreX, .centreY, 130, 70, 255, 255, 255, 255, 0, 1)
        endmethod
        
        
        //Edit this method if you want special effects to appear on the damaged units.
        //Use GetEnumUnit() for every damaged unit, centreX and centreY, .caster for the caster and .owner for the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also implement additional effects on the unit (e.g. Transfer X amount of mana from target to caster)
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private static method DAMAGE_TargetSpecialEffects takes nothing returns nothing
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            set SB_E = AddSpecialEffectTarget("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", GetEnumUnit(), "origin")
            call DestroyEffect(SB_E)
        endmethod
        
        
        //Edit this method if you want special effects to appear on the units attacked by the orbs.
        //Use "target" for the attacked unit, "orb" for the attacking orb, centreX and centreY, .caster for the caster and .owner for the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also implement additional effects on the unit (e.g. Transfer X amount of mana from target to caster)
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method ATTACK_SpecialEffects takes unit target, unit orb returns nothing
            //Lightning effect :)
            local integer i = 1
            set lgCount = lgCount + 1
            
            loop
            exitwhen i > lgCount
                //Finds an empty space for the lightning to store its data.
                if not lgOccupied[i] then
                    set lgRecycle[i] = AddLightningEx("CLPB", true, GetUnitX(orb), GetUnitY(orb), GetUnitFlyHeight(orb), GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target))
                    set lgRemaining[i] = LG_DURR
                    set lgSource[i] = orb
                    set lgTarget[i] = target
                    set lgOccupied[i] = true
                    
                    if lgCount == 1 then
                        call TimerStart(lgTimer, LG_INTR, true, function MUI_lightning)
                    endif
                    
                    set i = lgCount
                endif
                
            set i = i + 1
            endloop
            
            //The special effect on the target
            set SB_E = AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", GetUnitX(target), GetUnitY(target))
            call DestroyEffect(SB_E)
        endmethod
        
//      =====================
//        O P T I O N A L S
//      =====================

        //The method for dealing damage to the picked units.
        private static method DAMAGE_Deal takes nothing returns nothing
            local thistype this = DataToBePassed
            //Do not edit the above line.
            call UnitDamageTarget(caster, GetEnumUnit(), Damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //Edit the attack and damage type if you want.
        endmethod
        
        private method ATTACK_Damage takes unit u, integer i returns nothing
            call ATTACK_SpecialEffects(u, dummy(i))
            //Do not edit the above line.
            call UnitDamageTarget(caster, u, OrbDamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //Edit the attack and damage type if you want.
        endmethod
        
        //The method for retrieving the movement speed of units.
        private static method Impassable_getMoveSpeed takes unit u returns real
            return GetUnitMoveSpeed(u)
            //Edit the above line if you have custom movement speed systems that might clash with this spell. 
        endmethod
        
        
//         DO NOT TOUCH BELOW THIS LINE
/*====================================================================*/

        //Function for retrieving the dummy unit.
        private method dummy takes integer i returns unit
            return LoadUnitHandle(ht, this, i)
        endmethod
        
        //Function for retrieving the dummy effect.
        private method dummyattach takes integer i returns effect
            return LoadEffectHandle(ht, this, i + R2I(Orbs))
        endmethod
        
        //Function for saving the unit data of the dummy.
        private method setdummy takes unit u, integer i returns nothing
            call SaveUnitHandle(ht, this, i, u)
        endmethod
        
        //Function for saving the effect data of the dummy.
        private method setdummyattach takes effect e, integer i returns nothing
            call SaveEffectHandle(ht, this, i + R2I(Orbs), e)
        endmethod
        
        //Function for saving the lightning cooldown of the orb.
        private method setdummycd takes real r, integer i returns nothing
            call SaveReal(ht, this, i + 2 * R2I(Orbs), r)
        endmethod
        
        //Function for retrieving the lightning cooldown of the orb.
        private method dummycd takes integer i returns real
            return LoadReal(ht, this, i + 2 * R2I(Orbs))
        endmethod
        
        //Function for flushing the dummy data of the struct.
        private method flushDummy takes nothing returns nothing
            call FlushChildHashtable(ht, this)
        endmethod
        
        //The custom method for creating fully customizable special effects.
        private method AddCSpecialEffect takes string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration returns nothing
            local unit u = CreateUnit(owner, DUMMY_ID, x, y, angle)
            call SetUnitScalePercent(u, scale, scale, scale)
            call SetUnitVertexColor(u, red, green, blue, alpha)
            //Adds and removes the Storm Crow Form ability.
            if UnitAddAbility(u, 'Arav') then
                call UnitRemoveAbility(u, 'Arav')
            endif
            
            call SetUnitFlyHeight(u, height, 99999)
            call SetUnitX(u, x)
            call SetUnitY(u, y)
            set SB_E = AddSpecialEffectTarget(ModelPath, u, "origin")
            call DestroyEffect(SB_E)
            call UnitApplyTimedLife(u, 'BTLF', duration)
            set u = null
        endmethod
        
        //Pick units which pass the custom filter and damages them.
        private method DAMAGE_EnumUnits takes nothing returns nothing
            local group SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, DamageAOE, function thistype.DAMAGE_filter)
            call ForGroup(SB_G, function thistype.DAMAGE_Deal)
            call ForGroup(SB_G, function thistype.DAMAGE_TargetSpecialEffects)
            call DAMAGE_SpecialEffects()
            call DestroyGroup(SB_G)
        endmethod
        
        //This loops through the picked units and selects the nearest one.
        private static method ATTACK_Check takes nothing returns nothing
            local thistype this = DataToBePassed
            local real X = GetUnitX(GetEnumUnit()) - GetUnitX(dummy(DummyIndex))
            local real Y = GetUnitY(GetEnumUnit()) - GetUnitY(dummy(DummyIndex))
            local real dist = SquareRoot(X * X + Y * Y)
            
            if dist < RangeOfUnit then
                set UnitToBeAttacked = GetEnumUnit()
                set RangeOfUnit = dist
            endif
        endmethod
        
        //Pick units which pass the custom filter and calls the orbs to "attack" the closest unit.
        private method ATTACK_EnumUnits takes integer i returns nothing
            local unit u = dummy(i)
            set SB_G = CreateGroup()
            set DataToBePassed = this
            set DummyIndex = i
            set RangeOfUnit = OrbRange
            call GroupEnumUnitsInRange(SB_G, GetUnitX(u), GetUnitY(u), OrbRange, function thistype.ATTACK_filter)
            call ForGroup(SB_G, function thistype.ATTACK_Check)
            call DestroyGroup(SB_G)
            if UnitToBeAttacked != null then
                call setdummycd(OrbCooldown, i)
                call ATTACK_Damage(UnitToBeAttacked, i)
            endif
            set u = null
            set UnitToBeAttacked = null
        endmethod
        
        //The function for pulling units into the center.
        private static method Drag_Pull takes nothing returns nothing
            local thistype this = DataToBePassed
            local unit u = GetEnumUnit()
            local real X = GetUnitX(u) - centreX
            local real Y = GetUnitY(u) - centreY
            local real dist = SquareRoot(X * X + Y * Y)
            local real angle = bj_RADTODEG * Atan2(GetUnitY(u) - centreY, GetUnitX(u) - centreX)
            
            //If the unit reaches MIN_DISTANCE, it stops being pulled.
            if dist > MIN_DISTANCE then
                if dist <= Distance and dist > Distance - OrbWidth / 2 then
                    set X = centreX + (Distance - OrbWidth / 2) * Cos(angle * bj_DEGTORAD)
                    set Y = centreY + (Distance - OrbWidth / 2) * Sin(angle * bj_DEGTORAD)
                    call SetUnitX(u, X)
                    call SetUnitY(u, Y)
                endif
            endif
            set u = null
        endmethod
        
        //If DRAG = true, pick units that pass the custom filter and then call the function to drag the units.
        private method Drag_EnumUnits takes nothing returns nothing
            set SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, Distance, function thistype.DRAG_filter)
            call ForGroup(SB_G, function thistype.Drag_Pull)
            call DestroyGroup(SB_G)
        endmethod
        
        //The orbs begin moving towards the center after the specified time is up.
        private static method callbackMoveIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
            set i = i + 1
            endloop
            
            if Distance <= 0 then
                call DAMAGE_SpecialEffects()
                call DAMAGE_EnumUnits()
                call clearUp()
            endif
            
            set Distance = Distance - Speed * INTERVAL
            if DRAG then
                call Drag_EnumUnits()
            endif
        endmethod
        
        //The function for preventing units from passing through the orbs.
        private static method Impassable_Block takes nothing returns nothing
            local thistype this = DataToBePassed
            local unit u = GetEnumUnit()
            local real X = GetUnitX(u) - centreX
            local real Y = GetUnitY(u) - centreY
            local real dist = SquareRoot(X * X + Y * Y)
            local real angle = bj_RADTODEG * Atan2(GetUnitY(u) - centreY, GetUnitX(u) - centreX)
            
            if dist <= Distance and dist > Distance - OrbWidth / 2 then
                set X = GetUnitX(u) - (Impassable_getMoveSpeed(u) * INTERVAL /*Distance - OrbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                set Y = GetUnitY(u) - (Impassable_getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                call SetUnitX(u, X)
                call SetUnitY(u, Y)
            elseif dist > Distance then
                set X = GetUnitX(u) + (Impassable_getMoveSpeed(u) * INTERVAL /*Distance + OrbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                set Y = GetUnitY(u) + (Impassable_getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                call SetUnitX(u, X)
                call SetUnitY(u, Y)
            endif
            
            set u = null
        endmethod
        
        //If IMPASSABLE = true, pick units that pass the custom filter and then call the function to prevent units from passing.
        private method Impassable_EnumUnits takes nothing returns nothing
            set SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, Distance + OrbWidth / 2, function thistype.IMPASSABLE_filter)
            call ForGroup(SB_G, function thistype.Impassable_Block)
            call DestroyGroup(SB_G)
        endmethod
        
        //Orbit around the center and blocks units if IMPASSABLE = true.
        private static method callbackOrbit takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                
                if ORB_ATTACK then
                    if dummycd(i) <= 0 then
                        call ATTACK_EnumUnits(i)
                    else
                        call setdummycd(dummycd(i) - INTERVAL, i)
                    endif
                endif
                
            set i = i + 1
            endloop
            
            if TimeLeft <= 0 then
                call PauseTimer(t)
                call TimerStart(t, INTERVAL, true, function thistype.callbackMoveIn)
            endif
            
            if CLOCKWISE then
                set AngleOffset = AngleOffset - OrbitSpeed * INTERVAL
            else
                set AngleOffset = AngleOffset + OrbitSpeed * INTERVAL
            endif
            
            if IMPASSABLE then
                call Impassable_EnumUnits()
            endif
            
            set TimeLeft = TimeLeft - INTERVAL
        endmethod
        
        //Fading in while orbiting slowly.
        private static method callbackFadeIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                call SetUnitVertexColorBJ(dummy(i), 100, 100, 100, Transparency)
            set i = i + 1
            endloop
            
            if FadeTime <= 0 then
                call PauseTimer(t)
                call TimerStart(t, INTERVAL, true, function thistype.callbackOrbit)
            endif
            
            if CLOCKWISE then
                set AngleOffset = AngleOffset - OrbitSpeed * INTERVAL
            else
                set AngleOffset = AngleOffset + OrbitSpeed * INTERVAL
            endif
            
            set FadeTime = FadeTime - INTERVAL
            set Distance = Distance - FadeSpeed * INTERVAL
            set Transparency = Transparency - TransparencySpeed * INTERVAL
        endmethod
        
        //Activates on Spell Effect
        method startEffect takes nothing returns nothing
            local integer i = 1
            local real fadeDistance
            local real x
            local real y
            
            //Increases the current number of instances of the spell.
            set SB_INSTANCES = SB_INSTANCES + 1
            
            //Initialize the values of the variables.
            set AngleOffset = 0
            set Transparency = 100
            set Damage = DAMAGE_BASE + (DAMAGE_INCREMENT * level)
            set DamageAOE = DAMAGE_AREA_BASE + (DAMAGE_AREA_INCREMENT * level)
            set OrbDamage = ORB_DAMAGE_BASE + (ORB_DAMAGE_INCREMENT * level)
            set OrbCooldown = ORB_COOLDOWN_BASE + (ORB_COOLDOWN_INCREMENT * level)
            set OrbRange = ORB_RANGE_BASE + (ORB_RANGE_INCREMENT * level)
            set Orbs = ORB_BASE + (ORB_INCREMENT * level)
            set OrbWidth = ORB_WIDENESS_BASE + (ORB_WIDENESS_INCREMENT * level)
            set OrbSize = ORB_SIZE_BASE + (ORB_SIZE_INCREMENT * level)
            set Speed = SPEED_BASE + (SPEED_INCREMENT * level)
            set fadeDistance = FADE_DISTANCE_BASE + (FADE_DISTANCE_INCREMENT * level)
            set OrbitSpeed = ORBIT_SPEED_BASE + (ORBIT_SPEED_INCREMENT * level)
            
            //If TimeLeft is less than or equal to zero, set it to one interval's value so that callbackOrbit only runs once.
            set TimeLeft = TIME_BASE + (TIME_INCREMENT * level)
            if TimeLeft <= 0 then
                set TimeLeft = INTERVAL
            endif
            
            //If Fade is less than or equal to zero, set it to one interval's value so that callbackFadeIn only runs once.
            set FadeTime = FADE_TIME_BASE - (FADE_TIME_INCREMENT * level)
            if FadeTime <= 0 then
                set FadeTime = INTERVAL
            endif
            set FadeSpeed = fadeDistance / FadeTime
            set TransparencySpeed = FadeSpeed * (100 / fadeDistance)
            
            //If Radius is a negative, set it to zero.
            set Radius = RADIUS_BASE + (RADIUS_INCREMENT * level)
            if Radius < 0 then
                if DEBUG_MODE then
                    call BJDebugMsg("WARNING: Radius is less than or equal to 0.")
                endif
                set Radius = 0
            endif
            set Distance = Radius + fadeDistance
            
            //Creates the orbs. (Dummy Unit + Special Effects)
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs)) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs)) * bj_DEGTORAD)
                call setdummy(CreateUnit(owner, DUMMY_ID, x, y, 0), i)
                call setdummyattach(AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", dummy(i), "origin"), i)
                call setdummycd(OrbCooldown, i)
                call SetUnitScalePercent(dummy(i), OrbSize, OrbSize, OrbSize)
                
                //Adds and removes the Storm Crow Form ability.
                if UnitAddAbility(dummy(i), 'Arav') then
                    call UnitRemoveAbility(dummy(i), 'Arav')
                endif
                
                call SetUnitFlyHeight(dummy(i), 70, 99999)
                call SetUnitVertexColorBJ(dummy(i), 100, 100, 100, Transparency)
            set i = i + 1
            endloop
            
            set t = NewTimerEx(this)
            call TimerStart(t, INTERVAL, true, function thistype.callbackFadeIn)
        endmethod
        
        //Clears up the remaining stuff.
        method clearUp takes nothing returns nothing
            local integer i = 1
            
            call PauseTimer(t)
            call ReleaseTimer(t)
            set t = null
            set caster = null
            set owner = null
            
            loop
            exitwhen i > Orbs
                call DestroyEffect(dummyattach(i))
                call RemoveUnit(dummy(i))
            set i = i + 1
            endloop
            
            call flushDummy()
            call destroy()
            
            //Decreases the current number of instances of the spell.
            set SB_INSTANCES = SB_INSTANCES - 1
            //If there are no more instances, flush the whole hashtable (in case there are still some data inside the hashtable)
            if SB_INSTANCES == 0 then
                call FlushParentHashtable(ht)
                set ht = InitHashtable()
            endif
            
        endmethod
    endstruct
        
    //The function for the action of the spell
    private function action takes nothing returns nothing
        local StaticBomb s = StaticBomb.create()
        local location loc = GetSpellTargetLoc()
        set s.caster = GetTriggerUnit()
        
        if TARGET_TYPE == 0 then
            set s.centreX = GetUnitX(GetTriggerUnit())
            set s.centreY = GetUnitY(GetTriggerUnit())
            
        elseif TARGET_TYPE == 1 then
            set s.centreX = GetUnitX(GetSpellTargetUnit())
            set s.centreY = GetUnitY(GetSpellTargetUnit())
            
        elseif TARGET_TYPE == 2 then
            set s.centreX = GetLocationX(loc)
            set s.centreY = GetLocationY(loc)
            
        else
            if GetSpellTargetUnit() != null then
                set s.centreX = GetUnitX(GetSpellTargetUnit())
                set s.centreY = GetUnitY(GetSpellTargetUnit())
                
            elseif GetSpellTargetLoc() != null then
                set s.centreX = GetLocationX(loc)
                set s.centreY = GetLocationY(loc)
            endif
        endif
        
        set s.owner = GetOwningPlayer(s.caster)
        set s.level = GetUnitAbilityLevel(s.caster, ABILITY_ID)
        call s.startEffect()
        
        call RemoveLocation(loc)
        set loc = null
    endfunction
    
    //The ability check when the spell is casted.
    private function cond takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
        
    endfunction
    
    //Create a trigger and register the event and actions. (Initialize)
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function cond)
        call TriggerAddAction(t, function action)
        
        //Initialize the timer for the lightning effects
        set lgTimer = NewTimer()
    endfunction
endlibrary
:as:
 
Level 9
Joined
Dec 3, 2010
Messages
162
Use JASS tags instead. Here.

JASS:
library StaticBomb initializer init requires TimerUtils

//====================================================================================
/*                       STATIC BOMB   by F1ashB0nd                           */
//
// How to implement:
//
//      Step 1:
//    Create a new trigger and name it whatever you want (preferably "Static Bomb")
//    and then go to Edit->Convert to Custom Text. Replace everything in the newly
//    created trigger with this code.
//
//      Step 2:
//    Now you have to implement TimerUtils into your map. If you already have it, 
//    just skip this step. If you don't have it, either copy and paste TimerUtils from
//    the test map or find it at [url]http://www.wc3c.net/showthread.php?t=101322[/url]
//    (Note: TimerUtils is made by Vexorian. Give him credits if you use this system.)
//
//      Step 3:
//    Import dummy.mdx into your map. You can export the dummy.mdx file and then
//    import it into your map.
//    (Again, dummy.mdx is also by Vexorian, so give credits when used.)
//
//      Step 4:
//    If you don't have any dummy units that uses Vexorian's dummy.mdx, you can copy
//    and paste the dummy from the test map.
//
//      Step 5:
//    Create a new ability and configure the settings below (including the raw code of
//    the dummy unit and the raw code of the ability.) If you are lazy, feel free to 
//    copy and paste the ability in the test map but you still have to configure the
//    dummy raw code and ability raw code in the settings below.

//====================================================================================



//================================================
//     BASIC SETTINGS

    globals
    
        private constant integer  ABILITY_ID                = 'A000'
        //The Raw Code of the ability.
        
        private constant integer  DUMMY_ID                  = 'dumy'
        //The Raw Code of the dummy unit.
        
        private constant integer  TARGET_TYPE               = 3
        //The target type of the ability, which is Instant (No Target), Unit Target, Point Target and Unit or Point Target.
        // 0 is No Target, 1 is Unit Target, 2 is Point Target and any other value is Unit or Point Target.
        
        private constant real     DAMAGE_BASE               = 75
        //The base damage of the final explosion.
        
        private constant real     DAMAGE_INCREMENT          = 50
        //The additional damage of the final explosion per level.
        
        private constant boolean  ORB_ATTACK                = true
        //Determines whether if the orbs can have their own individual attacks.
        //   If set to false, ignore ORB_DAMAGE, ORB_COOLDOWN and ORB_RANGE.
        //If you prefer a more lagless way, give the dummy unit a real ranged attack and set this to false, but you can not adjust
        //the damage and cooldown of the dummy unit every level :(
        
        private constant real     ORB_DAMAGE_BASE           = 10
        //The base damage of every orb.
        
        private constant real     ORB_DAMAGE_INCREMENT      = 2
        //The additional damage of every orb per level.
        
        private constant real     ORB_COOLDOWN_BASE         = 2.5
        //The base cooldown every attack by each orb.
        
        private constant real     ORB_COOLDOWN_INCREMENT    = -0.5
        //The additional cooldown per level.
        
        private constant real     ORB_RANGE_BASE            = 200.00
        //The base range of the orbs.
        
        private constant real     ORB_RANGE_INCREMENT       = 0.00
        //The additional range of the orbs every level.
        //The total range of the orb should not be a big value (i.e. > 500.00) because when there is more than 40 units within range, the map
        //will lag ALOT, and multiple instances of the same spell will increase the lag exponentially. This is because individual unit groups
        //for each orbs are created and that means looping through (Number of Units, dead or alive, including orbs) x (Number of Orbs) units
        //every interval (by default it's 1/50 which is 0.02 seconds) when the cooldown is over. THAT is scary.
        
        private constant real     TIME_BASE                 = 3
        //The base duration of the orbs after fading in and before moving inwards.
        
        private constant real     TIME_INCREMENT            = 1
        //The additional time base every level. A negative value means a decrease in time base and vise versa.
        //If Total Time is less than zero, it will be automatically set to 0.
        
        private constant integer  ORB_BASE                  = 24
        //The base number of orbs created.
        
        private constant integer  ORB_INCREMENT             = 0
        //The additional number of orbs created per level.
        
        private constant real     RADIUS_BASE               = 400.00
        //The base distance every orb is away from the center.
        
        private constant real     RADIUS_INCREMENT          = 0.00
        //The additional radius per level.
        //Total radius should not be less than or equal to 0.
        
        private constant real     DAMAGE_AREA_BASE          = 200.00
        //The base area of effect where filtered units are to be damaged.
        
        private constant real     DAMAGE_AREA_INCREMENT     = 0.00
        //The additional area of effect every level.
        
        private constant real     SMOOTHNESS                = 50
        //The smoothness and accuracy of the skill effects. Suggested Value: (Lowest Recommended) 20, 25, 33, 50 or 100 (Highest Recommended)
        //If there are units moving at maximum speed, change this value to 100 for maximum smoothness in exchange for some FPS.
        //Only used for calculating INTERVAL. Leave it if you want to set INTERVAL manually.
        
    endglobals
    
        // Calculation: Total = Base value + (Increment * Ability Level)
        //e.g. DAMAGE_BASE = 200, DAMAGE_INCREMENT = 75. Damage at Ability Level 2 = 200 + (75 * 2) = 350.
        //     TIME_BASE = 8, TIME_INCREMENT = -2. Time before the orbs move in at Ability Level 3 = 8 + (-2 * 3) = 2 seconds.
        
        
//================================================
//     ADVANCED SETTINGS

    globals
    
        private constant real     FADE_TIME_BASE            = 0.6
        //The base time required for the orbs to fully fade in.
        
        private constant real     FADE_TIME_INCREMENT       = 0
        //The additional time every level for the orbs to fully fade in.
        //If the Total Fade Time is less than 0, it will be automatically set to 0.
        
        private constant real     FADE_DISTANCE_BASE        = 50
        //The base distance covered while fading.
        
        private constant real     FADE_DISTANCE_INCREMENT   = 0
        //The additional distance covered every level.
        
        private constant real     SPEED_BASE                = 550
        //The speed at which the orbs move after the specified time in units per second.
        
        private constant real     SPEED_INCREMENT           = 0
        //The additional speed every level in units per second.
        
        private constant real     ORBIT_SPEED_BASE          = 50
        //The base angle change at which the orbs orbit around the center in degrees per second.
        
        private constant real     ORBIT_SPEED_INCREMENT     = 0
        //The additional angle change every level in degrees per second.
        
        private constant boolean  CLOCKWISE                 = false
        //Determines if the orbs orbit with a clockwise movement.
        
        private constant real     ORB_WIDENESS_BASE         = 140
        //The base wideness of the area where the orbs occupy (the area where units cannot pass through if IMPASSABLE = true).
        
        private constant real     ORB_WIDENESS_INCREMENT    = 0
        //The additional wideness of the area per level.
        
        private constant real     ORB_SIZE_BASE             = 150
        //The base size of the orb in percent.
        
        private constant real     ORB_SIZE_INCREMENT        = 0
        //The additional size of the orb every level.
        
        private constant real     MIN_DISTANCE              = 50
        //The minimum distance between the center and the unit before the unit stops being dragged.
        
        private constant boolean  IMPASSABLE                = true
        //Determines whether if units are able to pass through the wall of orbs.
        
        private constant boolean  DRAG                      = true
        //Determines whether if units are dragged into the center when the orbs move in.
        
        private constant real     INTERVAL                  = 1 / SMOOTHNESS
        //The interval between each timer tick. Set it to something (0.01, 0.02, 0.03, etc.) if you don't like using "SMOOTHNESS" to configure.
        
    endglobals
    
//================================================
//     DO NOT TOUCH THE CODES WITHIN THE ARROW BELOW!                              !
// ============================================================================== >>>
    globals
    
        private hashtable ht = InitHashtable()
        //Creating a hashtable for storing dummy units, their cooldown and their effects.
        
        private integer DataToBePassed
        //An integer variable for passing struct ids to filters.
        
        private integer SB_INSTANCES = 0
        //To keep track of the number of instances.
        
        private group SB_G
        //This is the main unit group to be used and recycled in the code.
        
        private effect SB_E
        //This is the main effect variable to be used and recycled.
        
    endglobals
    
    
    //Retrieves the number of instances of the spell.
    public function GetInstances takes nothing returns integer
        return SB_INSTANCES
    endfunction
//                                                                                 !
//=============================================================================== <<<
//
/*    -------------------                                                      */
//     M A I N   C O D E
//
//Lightning Effect Setup

    globals
    //Variables for the lightning effect
        private lightning array lgRecycle
        private real array lgRemaining
        private unit array lgSource
        private unit array lgTarget
        private timer lgTimer
        private boolean array lgOccupied
        private integer lgCount = 0
        private constant real LG_DURR = 0.40 //Duration of lightning
        private constant real LG_INTR = 0.05 //Interval to check lightning duration left
        
    endglobals
    
    //The handling of lightning effects, used by ATTACK_SpecialEffets
        private function MUI_lightning takes nothing returns nothing
            local integer i = 1
            loop
            exitwhen i > lgCount
                if lgOccupied[i] then
                    call MoveLightningEx(lgRecycle[i], true, GetUnitX(lgSource[i]), GetUnitY(lgSource[i]), GetUnitFlyHeight(lgSource[i]), GetUnitX(lgTarget[i]), GetUnitY(lgTarget[i]), GetUnitFlyHeight(lgTarget[i]))
                    set lgRemaining[i] = lgRemaining[i] - LG_INTR
                    if lgRemaining[i] <= 0 then
                        call DestroyLightning(lgRecycle[i])
                        set lgRemaining[i] = 0
                        set lgRecycle[i] = null
                        set lgSource[i] = null
                        set lgTarget[i] = null
                        set lgOccupied[i] = false
                        if i == lgCount then
                            set lgCount = lgCount - 1
                        endif
                    endif
                endif
            set i = i + 1
            endloop
            if lgCount == 0 then
                call PauseTimer(lgTimer)
            endif
        endfunction
//End of Lightning Effect Setup
   
   
    private struct StaticBomb
        timer t
        unit caster
        player owner
        integer level
        real centreX
        real centreY
        real x
        real y
        real Damage
        real DamageAOE
        real OrbDamage
        real OrbCooldown
        real OrbRange
        real TimeLeft
        real FadeTime
        real FadeSpeed
        real OrbitSpeed
        real Transparency
        real TransparencySpeed
        real Radius
        real AngleOffset
        real Distance
        real OrbWidth
        real OrbSize
        real Orbs
        real Speed
        unit UnitToBeAttacked
        real RangeOfUnit
        integer DummyIndex
        
        
        //Edit the filter if you want only certain units to be blocked by the orbs if IMPASSABLE = true.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method IMPASSABLE_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and GetWidgetLife(GetFilterUnit()) > 0.405 and GetOwningPlayer(GetFilterUnit()) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters any unit who is alive, is not the dummy unit and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be dragged by the orbs if DRAG = true.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method DRAG_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and GetWidgetLife(GetFilterUnit()) > 0.405 and GetOwningPlayer(GetFilterUnit()) != Player(PLAYER_NEUTRAL_PASSIVE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters any unit who is alive, is not the dummy unit and who does not belong to neutral passive.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method DAMAGE_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(GetFilterUnit(), .owner) == false and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic and is not a structure.
        endmethod
        
        
        //Edit the filter if you want only certain units to be damaged in the explosion.
        //Use GetFilterUnit() as the units to be blocked, .caster as the caster and .owner as the owner of the caster.
        private static method ATTACK_filter takes nothing returns boolean
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            return IsUnitAlly(GetFilterUnit(), .owner) == false and GetWidgetLife(GetFilterUnit()) > 0.405 and GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)
            //The above condition filters only enemies who are alive, who is not the dummy unit, is not immune to magic and is not a structure.
        endmethod
        
        
        //Edit this method to change it to your own special effect upon damage if you wish.
        //Use .centreX and .centreY for the X and Y of the target point, .caster as the caster and .owner as the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method DAMAGE_SpecialEffects takes nothing returns nothing
            call AddCSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", .centreX, .centreY, 100, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl", .centreX, .centreY, 180, 0, 255, 255, 255, 255, 0, 1)
            call AddCSpecialEffect("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", .centreX, .centreY, 130, 70, 255, 255, 255, 255, 0, 1)
        endmethod
        
        
        //Edit this method if you want special effects to appear on the damaged units.
        //Use GetEnumUnit() for every damaged unit, centreX and centreY, .caster for the caster and .owner for the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also implement additional effects on the unit (e.g. Transfer X amount of mana from target to caster)
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private static method DAMAGE_TargetSpecialEffects takes nothing returns nothing
            local thistype this = DataToBePassed
            //Do not edit the above line.
            
            set SB_E = AddSpecialEffectTarget("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", GetEnumUnit(), "origin")
            call DestroyEffect(SB_E)
        endmethod
        
        
        //Edit this method if you want special effects to appear on the units attacked by the orbs.
        //Use "target" for the attacked unit, "orb" for the attacking orb, centreX and centreY, .caster for the caster and .owner for the owner of the caster.
        //An effect variable SB_E used for destroying an effect after it is created is provided.
        //You can also implement additional effects on the unit (e.g. Transfer X amount of mana from target to caster)
        //You can also make sound effects with special effects by calling the method:
        //    AddCSpecialEffect(string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration)
        private method ATTACK_SpecialEffects takes unit target, unit orb returns nothing
            //Lightning effect :)
            local integer i = 1
            set lgCount = lgCount + 1
            
            loop
            exitwhen i > lgCount
                //Finds an empty space for the lightning to store its data.
                if not lgOccupied[i] then
                    set lgRecycle[i] = AddLightningEx("CLPB", true, GetUnitX(orb), GetUnitY(orb), GetUnitFlyHeight(orb), GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target))
                    set lgRemaining[i] = LG_DURR
                    set lgSource[i] = orb
                    set lgTarget[i] = target
                    set lgOccupied[i] = true
                    
                    if lgCount == 1 then
                        call TimerStart(lgTimer, LG_INTR, true, function MUI_lightning)
                    endif
                    
                    set i = lgCount
                endif
                
            set i = i + 1
            endloop
            
            //The special effect on the target
            set SB_E = AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", GetUnitX(target), GetUnitY(target))
            call DestroyEffect(SB_E)
        endmethod
        
//      =====================
//        O P T I O N A L S
//      =====================

        //The method for dealing damage to the picked units.
        private static method DAMAGE_Deal takes nothing returns nothing
            local thistype this = DataToBePassed
            //Do not edit the above line.
            call UnitDamageTarget(caster, GetEnumUnit(), Damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //Edit the attack and damage type if you want.
        endmethod
        
        private method ATTACK_Damage takes unit u, integer i returns nothing
            call ATTACK_SpecialEffects(u, dummy(i))
            //Do not edit the above line.
            call UnitDamageTarget(caster, u, OrbDamage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //Edit the attack and damage type if you want.
        endmethod
        
        //The method for retrieving the movement speed of units.
        private static method Impassable_getMoveSpeed takes unit u returns real
            return GetUnitMoveSpeed(u)
            //Edit the above line if you have custom movement speed systems that might clash with this spell. 
        endmethod
        
        
//         DO NOT TOUCH BELOW THIS LINE
/*====================================================================*/

        //Function for retrieving the dummy unit.
        private method dummy takes integer i returns unit
            return LoadUnitHandle(ht, this, i)
        endmethod
        
        //Function for retrieving the dummy effect.
        private method dummyattach takes integer i returns effect
            return LoadEffectHandle(ht, this, i + R2I(Orbs))
        endmethod
        
        //Function for saving the unit data of the dummy.
        private method setdummy takes unit u, integer i returns nothing
            call SaveUnitHandle(ht, this, i, u)
        endmethod
        
        //Function for saving the effect data of the dummy.
        private method setdummyattach takes effect e, integer i returns nothing
            call SaveEffectHandle(ht, this, i + R2I(Orbs), e)
        endmethod
        
        //Function for saving the lightning cooldown of the orb.
        private method setdummycd takes real r, integer i returns nothing
            call SaveReal(ht, this, i + 2 * R2I(Orbs), r)
        endmethod
        
        //Function for retrieving the lightning cooldown of the orb.
        private method dummycd takes integer i returns real
            return LoadReal(ht, this, i + 2 * R2I(Orbs))
        endmethod
        
        //Function for flushing the dummy data of the struct.
        private method flushDummy takes nothing returns nothing
            call FlushChildHashtable(ht, this)
        endmethod
        
        //The custom method for creating fully customizable special effects.
        private method AddCSpecialEffect takes string ModelPath, real x, real y, real scale, real height, integer red, integer green, integer blue, integer alpha, real angle, real duration returns nothing
            local unit u = CreateUnit(owner, DUMMY_ID, x, y, angle)
            call SetUnitScalePercent(u, scale, scale, scale)
            call SetUnitVertexColor(u, red, green, blue, alpha)
            //Adds and removes the Storm Crow Form ability.
            if UnitAddAbility(u, 'Arav') then
                call UnitRemoveAbility(u, 'Arav')
            endif
            
            call SetUnitFlyHeight(u, height, 99999)
            call SetUnitX(u, x)
            call SetUnitY(u, y)
            set SB_E = AddSpecialEffectTarget(ModelPath, u, "origin")
            call DestroyEffect(SB_E)
            call UnitApplyTimedLife(u, 'BTLF', duration)
            set u = null
        endmethod
        
        //Pick units which pass the custom filter and damages them.
        private method DAMAGE_EnumUnits takes nothing returns nothing
            local group SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, DamageAOE, function thistype.DAMAGE_filter)
            call ForGroup(SB_G, function thistype.DAMAGE_Deal)
            call ForGroup(SB_G, function thistype.DAMAGE_TargetSpecialEffects)
            call DAMAGE_SpecialEffects()
            call DestroyGroup(SB_G)
        endmethod
        
        //This loops through the picked units and selects the nearest one.
        private static method ATTACK_Check takes nothing returns nothing
            local thistype this = DataToBePassed
            local real X = GetUnitX(GetEnumUnit()) - GetUnitX(dummy(DummyIndex))
            local real Y = GetUnitY(GetEnumUnit()) - GetUnitY(dummy(DummyIndex))
            local real dist = SquareRoot(X * X + Y * Y)
            
            if dist < RangeOfUnit then
                set UnitToBeAttacked = GetEnumUnit()
                set RangeOfUnit = dist
            endif
        endmethod
        
        //Pick units which pass the custom filter and calls the orbs to "attack" the closest unit.
        private method ATTACK_EnumUnits takes integer i returns nothing
            local unit u = dummy(i)
            set SB_G = CreateGroup()
            set DataToBePassed = this
            set DummyIndex = i
            set RangeOfUnit = OrbRange
            call GroupEnumUnitsInRange(SB_G, GetUnitX(u), GetUnitY(u), OrbRange, function thistype.ATTACK_filter)
            call ForGroup(SB_G, function thistype.ATTACK_Check)
            call DestroyGroup(SB_G)
            if UnitToBeAttacked != null then
                call setdummycd(OrbCooldown, i)
                call ATTACK_Damage(UnitToBeAttacked, i)
            endif
            set u = null
            set UnitToBeAttacked = null
        endmethod
        
        //The function for pulling units into the center.
        private static method Drag_Pull takes nothing returns nothing
            local thistype this = DataToBePassed
            local unit u = GetEnumUnit()
            local real X = GetUnitX(u) - centreX
            local real Y = GetUnitY(u) - centreY
            local real dist = SquareRoot(X * X + Y * Y)
            local real angle = bj_RADTODEG * Atan2(GetUnitY(u) - centreY, GetUnitX(u) - centreX)
            
            //If the unit reaches MIN_DISTANCE, it stops being pulled.
            if dist > MIN_DISTANCE then
                if dist <= Distance and dist > Distance - OrbWidth / 2 then
                    set X = centreX + (Distance - OrbWidth / 2) * Cos(angle * bj_DEGTORAD)
                    set Y = centreY + (Distance - OrbWidth / 2) * Sin(angle * bj_DEGTORAD)
                    call SetUnitX(u, X)
                    call SetUnitY(u, Y)
                endif
            endif
            set u = null
        endmethod
        
        //If DRAG = true, pick units that pass the custom filter and then call the function to drag the units.
        private method Drag_EnumUnits takes nothing returns nothing
            set SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, Distance, function thistype.DRAG_filter)
            call ForGroup(SB_G, function thistype.Drag_Pull)
            call DestroyGroup(SB_G)
        endmethod
        
        //The orbs begin moving towards the center after the specified time is up.
        private static method callbackMoveIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
            set i = i + 1
            endloop
            
            if Distance <= 0 then
                call DAMAGE_SpecialEffects()
                call DAMAGE_EnumUnits()
                call clearUp()
            endif
            
            set Distance = Distance - Speed * INTERVAL
            if DRAG then
                call Drag_EnumUnits()
            endif
        endmethod
        
        //The function for preventing units from passing through the orbs.
        private static method Impassable_Block takes nothing returns nothing
            local thistype this = DataToBePassed
            local unit u = GetEnumUnit()
            local real X = GetUnitX(u) - centreX
            local real Y = GetUnitY(u) - centreY
            local real dist = SquareRoot(X * X + Y * Y)
            local real angle = bj_RADTODEG * Atan2(GetUnitY(u) - centreY, GetUnitX(u) - centreX)
            
            if dist <= Distance and dist > Distance - OrbWidth / 2 then
                set X = GetUnitX(u) - (Impassable_getMoveSpeed(u) * INTERVAL /*Distance - OrbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                set Y = GetUnitY(u) - (Impassable_getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                call SetUnitX(u, X)
                call SetUnitY(u, Y)
            elseif dist > Distance then
                set X = GetUnitX(u) + (Impassable_getMoveSpeed(u) * INTERVAL /*Distance + OrbWidth / 2*/) * Cos(angle * bj_DEGTORAD)
                set Y = GetUnitY(u) + (Impassable_getMoveSpeed(u) * INTERVAL) * Sin(angle * bj_DEGTORAD)
                call SetUnitX(u, X)
                call SetUnitY(u, Y)
            endif
            
            set u = null
        endmethod
        
        //If IMPASSABLE = true, pick units that pass the custom filter and then call the function to prevent units from passing.
        private method Impassable_EnumUnits takes nothing returns nothing
            set SB_G = CreateGroup()
            set DataToBePassed = this
            
            call GroupEnumUnitsInRange(SB_G, centreX, centreY, Distance + OrbWidth / 2, function thistype.IMPASSABLE_filter)
            call ForGroup(SB_G, function thistype.Impassable_Block)
            call DestroyGroup(SB_G)
        endmethod
        
        //Orbit around the center and blocks units if IMPASSABLE = true.
        private static method callbackOrbit takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                
                if ORB_ATTACK then
                    if dummycd(i) <= 0 then
                        call ATTACK_EnumUnits(i)
                    else
                        call setdummycd(dummycd(i) - INTERVAL, i)
                    endif
                endif
                
            set i = i + 1
            endloop
            
            if TimeLeft <= 0 then
                call PauseTimer(t)
                call TimerStart(t, INTERVAL, true, function thistype.callbackMoveIn)
            endif
            
            if CLOCKWISE then
                set AngleOffset = AngleOffset - OrbitSpeed * INTERVAL
            else
                set AngleOffset = AngleOffset + OrbitSpeed * INTERVAL
            endif
            
            if IMPASSABLE then
                call Impassable_EnumUnits()
            endif
            
            set TimeLeft = TimeLeft - INTERVAL
        endmethod
        
        //Fading in while orbiting slowly.
        private static method callbackFadeIn takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs) + AngleOffset) * bj_DEGTORAD)
                call SetUnitX(dummy(i), x)
                call SetUnitY(dummy(i), y)
                call SetUnitVertexColorBJ(dummy(i), 100, 100, 100, Transparency)
            set i = i + 1
            endloop
            
            if FadeTime <= 0 then
                call PauseTimer(t)
                call TimerStart(t, INTERVAL, true, function thistype.callbackOrbit)
            endif
            
            if CLOCKWISE then
                set AngleOffset = AngleOffset - OrbitSpeed * INTERVAL
            else
                set AngleOffset = AngleOffset + OrbitSpeed * INTERVAL
            endif
            
            set FadeTime = FadeTime - INTERVAL
            set Distance = Distance - FadeSpeed * INTERVAL
            set Transparency = Transparency - TransparencySpeed * INTERVAL
        endmethod
        
        //Activates on Spell Effect
        method startEffect takes nothing returns nothing
            local integer i = 1
            local real fadeDistance
            local real x
            local real y
            
            //Increases the current number of instances of the spell.
            set SB_INSTANCES = SB_INSTANCES + 1
            
            //Initialize the values of the variables.
            set AngleOffset = 0
            set Transparency = 100
            set Damage = DAMAGE_BASE + (DAMAGE_INCREMENT * level)
            set DamageAOE = DAMAGE_AREA_BASE + (DAMAGE_AREA_INCREMENT * level)
            set OrbDamage = ORB_DAMAGE_BASE + (ORB_DAMAGE_INCREMENT * level)
            set OrbCooldown = ORB_COOLDOWN_BASE + (ORB_COOLDOWN_INCREMENT * level)
            set OrbRange = ORB_RANGE_BASE + (ORB_RANGE_INCREMENT * level)
            set Orbs = ORB_BASE + (ORB_INCREMENT * level)
            set OrbWidth = ORB_WIDENESS_BASE + (ORB_WIDENESS_INCREMENT * level)
            set OrbSize = ORB_SIZE_BASE + (ORB_SIZE_INCREMENT * level)
            set Speed = SPEED_BASE + (SPEED_INCREMENT * level)
            set fadeDistance = FADE_DISTANCE_BASE + (FADE_DISTANCE_INCREMENT * level)
            set OrbitSpeed = ORBIT_SPEED_BASE + (ORBIT_SPEED_INCREMENT * level)
            
            //If TimeLeft is less than or equal to zero, set it to one interval's value so that callbackOrbit only runs once.
            set TimeLeft = TIME_BASE + (TIME_INCREMENT * level)
            if TimeLeft <= 0 then
                set TimeLeft = INTERVAL
            endif
            
            //If Fade is less than or equal to zero, set it to one interval's value so that callbackFadeIn only runs once.
            set FadeTime = FADE_TIME_BASE - (FADE_TIME_INCREMENT * level)
            if FadeTime <= 0 then
                set FadeTime = INTERVAL
            endif
            set FadeSpeed = fadeDistance / FadeTime
            set TransparencySpeed = FadeSpeed * (100 / fadeDistance)
            
            //If Radius is a negative, set it to zero.
            set Radius = RADIUS_BASE + (RADIUS_INCREMENT * level)
            if Radius < 0 then
                if DEBUG_MODE then
                    call BJDebugMsg("WARNING: Radius is less than or equal to 0.")
                endif
                set Radius = 0
            endif
            set Distance = Radius + fadeDistance
            
            //Creates the orbs. (Dummy Unit + Special Effects)
            loop
            exitwhen i > Orbs
                set x = centreX + Distance * Cos((i * (360 / Orbs)) * bj_DEGTORAD)
                set y = centreY + Distance * Sin((i * (360 / Orbs)) * bj_DEGTORAD)
                call setdummy(CreateUnit(owner, DUMMY_ID, x, y, 0), i)
                call setdummyattach(AddSpecialEffectTarget("Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl", dummy(i), "origin"), i)
                call setdummycd(OrbCooldown, i)
                call SetUnitScalePercent(dummy(i), OrbSize, OrbSize, OrbSize)
                
                //Adds and removes the Storm Crow Form ability.
                if UnitAddAbility(dummy(i), 'Arav') then
                    call UnitRemoveAbility(dummy(i), 'Arav')
                endif
                
                call SetUnitFlyHeight(dummy(i), 70, 99999)
                call SetUnitVertexColorBJ(dummy(i), 100, 100, 100, Transparency)
            set i = i + 1
            endloop
            
            set t = NewTimerEx(this)
            call TimerStart(t, INTERVAL, true, function thistype.callbackFadeIn)
        endmethod
        
        //Clears up the remaining stuff.
        method clearUp takes nothing returns nothing
            local integer i = 1
            
            call PauseTimer(t)
            call ReleaseTimer(t)
            set t = null
            set caster = null
            set owner = null
            
            loop
            exitwhen i > Orbs
                call DestroyEffect(dummyattach(i))
                call RemoveUnit(dummy(i))
            set i = i + 1
            endloop
            
            call flushDummy()
            call destroy()
            
            //Decreases the current number of instances of the spell.
            set SB_INSTANCES = SB_INSTANCES - 1
            //If there are no more instances, flush the whole hashtable (in case there are still some data inside the hashtable)
            if SB_INSTANCES == 0 then
                call FlushParentHashtable(ht)
                set ht = InitHashtable()
            endif
            
        endmethod
    endstruct
        
    //The function for the action of the spell
    private function action takes nothing returns nothing
        local StaticBomb s = StaticBomb.create()
        local location loc = GetSpellTargetLoc()
        set s.caster = GetTriggerUnit()
        
        if TARGET_TYPE == 0 then
            set s.centreX = GetUnitX(GetTriggerUnit())
            set s.centreY = GetUnitY(GetTriggerUnit())
            
        elseif TARGET_TYPE == 1 then
            set s.centreX = GetUnitX(GetSpellTargetUnit())
            set s.centreY = GetUnitY(GetSpellTargetUnit())
            
        elseif TARGET_TYPE == 2 then
            set s.centreX = GetLocationX(loc)
            set s.centreY = GetLocationY(loc)
            
        else
            if GetSpellTargetUnit() != null then
                set s.centreX = GetUnitX(GetSpellTargetUnit())
                set s.centreY = GetUnitY(GetSpellTargetUnit())
                
            elseif GetSpellTargetLoc() != null then
                set s.centreX = GetLocationX(loc)
                set s.centreY = GetLocationY(loc)
            endif
        endif
        
        set s.owner = GetOwningPlayer(s.caster)
        set s.level = GetUnitAbilityLevel(s.caster, ABILITY_ID)
        call s.startEffect()
        
        call RemoveLocation(loc)
        set loc = null
    endfunction
    
    //The ability check when the spell is casted.
    private function cond takes nothing returns boolean
        return GetSpellAbilityId() == ABILITY_ID
        
    endfunction
    
    //Create a trigger and register the event and actions. (Initialize)
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, function cond)
        call TriggerAddAction(t, function action)
        
        //Initialize the timer for the lightning effects
        set lgTimer = NewTimer()
    endfunction
endlibrary
 
Level 5
Joined
Sep 28, 2010
Messages
75
Use JASS tags instead. Here.
thanks.

it would be nice if the hero could leave the region.
There is a custom filter for that, if i understand what you meant.

you can send enemies abroad cards if I'm armed with this ability
I don't actually get what you mean. Do you mean moving over boundaries? If so, i am working on updating it.

EDIT: Updated the spell. Units being pulled will now be unable to pass through the map borders with IsTerrainPathable. In addition, the test map is now easier to navigate through with an extra item from the nearest barrel from the starting point.

EDIT 2: I accidentally wrote " x, y" in the IsTerrainPathable check instead of "X, Y" (capital letter). Also, I remember coming across a post that said something about using a global group and clearing it after use is better than creating and destroying local groups. I have updated the spell once again to implement these changes.
 
Last edited:
Top