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