Name | Type | is_array | initial_value |
YourPoint | location | Yes | |
YourUnit | unit | Yes |
//TESH.scrollpos=27
//TESH.alwaysfold=0
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//* Variables that can be used from the library:
//* [real] TerrainPathability_X
//* [real] TerrainPathability_Y
//*
globals
private constant real MAX_RANGE = 10.
private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals
globals
private item Item = null
private rect Find = null
private item array Hid
private integer HidMax = 0
public real X = 0.
public real Y = 0.
endglobals
function IsTerrainDeepWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
private function HideItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set Hid[HidMax] = GetEnumItem()
call SetItemVisible(Hid[HidMax], false)
set HidMax = HidMax + 1
endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
//Hide any items in the area to avoid conflicts with our item
call MoveRectTo(Find, x, y)
call EnumItemsInRect(Find ,null, function HideItem)
//Try to move the test item and get its coords
call SetItemPosition(Item, x, y) //Unhides the item
set X = GetItemX(Item)
set Y = GetItemY(Item)
static if LIBRARY_IsTerrainWalkable then
//This is for compatibility with the IsTerrainWalkable library
set IsTerrainWalkable_X = X
set IsTerrainWalkable_Y = Y
endif
call SetItemVisible(Item, false)//Hide it again
//Unhide any items hidden at the start
loop
exitwhen HidMax <= 0
set HidMax = HidMax - 1
call SetItemVisible(Hid[HidMax], true)
set Hid[HidMax] = null
endloop
//Return walkability
return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
private function Init takes nothing returns nothing
set Find = Rect(0., 0., 128., 128.)
set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
call SetItemVisible(Item, false)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: http://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Release a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=369
//TESH.alwaysfold=0
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