scope SpikeEruption initializer init
// requires:
// - StunSystem
// http://www.hiveworkshop.com/forums/jass-resources-412/system-stun-196749/
// - TimerUtils
// http://www.wc3c.net/showthread.php?t=101322
// or
// http://www.hiveworkshop.com/forums/graveyard-418/system-timerutilsex-204500/
// HOW TO IMPORT :
// 1) Import the recquired libraries.
// 2) Download the test map.
// 3) Copy/paste this scope in your map.
// 4) Copy/paste the spell "Spike Eruption" and the destructable "Spike (Spike Eruption)".
// 5) Look the obtained spell and destructable IDs and configure the scope.
//=====================================//
// //
// CONFIGURATION BLOC //
// //
//=====================================//
globals
private constant integer RAVEN_ID = 'Amrf'
private constant integer SPELL_ID = 'A000'
private constant integer DEST_ID = 'B000'
private constant attacktype ATTACK = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant real MIN_RADIUS = 100
private constant real SPIKE_RADIUS = 64
private constant real MAX_HEIGHT = 500 // The spike's top is at 362 height for scale 1
private constant real TIMEOUT = 0.03
private constant boolean FOLLOW_CASTER = false // true: the center of the eruption area is always updated with the position of the caster
private group TmpGroup = CreateGroup()
private real array SpellRadius // Radius of Eruption
private real array SpikesDelay // Delay between 2 spike waves
private real array StunDuration // Stun duration after reached the ground
private real array SpikeStopDuration // Duration of the spike remaining rised
private integer array SpikesWaveAmount // Amount of spikes per wave
private integer array SpikesTotalAmount // Total amount of spikes in one cast
endglobals
private function GetDamage takes unit caster, integer lvl returns real
return 0.1*GetHeroStr(caster,true)*lvl
endfunction
// Filter for units affected by the spikes
private function SpellFilter takes unit caster, unit target returns boolean
return IsUnitEnemy(target,GetOwningPlayer(caster)) and /*
*/ not IsUnitType(target,UNIT_TYPE_DEAD) and /*
*/ not IsUnitType(target,UNIT_TYPE_STRUCTURE) and /*
*/ not IsUnitType(target,UNIT_TYPE_MECHANICAL) and /*
*/ not IsUnitType(target,UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ GetUnitFlyHeight(target)<MAX_HEIGHT
endfunction
private function SetupDatas takes nothing returns nothing
// Level 1
set SpellRadius[1]=400
set SpikesDelay[1]=0.3
set StunDuration[1]=3
set SpikeStopDuration[1]=2
set SpikesWaveAmount[1]=1
set SpikesTotalAmount[1]=6
// Level 2
set SpellRadius[2]=400
set SpikesDelay[2]=0.2
set StunDuration[2]=3
set SpikeStopDuration[2]=2
set SpikesWaveAmount[2]=2
set SpikesTotalAmount[2]=8
// Level 3
set SpellRadius[3]=400
set SpikesDelay[3]=0.2
set StunDuration[3]=3
set SpikeStopDuration[3]=2
set SpikesWaveAmount[3]=2
set SpikesTotalAmount[3]=10
endfunction
//=====================================//
// //
// END OF CONFIGURATION BLOC //
// //
//=====================================//
private struct SpellStructMain
unit caster
integer lvl
real x
real y
real damage
integer countleft
endstruct
private struct SpellStructSpike
unit caster
integer lvl
destructable spike
real time
unit target
endstruct
public function GetRandomRadius takes real min, real max returns real
return SquareRoot(min*min+GetRandomReal(0,max*max-min*min))
endfunction
private function SquaredDistance takes real x1, real y1, real x2, real y2 returns real
local real dx=x2-x1
local real dy=y2-y1
return dx*dx+dy*dy
endfunction
private function SpikeFallsLoop takes nothing returns nothing
local SpellStructSpike data=GetTimerData(GetExpiredTimer())
set data.time=data.time-TIMEOUT*1.18
if GetUnitTypeId(data.target)!=0 then
call SetUnitFlyHeight(data.target,RMaxBJ(GetUnitDefaultFlyHeight(data.target),MAX_HEIGHT*data.time/0.37),0)
endif
if data.time<=0 then
call RemoveDestructable(data.spike)
call ReleaseTimer(GetExpiredTimer())
call data.destroy()
endif
endfunction
private function SpikeFallsBegin takes nothing returns nothing
call SetDestructableAnimationSpeed(SpellStructSpike(GetTimerData(GetExpiredTimer())).spike,1)
call TimerStart(GetExpiredTimer(),TIMEOUT,true,function SpikeFallsLoop)
endfunction
private function SpikeRisesLoop takes nothing returns nothing
local SpellStructSpike data=GetTimerData(GetExpiredTimer())
set data.time=data.time+TIMEOUT
if GetUnitTypeId(data.target)!=0 then
call SetUnitFlyHeight(data.target,RMaxBJ(GetUnitFlyHeight(data.target),MAX_HEIGHT*data.time/0.37),0)
if data.time<=TIMEOUT then
call SetUnitX(data.target,GetWidgetX(data.spike))
call SetUnitY(data.target,GetWidgetY(data.spike))
endif
endif
if data.time>=0.37 then
call SetDestructableAnimationSpeed(data.spike,0)
call TimerStart(GetExpiredTimer(),SpikeStopDuration[data.lvl],false,function SpikeFallsBegin)
endif
endfunction
private function CreateOneSpike takes unit caster, real damage, real x, real y, integer lvl returns nothing
local SpellStructSpike data=SpellStructSpike.create()
local real range=99999
local real tmprange
local unit tmp
set data.caster=caster
set data.lvl=lvl
set data.spike=CreateDestructable(DEST_ID,x,y,GetRandomReal(0,360),2,0)
set data.time=0
set data.target=null
call GroupEnumUnitsInRange(TmpGroup,x,y,SPIKE_RADIUS+200,null)
loop
set tmp=FirstOfGroup(TmpGroup)
exitwhen tmp==null
call GroupRemoveUnit(TmpGroup,tmp)
if IsUnitInRangeXY(tmp,x,y,SPIKE_RADIUS) and SpellFilter(caster,tmp) then
set tmprange=SquaredDistance(x,y,GetUnitX(tmp),GetUnitY(tmp))
if tmprange<=range then
set data.target=tmp
set range=tmprange
endif
endif
endloop
if GetUnitTypeId(data.target)!=0 then
if UnitAddAbility(data.target,RAVEN_ID) then
call UnitRemoveAbility(data.target,RAVEN_ID)
endif
call UnitDamageTarget(caster,data.target,damage,true,false,ATTACK,DAMAGE,null)
if GetWidgetLife(data.target)>0.405 then
call Stun.apply(data.target,0.7+StunDuration[lvl]+SpikeStopDuration[lvl],false)
endif
endif
call TimerStart(NewTimerEx(data),TIMEOUT,true,function SpikeRisesLoop)
endfunction
private function SpellLoop takes nothing returns nothing
local SpellStructMain data=GetTimerData(GetExpiredTimer())
local integer i=0
local real angle
local real dist
static if FOLLOW_CASTER then
set data.x=GetUnitX(data.caster)
set data.y=GetUnitY(data.caster)
endif
loop
exitwhen i>=SpikesWaveAmount[data.lvl] or data.countleft<=0
set angle=GetRandomReal(-bj_PI,bj_PI)
set dist=GetRandomRadius(MIN_RADIUS,SpellRadius[data.lvl])
call CreateOneSpike(data.caster,data.damage,data.x+dist*Cos(angle),data.y+dist*Sin(angle),data.lvl)
set data.countleft=data.countleft-1
set i=i+1
endloop
if data.countleft<=0 then
call ReleaseTimer(GetExpiredTimer())
call data.destroy()
endif
endfunction
private function SpellCast takes nothing returns nothing
local SpellStructMain data=SpellStructMain.create()
local integer i=0
local real angle
local real dist
set data.caster=GetTriggerUnit()
set data.x=GetUnitX(data.caster)
set data.y=GetUnitY(data.caster)
set data.lvl=GetUnitAbilityLevel(data.caster,SPELL_ID)
set data.damage=GetDamage(data.caster,data.lvl)
set data.countleft=SpikesTotalAmount[data.lvl]
loop
exitwhen i>=SpikesWaveAmount[data.lvl] or data.countleft<=0
set angle=GetRandomReal(-bj_PI,bj_PI)
set dist=GetRandomRadius(MIN_RADIUS,SpellRadius[data.lvl])
call CreateOneSpike(data.caster,data.damage,data.x+dist*Cos(angle),data.y+dist*Sin(angle),data.lvl)
set data.countleft=data.countleft-1
set i=i+1
endloop
if data.countleft>0 then
call TimerStart(NewTimerEx(data),SpikesDelay[data.lvl],true,function SpellLoop)
else
call data.destroy()
endif
endfunction
private function SpellCondition takes nothing returns boolean
if GetSpellAbilityId()==SPELL_ID then
call SpellCast()
endif
return false
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition(function SpellCondition))
call SetupDatas()
set t=null
endfunction
endscope