/*
meteorites v1.01 by scorpion182
requires:
timer utils, bound sentinel, xedamage, xefx by Vexorian
grouputils by Rising_Dusk
*/
library Meteorites requires TimerUtils, xedamage, xefx, GroupUtils, BoundSentinel, SimError
private keyword data //don't touch this
//---------------------------------------------------------------------------------------------
//--------------CALIBRATION SECTION------------------------------------------------------------
globals
private constant integer SPELL_ID='A000' //ability rawcode
private constant string ORDER_ID="starfall" //ability order string
private constant integer MAXMISSILE=5 //keep this value higher or equal than GetMissileCount
private constant string PATH="Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl" //missile path
private constant string DAMAGE_FX="Abilities\\Weapons\\DemolisherFireMissile\\DemolisherFireMissile.mdl" //on-damage effect
private constant string DAMAGE_ATTCH="origin" //on-damage attachment point
private constant real SCALE=1.5 //missile scale
private constant real INIT_HEIGHT=1000. //missile initial height
private constant real HEIGHT=100. //missile normal height
private constant real HEIGHT_DEC=50. //missile height decrement per interval
private constant real M_INTERVAL=1. //how fast the missile created
private constant real ANGLE_SPEED=.15 //missile angle speed per interval
private constant string ERROR_MSG="Another instance is running for this caster." //error message
endglobals
private constant function GetMissileCount takes integer lvl returns integer
return 5+lvl*0 //how many missile created
endfunction
private constant function GetDuration takes integer lvl returns real
return 15.+lvl*0 //spell duration
endfunction
private constant function GetAoE takes integer lvl returns real
return 450.+lvl*0 //area of effect
endfunction
private constant function GetCollisionSize takes integer lvl returns real
return 150.+lvl*0 //missile collision size
endfunction
private constant function GetDamage takes integer lvl returns real
return 100.+lvl*50 //do damage each missile
endfunction
//damage filter
private function DamageOptions takes xedamage spellDamage returns nothing
set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
set spellDamage.atype=ATTACK_TYPE_NORMAL
set spellDamage.exception=UNIT_TYPE_STRUCTURE //don't damage building
set spellDamage.visibleOnly=true //only damage visible unit
set spellDamage.damageAllies=false //damage allies if true
endfunction
//filter the targets, should match the value from DamageOptions
private function IsValidTarget takes unit u, data s returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u) != 0 and not IsUnitType(u,UNIT_TYPE_STRUCTURE) and IsUnitEnemy(u,GetOwningPlayer(s.caster)) and IsUnitVisible(u,GetOwningPlayer(s.caster))
endfunction
//------------END OF CALIBRATION----------------------------------------------------------------
globals
private group casters=CreateGroup()
private xedamage xed
private constant real A=2*bj_PI
endglobals
private struct data
unit caster
timer t
integer lvl
xefx array fx[MAXMISSILE]
real array angle[MAXMISSILE]
integer array flag[MAXMISSILE]
private static thistype temp
integer count=0
real interval=0.
real duration=0.
integer total=0
integer n=0
boolean finish=false
static method create takes unit c returns data
local data this=data.allocate()
local integer i=0
set .lvl=GetUnitAbilityLevel(c,SPELL_ID)
set .caster=c
set .t=NewTimer()
set .total=GetMissileCount(.lvl)
//create the missiles
loop
exitwhen i==.total
set .fx[i]=xefx.create(GetUnitX(c),GetUnitY(c),i*A/.total)
set .fx[i].fxpath=""
set .fx[i].scale=SCALE
set .fx[i].z=INIT_HEIGHT
set .angle[i]=i*A/.total
set .flag[i]=0
set i=i+1
endloop
call GroupAddUnit(casters,c)
return this
endmethod
static method move takes nothing returns nothing
local integer i=0
local real a
local unit u
local unit target=null
loop
exitwhen i==temp.count
//configure the height each missile
if (temp.fx[i].z>HEIGHT) then
set temp.fx[i].z=temp.fx[i].z-HEIGHT_DEC
endif
set i=i+1
endloop
set i=0
loop
exitwhen i==temp.total
if temp.fx[i]!=0 then
//move the missiles
set a=temp.angle[i]+ANGLE_SPEED
set temp.fx[i].x=GetUnitX(temp.caster)+GetAoE(temp.lvl)*Cos(a)
set temp.fx[i].y=GetUnitY(temp.caster)+GetAoE(temp.lvl)*Sin(a)
set temp.angle[i]=a
//search a target
call GroupEnumUnitsInArea(ENUM_GROUP,temp.fx[i].x,temp.fx[i].y,GetCollisionSize(temp.lvl),BOOLEXPR_TRUE)
//i don't know how to exit in enumeration function, so i use this firstofgroup loop :D
loop
set u=FirstOfGroup(ENUM_GROUP)
exitwhen u==null or target!=null
call GroupRemoveUnit(ENUM_GROUP,u)
if IsValidTarget(u,temp) and temp.fx[i].z==HEIGHT then
call temp.fx[i].destroy()
set temp.n=temp.n+1
set temp.flag[i]=1
if temp.n==temp.count and GetUnitCurrentOrder(temp.caster)!=OrderId(ORDER_ID) then
set temp.finish=true
set temp.duration=GetDuration(temp.lvl)
endif
//damage
call xed.useSpecialEffect(DAMAGE_FX,DAMAGE_ATTCH)
call xed.damageTarget(temp.caster,u,GetDamage(temp.lvl))
set target=u
endif
endloop
endif
set i=i+1
endloop
set target=null
set u=null
endmethod
static method Loop takes nothing returns nothing
local data this=data(GetTimerData(GetExpiredTimer()))
set temp=this
if (.duration<GetDuration(.lvl) and not IsUnitType(.caster, UNIT_TYPE_DEAD) and not .finish ) then
call data.move()
set .duration=.duration+XE_ANIMATION_PERIOD
else
call .destroy()
return
endif
if .count<GetMissileCount(.lvl) and .interval==M_INTERVAL and GetUnitCurrentOrder(.caster)==OrderId(ORDER_ID) then
set .fx[.count].fxpath=PATH
set .count=.count+1
//order caster to stop when he succes summons all missiles
if (.count==GetMissileCount(.lvl)) then
call IssueImmediateOrder(.caster,"stop")
endif
set .interval=0.
else
//preventing bug when aborting spell before the first missile created
//if GetUnitCurrentOrder(.caster)!=OrderId(ORDER_ID) and .count==0 then
if GetUnitCurrentOrder(.caster)!=OrderId(ORDER_ID) and (.count==0 or .n==.count)then
call .destroy()
endif
set .interval=.interval+XE_ANIMATION_PERIOD
endif
endmethod
static method SpellEffect takes nothing returns boolean
local data this
local unit c=GetSpellAbilityUnit()
if GetSpellAbilityId()==SPELL_ID and not IsUnitInGroup(c,casters) then
set this=create(c)
call SetTimerData(.t,this)
call TimerStart(t,XE_ANIMATION_PERIOD,true,function data.Loop)
endif
set c=null
return false
endmethod
private method onDestroy takes nothing returns nothing
local integer i=0
call ReleaseTimer(.t)
call GroupRemoveUnit(casters,.caster)
loop
exitwhen i==.total
if .fx[i]!=0 and .flag[i]==0 then
call .fx[i].destroy()
endif
set i=i+1
endloop
endmethod
static method Stop takes nothing returns boolean
local unit c=GetSpellAbilityUnit()
if GetSpellAbilityId()==SPELL_ID and IsUnitInGroup(c,casters) then
call SimError(GetOwningPlayer(c),ERROR_MSG)
call PauseUnit(c,true)
call IssueImmediateOrder(c,"stop")
call PauseUnit(c,false)
endif
set c=null
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 data.SpellEffect))
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_CHANNEL)
call TriggerAddCondition(t,Condition(function data.Stop))
//init xedamage
set xed=xedamage.create()
call DamageOptions(xed)
endmethod
endstruct
endlibrary