//==========================================================================================
// Storm v1.04 by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - DestructableLib
// - TimerUtils
// - TimedHandles
// - xe system (xebasic and xefx)
// * GroupUtils
//##########################################################################################
// Importing:
// 1. Copy the ability, Diabolic Countdown.
// 2. Copy this trigger.
// 3. Implement the required libraries.
// 4. Configure the spell.
//==========================================================================================
scope Storm
native UnitAlive takes unit id returns boolean // It's not neccessary, but you can remove this line if it was already declared
// Provided as an easier way to use another method that detects if a unit is alive.
private function IsUnitAlive takes unit u returns boolean
return UnitAlive(u) // GetWidgetLife(u) > 0.405
endfunction
globals
private constant integer SPELL_ID = 'A000' // The raw id of the Storm ability
private constant real STORM_TIME_LOOP = 1 // How often the storm will hit.
private constant real FIRE_TIME_LOOP = 0.25 // How often the fire will damage nearby enemy units. How long the FIRE_DMG_SFX will be shown is also equal to this.
private constant real FIRE_AREA = 100. // The area where the fire will damage units.
// SFX settings
private constant boolean DO_PRELOAD = true // Determines whether or not to preload the special effects.
private constant string BOLT_PATH = "FORK" // This determines what kind of lightning will be created for the bolt
private constant real BOLT_DUR = 0.5 // How long the bolt will be seen
private constant real BOLT_HEIGHT = 2000. // The Height, or z the bolt will be created from at its starting point
private constant real BOLT_DEVIATE = 200. // How much deviation the bolt can have in its x and y from its starting point
private constant string IMPACT_SFX = "Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl" // An extra SFX that plays when the bolt is created
private constant string SHOCK_SFX = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl" // An extra SFX to be played when the bolt is created
private constant string FIRE_SFX = "Doodads\\Cinematic\\TownBurningFireEmitter\\TownBurningFireEmitter.mdl" // Path for the fire
private constant real FIRE_SCALE = .7 // Scaling for the fire sfx
private constant string FIRE_DMG_SFX = "Abilities\\Spells\\Other\\ImmolationRed\\ImmolationRedDamage.mdl" // Path for the sfx that damages units by the fire. Played every FIRE_TIME_LOOP.
private constant string FIRE_DMG_SFX_ATTACH = "origin" // Attachment point for the sfx.
// Damage Settings (Both bolt and fire)
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPN_TYPE = null
endglobals
// Determines which units will be damaged by the bolt. Default targets: alive, enemy units, and non-magic immune units
private function BoltAffectedTargets takes unit targ, player owner returns boolean
return IsUnitAlive(targ) and IsUnitEnemy(targ,owner) and not IsUnitType(targ,UNIT_TYPE_MAGIC_IMMUNE)
endfunction
// Determines which units can be hurt by the fire. Default targets: alive, enemy units, and non-flying units
private function FireAffectedTargets takes unit targ, player owner returns boolean
return IsUnitAlive(targ) and IsUnitEnemy(targ,owner) and not IsUnitType(targ,UNIT_TYPE_FLYING)
endfunction
// The entire area of the spell where all units in this AoE will get hurt.
private function FullArea takes integer lvl returns real
return 250. +0*lvl
endfunction
// The damage all units will receive if they're in the FullArea
private function FullDamage takes integer lvl returns real
return 25.+15*lvl
endfunction
// This determines the center area where targets will get extra damage
private function CenterArea takes integer lvl returns real
return 100. +0*lvl
endfunction
// The extra damage targets will receive if they're in the CenterArea
private function CenterDamage takes integer lvl returns real
return 15.+10*lvl
endfunction
// How much damage the fire deals per second
private constant function FireDamage takes integer lvl returns real
return 2. + 3*lvl
endfunction
// The duration of the spell, how long the storm will last.
private function StormDuration takes integer lvl returns real
return 6.
endfunction
// How long the fire will last.
private function FireDuration takes integer lvl returns real
return 4.+2*lvl
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
globals
private rect TempRect = Rect(0,0,0,0) // Used to destroy trees in.
private location TempLoc = Location(0,0) // Used for the lightning height.
private group g // Used if GroupUtils is not in the map.
endglobals
// Creates a fire whenever a tree is destroyed; damages nearby enemy ground units.
private struct Fire
unit cast
xefx fire
real dmg
real dur
real count = 0
timer tim
static thistype temp
static method groupActions takes nothing returns boolean
local unit u = GetFilterUnit()
if FireAffectedTargets(u,GetOwningPlayer(temp.cast)) then
call DestroyEffectTimed(AddSpecialEffectTarget(FIRE_DMG_SFX,u,FIRE_DMG_SFX_ATTACH),FIRE_TIME_LOOP)
call UnitDamageTarget(temp.cast,u,temp.dmg,false,true,ATK_TYPE,DMG_TYPE,WPN_TYPE)
endif
set u = null
return false
endmethod
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if .count < .dur then
set .count = .count + FIRE_TIME_LOOP
set temp = this
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP,.fire.x,.fire.y,FIRE_AREA,Filter(function thistype.groupActions))
else
call GroupEnumUnitsInRange(g,.fire.x,.fire.y,FIRE_AREA,Filter(function thistype.groupActions))
endif
else
call .fire.destroy()
call ReleaseTimer(.tim)
call .destroy()
endif
endmethod
static method create takes unit c, integer l, real x, real y returns thistype
local thistype this = thistype.allocate()
set .cast = c
set .dmg = FireDamage(l)*FIRE_TIME_LOOP
set .dur = FireDuration(l)
set .fire = xefx.create(x,y,0)
set .fire.fxpath = FIRE_SFX
set .fire.scale = FIRE_SCALE
set .tim = NewTimer()
call SetTimerData(.tim,this)
call TimerStart(.tim,FIRE_TIME_LOOP,true,function thistype.onLoop)
return this
endmethod
endstruct
// The main code for the storm
private struct Data
unit cast
integer lvl
real x
real y
real area
real count = 0 // This will be used to count how many times the spell should run
timer tim
static thistype temp
//Starts the spell
static method create takes unit c, real x, real y returns thistype
local thistype this = thistype.allocate()
set .cast = c
set .x = x
set .y = y
set .lvl = GetUnitAbilityLevel(.cast,SPELL_ID)
set .area = FullArea(.lvl)
set .tim = NewTimer()
call SetTimerData(.tim,this)
call TimerStart(.tim,STORM_TIME_LOOP,true,function thistype.onLoop)
return this
endmethod
// This method deals with the fire created from trees
static method treeActions takes nothing returns nothing
local destructable d = GetEnumDestructable()
if not IsDestructableDead(d) and IsDestructableTree(d) then
call KillDestructable(d)
call Fire.create(temp.cast,temp.lvl,GetDestructableX(d),GetDestructableY(d))
endif
set d = null
endmethod
static method groupActions takes nothing returns boolean
local unit u = GetFilterUnit()
if BoltAffectedTargets(u,GetOwningPlayer(temp.cast)) then
call UnitDamageTarget(temp.cast,u,FullDamage(temp.lvl)*STORM_TIME_LOOP,false,true,ATK_TYPE,DMG_TYPE,WPN_TYPE)
// Deals extra damage if u is still alive and within CenterArea.
if IsUnitAlive(u) then
if IsUnitInRangeXY(u,temp.x,temp.y,CenterArea(temp.lvl)) then
call UnitDamageTarget(temp.cast,u,CenterDamage(temp.lvl)*STORM_TIME_LOOP,false,true,ATK_TYPE,DMG_TYPE,WPN_TYPE)
endif
endif
endif
set u = null
return false
endmethod
// Basically where the storm is done.
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if .count < StormDuration(.lvl) then
set .count = .count + STORM_TIME_LOOP
set temp = this
call SetRect(TempRect,.x-.area,.y-.area,.x+.area,.y+.area)
call EnumDestructablesInRect(TempRect,null,function thistype.treeActions)
call MoveLocation(TempLoc,.x,.y)
call DestroyLightningTimed(AddLightningEx(BOLT_PATH,true,GetRandomReal(-BOLT_DEVIATE,BOLT_DEVIATE)+.x,GetRandomReal(-BOLT_DEVIATE,BOLT_DEVIATE)+.y,BOLT_HEIGHT,.x,.y,GetLocationZ(TempLoc)),BOLT_DUR)
call DestroyEffect(AddSpecialEffect(IMPACT_SFX,.x,.y))
call DestroyEffect(AddSpecialEffect(SHOCK_SFX,.x,.y))
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP,.x,.y,.area,Filter(function thistype.groupActions))
else
call GroupEnumUnitsInRange(g,.x,.y,.area,Filter(function thistype.groupActions))
endif
else
call ReleaseTimer(.tim)
call .destroy()
endif
endmethod
static method spellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.create(GetTriggerUnit(),GetSpellTargetX(),GetSpellTargetY())
endif
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition(function thistype.spellActions))
static if not LIBRARY_GroupUtils then
set g = CreateGroup()
endif
static if DO_PRELOAD then
call Preload(IMPACT_SFX)
call Preload(SHOCK_SFX)
call Preload(FIRE_SFX)
call Preload(FIRE_DMG_SFX)
endif
endmethod
endstruct
endscope