/*
SHADOW EXPLOSION
by Kricz
Version:
1.01
Credits:
Vexorian, cedi, peq, D1000, moyack & SkriK
Requires:
TimerUtils, xebasic, xecast, xedamage, xepreload
Discription:
Creates a circle of shadow missiles arround the target area, jumping
to the target point. When they reach the middle, a huge explosion appears,
damaging enemies in the area arround it over time. After the explosion expires,
shadow splitters spawn out of the explosion, flying to random point arround the
area, dealing AoE Damage in their impact area.
*/
scope ShadowExplosion ////needs TimerUtils, xedamage, xepreload
globals
/*MAIN SETUP*/
//The Spell Id of the Spell
private constant integer SPELL_ID = 'A000'
//The Number of Levels for the Spell
private constant integer LEVELS = 3
//Should all destructibles/trees be destroyed by the explosion and the splitters?
private constant boolean DESTROY_TREES = true
/*END OF MAIN SETUP*/
/*MISSILE SETUP*/
//The model of the missiles
private constant string MISSILE_MODEL = "Abilities\\Spells\\Items\\OrbCorruption\\OrbCorruptionMissile.mdl"
//The size of the missiles
private constant real MISSILE_SIZE = 2.25
//The heigh of the missiles
private constant real MISSILE_HEIGH = 55.00
//The duration, unitl all missiles are created at the start
private constant real CREATE_INTERVAL = 1.25
//The number of missiles at the start
private constant integer MISSILE_COUNT = 25 //15 is also good, but 30 looks better for me^^
//The start-speed of the missiles
private constant real MISSILE_START_SPEED = 200.00
//The end-speed of the missiles
private constant real MISSILE_END_SPEED = 700.00
//The distance between the casting point and the created missiles
private constant real MISSILE_DISTANCE = 500.00
/*ONLY 3D MODE SETUPS*/
//If this is true, the missiles will "jump" to the middle ;)
private constant boolean MISSILE_3D_MODE = true
//The maximal heigh of the missiles
private constant real MISSILE_3D_HEIGH = 325.00
//Should the Z-Angle also be changed while jumping? (Looks cooler)
private constant boolean MISSILE_Z_ANGLE = true
/*END OF 3D MODE SETUPS*/
/*END OF MISSILE SETUP*/
/*SPLITTER SETUP*/
//The model of the splitters
private constant string SPLITTER_MODEL = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
//The size of the splitter
private constant real SPLITTER_SIZE = 0.85
//The start heigh of the splitter
private constant real SPLITTER_START_HEIGH = 10.00
//The end heigh of the splitter
private constant real SPLITTER_END_HEIGH = 15.00
//The start-speed of the splitter
private constant real SPLITTER_START_SPEED = 255.00
//The end-speed of the splitter
private constant real SPLITTER_END_SPEED = 485.00
//The minimum distance between explosion and splitter impact point
private constant real SPLITTER_MIN_DISTANCE = 125.00
//The maximum distance between explosion and splitter impact point
private constant real SPLITTER_MAX_DISTANCE = 325.00
//The minimum heigh of the splitters
private constant real SPLITTER_MIN_HEIGH = 200.00
//The maximum heigh of the splitters
private constant real SPLITTER_MAX_HEIGH = 445.00
//Should the Z-Angle also be changed while flying? (Looks cooler)
private constant boolean SPLITTER_Z_ANGLE = true
//How much splitters can be summoned maximum? (Don't make this value smaller then any value of SPLITTERS[X])
private constant integer MAX_SPLITTERS = 25
/*END OF SPLITTER SETUP*/
/*EXPLOSION SETUP*/
//The model of the explosion
private constant string EXPLOSION_MODEL = "Effects\\ShadowExplosion.mdl"
//The duration of the explosion (after that, no damage will be dealed and the splitters will be summoned)
private constant real EXPLOSION_DURATION = 0.5
/*END OF EXPLOSION SETUP*/
//Don't change anything here!
//They are for private using!
private real array EXPLOSION_DMG[LEVELS]
private real array EXPLOSION_AOE[LEVELS]
private real array EXPLOSION_SIZE[LEVELS]
private integer array SPLITTERS[LEVELS]
private real array SPLITTER_AOE[LEVELS]
private real array SPLITTER_DMG[LEVELS]
endglobals
//Setup for the damage dealt over the duration in the circle
private function SETUP_EXPLOSION_DMG takes nothing returns nothing
set EXPLOSION_DMG[1] = 150.00
set EXPLOSION_DMG[2] = 225.00
set EXPLOSION_DMG[3] = 300.00
endfunction
//Setup for the AoE, where units get damaged on the explosion
private function SETUP_EXPLOSION_AOE takes nothing returns nothing
set EXPLOSION_AOE[1] = 275.00
set EXPLOSION_AOE[2] = 325.00
set EXPLOSION_AOE[3] = 375.00
endfunction
//Setup for the model-scale of the explosion dummy
private function SETUP_EXPLOSION_SIZE takes nothing returns nothing
set EXPLOSION_SIZE[1] = 2.00
set EXPLOSION_SIZE[2] = 2.25
set EXPLOSION_SIZE[3] = 2.50
endfunction
//Setup for the summoned splitter each level
private function SETUP_SPLITTERS takes nothing returns nothing
set SPLITTERS[1] = 15
set SPLITTERS[2] = 20
set SPLITTERS[3] = 25
endfunction
//Setup for the damage AoE of each splitter
private function SETUP_SPLITTER_AOE takes nothing returns nothing
set SPLITTER_AOE[1] = 45.00
set SPLITTER_AOE[2] = 60.00
set SPLITTER_AOE[3] = 75.00
endfunction
//Setup for the dealt damage by each splitter
private function SETUP_SPLITTER_DMG takes nothing returns nothing
set SPLITTER_DMG[1] = 30.00
set SPLITTER_DMG[2] = 45.00
set SPLITTER_DMG[3] = 60.00
endfunction
//Setup for the Damage-Options
//! textmacro SE_DAMAGE_OPTIONS
set .dmg.dtype = DAMAGE_TYPE_FIRE
set .dmg.atype = ATTACK_TYPE_HERO
set .dmg.wtype = WEAPON_TYPE_WHOKNOWS
//! endtextmacro
/**************************/
/*END OF USER CONFIGURATION!*/
/**************************/
//Don't change anything below here!!
private function ParabolaZ takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
local real A = ( 2 * ( ZStart + ZEnd ) - 4 * High ) / ( MaxDist * MaxDist )
local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
return A * Distance * Distance + B * Distance + ZStart
endfunction
private function ParabolaZalpha takes real ZStart, real ZEnd, real High, real MaxDist, real Distance returns real
local real A = ( 2 * ( ZStart + ZEnd )- 4 * High ) / ( MaxDist * MaxDist )
local real B = ( ZEnd - ZStart - A * MaxDist * MaxDist ) / MaxDist
return Atan( 2 * A * Distance + B)
endfunction
private keyword missile
private keyword splitter
private struct spell
unit caster = null //The caster
delegate xefx explosionFX = 0 //The explosion xefx
real px = 0.00 //The X of the cast point
real py = 0.00 //The Y of the cast point
integer lvl = 0 //The level of the spell
integer count = 0 //Counts the summoned missiles
real exp_counter = 0.00 //Counts the seconds of the explosion
timer t = null //The used timer
real counter = 0.00 //This real is used to check the time for creating the missiles
integer mode = 0 //The mode for the periodic method (0 = missiles, 1 = explosion, 2 = splitter)
real middlez = 0.00 //The terrain heigh of the middle point
boolean explosion = false //Used to check if it's time for the explosion
//Used to save the actual explosion animation speed
missile array missiles[MISSILE_COUNT] //The missiles at the start
splitter array splitters[MAX_SPLITTERS]
//The splitters created after the explosion
static real interval = CREATE_INTERVAL / MISSILE_COUNT
static group tempgroup = CreateGroup() //Group to detect units each interval
static xedamage dmg //Used to deal damage
static boolexpr filter //filter for tempgroup
static spell temp //required by the filter
static location loc = Location(0., 0.) //Required location to check Terrain Z-Values
static method create takes unit caster, real x, real y, integer lvl returns spell
local spell this = spell.allocate()
set .caster = caster
set .lvl = lvl
set .px = x
set .py = y
set .t = NewTimer()
call MoveLocation(.loc, .px, .py)
set .middlez = GetLocationZ(.loc)
set .explosionFX = xefx.create(.px, .py, 0.00)
set .scale = EXPLOSION_SIZE[.lvl]
debug call BJDebugMsg(GetUnitName(.caster) + " casted Shadow Explosion, creating datas...")
call SetTimerData(.t, this)
call TimerStart(.t, .interval, true, function spell.createMissiles)
return this
endmethod
static method createMissiles takes nothing returns nothing
local timer t = GetExpiredTimer()
local spell this = GetTimerData(t)
if .count >= MISSILE_COUNT then
call PauseTimer(.t)
debug call BJDebugMsg("All missiles are created now. Starting the running the timer with the method periodic.")
call TimerStart(.t, XE_ANIMATION_PERIOD, true, function spell.periodic)
return
else
set .missiles[.count] = missile.create(this)
set .count = .count + 1
endif
endmethod
static method actions takes nothing returns boolean
local unit caster = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
if GetSpellAbilityId() == SPELL_ID then
call spell.create(caster, x, y, lvl)
endif
set caster = null
return false
endmethod
//This method is called the wohle spell over every interval and moves missiles, creates the explosions and so on
static method periodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local spell this = GetTimerData(t)
local integer i = 0
local unit u = null
if .mode <= 0 then
//move missiles and check for explosion
loop
exitwhen i >= MISSILE_COUNT or .explosion
call .missiles[i].move()
set i = i + 1
endloop
set i = .count - 1
// if explosion is true, destroy missiles and create explosion
if .explosion then
loop
exitwhen i < 0
call .missiles[i].destroy()
set .count = .count - 1
set i = i - 1
endloop
//create the explosion dummy, destroy trees if it wanted and set the mode to 1 (explosion mode)
set .fxpath = EXPLOSION_MODEL
call .explosionFX.destroy()
if DESTROY_TREES then
call .dmg.damageDestructablesAOE(.caster, .px, .py, EXPLOSION_AOE[.lvl], 1000000.00) //Found no better way with xedamage^^
endif
debug call BJDebugMsg("Missiles reached the middle, starting explosion now.")
set .mode = 1
return
endif
elseif .mode == 1 then
//checks the counter for the explosion
if .exp_counter < EXPLOSION_DURATION then
set .exp_counter = .exp_counter + XE_ANIMATION_PERIOD
set .temp = this
call GroupEnumUnitsInRange(.tempgroup, .px, .py, EXPLOSION_AOE[.lvl], .filter)
//damage units near the explosion
loop
set u = FirstOfGroup(.tempgroup)
exitwhen u == null
call .dmg.damageTarget(.caster, u, EXPLOSION_DMG[.lvl] / EXPLOSION_DURATION * XE_ANIMATION_PERIOD)
call GroupRemoveUnit(.tempgroup, u)
set u = null
endloop
call GroupClear(.tempgroup)
//if explosion is done, create the spiltters
return
else
debug call BJDebugMsg("Explosion is over, creating splitters now.")
set .mode = 2
loop
exitwhen .count >= SPLITTERS[.lvl]
set .splitters[.count] = splitter.create(this)
set .count = .count + 1
endloop
return
endif
elseif .mode >= 2 then
if .count <= 0 then
call .destroy()
debug call BJDebugMsg("No splitters left, destroying datas.")
return
else
set i = .count - 1
loop
exitwhen i < 0
if not .splitters[i].Control() then
set .count = .count - 1
if .count < 0 then
set .count = 0
else
set .splitters[i] = .splitters[.count]
endif
endif
set i = i - 1
endloop
endif
endif
endmethod
//Destroys the Mainstruct and the Timer
method onDestroy takes nothing returns nothing
call ReleaseTimer(.t)
endmethod
//This is the filter used to check for enemy units and so on...
private static method FilterFunc takes nothing returns boolean
return GetFilterUnit() != spell.temp.caster and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_RESISTANT) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_MAGIC_IMMUNE) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(spell.temp.caster))
endmethod
//Initialization of the Spell, same like the normal initializers, just in a struct
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
set .dmg = xedamage.create()
set .filter = Condition(function spell.FilterFunc)
//adding Events and Actions
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_CAST, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(t, Condition(function spell.actions)) //cause conditions are faster than actions ;)
call SETUP_EXPLOSION_DMG()
call SETUP_EXPLOSION_AOE()
call SETUP_EXPLOSION_SIZE()
call SETUP_SPLITTERS()
call SETUP_SPLITTER_AOE()
call SETUP_SPLITTER_DMG()
//! runtextmacro SE_DAMAGE_OPTIONS()
call Preload(MISSILE_MODEL)
call Preload(SPLITTER_MODEL)
set t = null
endmethod
endstruct
//This struct is for the missiles, created in the circle at the start
private struct missile
delegate xefx model = 0 //the missile itself
delegate spell root = 0 //the struct the missiles are from
real angle = 0.00 //the angle of the missile
real speed = MISSILE_START_SPEED //The actual speed of the missiles
real dist = 0.00 //The distance the missile has moved(required by the 3D-Mode)
static real ac = (MISSILE_END_SPEED - MISSILE_START_SPEED) / (MISSILE_DISTANCE / ((MISSILE_END_SPEED + MISSILE_START_SPEED) /2)) * XE_ANIMATION_PERIOD
//The acceleration of the missiles
//Creates a new missile and attaches it to the main struct..
static method create takes spell root returns missile
local missile this = missile.allocate()
local real mx = 0.00
local real my = 0.00
local real angle = 0.00
set .root = root
set angle = I2R(.count) / MISSILE_COUNT * 2 * bj_PI
set mx = .px + MISSILE_DISTANCE * Cos(angle)
set my = .py + MISSILE_DISTANCE * Sin(angle)
set .angle = Atan2(.py - my, .px - mx)
set .model = xefx.create(mx, my, .angle)
debug call BJDebugMsg("Creating new missile with index " + I2S(.count) + " and an angle of " + I2S(R2I(angle * bj_RADTODEG + 0.5)) + ".")
set .fxpath = MISSILE_MODEL
set .z = MISSILE_HEIGH
set .scale = MISSILE_SIZE
return this
endmethod
//moves the missiles each interval
method move takes nothing returns nothing
if .speed < MISSILE_END_SPEED and .dist < MISSILE_DISTANCE then
set .speed = .speed + .ac
set .dist = .dist + .speed * XE_ANIMATION_PERIOD
set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
if MISSILE_3D_MODE then
set .z = ParabolaZ(MISSILE_HEIGH, MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
if MISSILE_Z_ANGLE then
set .zangle = ParabolaZalpha(MISSILE_HEIGH, .middlez + MISSILE_HEIGH, MISSILE_3D_HEIGH, MISSILE_DISTANCE, .dist)
endif
endif
return
elseif .speed >= MISSILE_END_SPEED or .dist >= MISSILE_DISTANCE then
set .explosion = true
endif
endmethod
//Who would believe it? This method destroys the missile
method onDestroy takes nothing returns nothing
call .model.destroy()
endmethod
endstruct
//This is the struct fot the splitter created after the explosion
private struct splitter
delegate xefx model = 0 //The xefx model of the splitter
delegate spell root = 0 //The struct, the splitter are from
real dist = 0.00 //The randomed distance of the splitter
real heigh = 0.00 //The randomed heigh of the splitter
real angle = 0.00 //The angle, the splitter moves
real ac = 0.00 //The acceleration of the splitter
real speed = SPLITTER_START_SPEED //The actual speed of the missile
real moved = 0.00 //The moved distance of the missile
//Just the same as the missiles...
static method create takes spell root returns splitter
local splitter this = splitter.allocate()
set .root = root
set .dist = GetRandomReal(SPLITTER_MIN_DISTANCE, SPLITTER_MAX_DISTANCE)
set .heigh = GetRandomReal(SPLITTER_MIN_HEIGH + SPLITTER_START_HEIGH, SPLITTER_MAX_HEIGH)
set .angle = GetRandomReal(0.00, 360.00)
set .ac = (SPLITTER_END_SPEED - SPLITTER_START_SPEED) / (.dist / ((SPLITTER_END_SPEED + SPLITTER_START_SPEED) / 2)) * XE_ANIMATION_PERIOD
debug call BJDebugMsg("Creating new splitter with index " + I2S(.count) + ".")
set .model = xefx.create(.px, .py, .angle)
set .fxpath = SPLITTER_MODEL
set .scale = SPLITTER_SIZE
set .z = SPLITTER_START_HEIGH
return this
endmethod
//This method is called, when a splitter "lands". It damages enemies arround it.
method damage takes nothing returns nothing
local unit u = null
set .temp = .root
call GroupEnumUnitsInRange(.tempgroup, .x, .y, SPLITTER_AOE[.lvl], .filter)
loop
set u = FirstOfGroup(.tempgroup)
exitwhen u == null
call .dmg.damageTarget(.caster, u, SPLITTER_DMG[.lvl])
call GroupRemoveUnit(.tempgroup, u)
set u = null
endloop
if DESTROY_TREES then
call .dmg.damageDestructablesAOE(.caster, .x, .y, SPLITTER_AOE[.lvl], 100000.)
endif
call GroupClear(.tempgroup)
endmethod
//w00t?? You destroy the poor splitter???
method onDestroy takes nothing returns nothing
call .model.destroy()
endmethod
//moves the splitter and refreshes its angle, blablabla...
method Control takes nothing returns boolean
if .moved < .dist then
set .speed = .speed + .ac
set .moved = .moved + .ac
set .x = .x + .speed * XE_ANIMATION_PERIOD * Cos(.angle)
set .y = .y + .speed * XE_ANIMATION_PERIOD * Sin(.angle)
set .z = ParabolaZ(SPLITTER_START_HEIGH, SPLITTER_START_HEIGH, .heigh, .dist, .moved)
if SPLITTER_Z_ANGLE then
set .zangle = ParabolaZalpha(SPLITTER_START_HEIGH, .middlez + SPLITTER_START_HEIGH, .heigh, .dist, .moved)
endif
return true
else
debug call BJDebugMsg("A splitter reached its target point and will deal damage and destroyed.")
call .damage()
call this.destroy()
return false
endif
return false
endmethod
endstruct
endscope