//==========================================================================================
// Firewave v1.00 by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - ParabolicMovement
// - TimerUtils
// - xe system (xebasic, xefx, and *xepreload)
//##########################################################################################
// Importing:
// 1. Copy the abilities, Firewave and Firewave (Burn). Copy the buff Firewave Burn.
// 2. Copy this trigger.
// 3. Implement the required libraries.
// 4. Configure the spell.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
// - You can modify the sfx for the fire in the Firewave Burn buff.
// - z arc for wave missile isn't supported yet.
// - Homing movement can be funky looking. It's probably because of the way I'm checking to
// see if the wave missile reached the target
//==========================================================================================
scope Firewave initializer Init
native UnitAlive takes unit id returns boolean // You can remove this line if you already have UnitAlive implemented or don't want to use it.
// Provided in case you don't want to use UnitAlive
private function IsUnitAlive takes unit u returns boolean
return UnitAlive(u)
endfunction
globals
private constant integer SPELL_ID = 'A000' // The raw id of the Firewave ability
private constant integer BURN_ID = 'A001' // The raw id of the Firewave (Burn) ability
private constant real TIMER_LOOP = 0.03 // How often the timer will loop for the spell's actions
// Wave settings
private constant boolean DO_PRELOAD = true // Determines whether or not to preload the ability and the sfx. Needs xepreload if set to true.
private constant string WAVE_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl" // The sfx for the wave missile.
private constant real WAVE_SCALE = 1.5 // Scaling for wave missile
private constant real WAVE_HEIGHT = 10 // The height for the wave missile
private constant real WAVE_FIRE_SPACE = 30 // The spacing there should be between each fire. How accurate the spacing is determined by TIMER_LOOP and the speed of the wave missiles.
private constant integer MAX_WAVE_NUMBER = 2 // The max number of wave missiles that can be created.
// Damage settings:
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPN_TYPE = null
endglobals
// The damage dealt to the target upon impact.
private constant function Damage takes integer lvl returns real
return 15. + 85*lvl
endfunction
// The number of wave missiles that would be created.
private constant function WaveNumber takes integer lvl returns integer
return 2 + 0*lvl
endfunction
// The speed for the wave missile. In standard WC3 movement speed.
private constant function WaveSpeed takes integer lvl returns real
return 800. + 0*lvl
endfunction
// The xy arc for the wave. Bigger values make the waves wider.
private constant function WaveXYArc takes integer lvl returns real
return 200. + 0*lvl
endfunction
// The duration for the fire that's created from the wave.
private constant function WaveDur takes integer lvl returns real
return 3. + 0*lvl
endfunction
// The area of the ring of fire.
private constant function RingArea takes integer lvl returns real
return 200. + 0*lvl
endfunction
// The number of fires would be created in the ring.
private constant function RingNumber takes integer lvl returns integer
return 14 + 0*lvl
endfunction
// The duration for the fire created in a ring.
private constant function RingDur takes integer lvl returns real
return 5. + 0*lvl
endfunction
// Scaling for the fire sfx
private constant function FireScale takes integer lvl returns real
return 0.6 + 0*lvl
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
globals
private constant real TWO_PI = 6.28319
endglobals
private constant function MissileDeviate takes integer n, integer tot returns real
if tot == 1 then
return 0.
else
return -1. + (n*2.)/(tot-1.)
endif
endfunction
// Struct for every fire instance that gets created.
private struct Fire
xefx fire
timer tim
static method create takes real x, real y, integer lvl, player p, real dur returns thistype
local thistype this = thistype.allocate()
set .fire = xefx.create(x,y,0)
set .fire.abilityid = BURN_ID
set .fire.abilityLevel = lvl
set .fire.owner = p
set .fire.scale = FireScale(lvl)
set .tim = NewTimer()
call SetTimerData(.tim,this)
call TimerStart(.tim,dur,false,function thistype.onExpire)
return this
endmethod
static method onExpire takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call ReleaseTimer(.tim)
call .fire.destroy()
call .destroy()
endmethod
endstruct
// Main struct with spell actions.
private struct Data
unit cast
integer lvl
unit targ
real targX
real targY
real targDist
xefx array wave[MAX_WAVE_NUMBER]
real array xyArc[MAX_WAVE_NUMBER]
real x
real y
real dist = 0 // The distance the wave missiles have travelled.
real count = 0 // Used to properly space out the fires
integer num
real speed
timer tim
static method create takes unit c, unit t returns thistype
local thistype this = thistype.allocate()
local real dx
local real dy
local real ang
local integer i = 0
set .cast = c
set .lvl = GetUnitAbilityLevel(.cast,SPELL_ID)
set .x = GetUnitX(c)
set .y = GetUnitY(c)
set .targ = t
set .targX = GetUnitX(t)
set .targY = GetUnitY(t)
set dx = .targX-.x
set dy = .targY-.y
set .targDist = SquareRoot(dx*dx+dy*dy)
set .num = WaveNumber(.lvl)
set .speed = WaveSpeed(.lvl)*TIMER_LOOP
set ang = Atan2(dy,dx)
loop
set .wave[i] = xefx.create(.x,.y,ang)
set .wave[i].fxpath = WAVE_SFX
set .wave[i].scale = WAVE_SCALE
set .wave[i].z = WAVE_HEIGHT
set .xyArc[i] = WaveXYArc(.lvl)*MissileDeviate(i,.num)
set i = i + 1
exitwhen i == .num
endloop
set .tim = NewTimer()
call SetTimerData(.tim,this)
call TimerStart(.tim,TIMER_LOOP,true,function thistype.onLoop)
return this
endmethod
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real newX
local real newY
local real ang
local integer i = 0
local real xyArc
local boolean createFire = false
if .targ != null then
if not IsUnitAlive(.targ) then
set .targ = null
else
set newX = GetUnitX(.targ)
set newY = GetUnitY(.targ)
set .targDist = .targDist + SquareRoot((newX-.targX)*(newX-.targX)+(newY-.targY)*(newY-.targY))
set .targX = newX
set .targY = newY
endif
endif
if .dist + .speed < .targDist then
if .count == 0 then
set createFire = true
endif
set ang = Atan2(.targY-.y,.targX-.x)
set .x = .x + .speed*Cos(ang)
set .y = .y + .speed*Sin(ang)
loop
set xyArc = ParabolaZ(.xyArc[i],.targDist,.dist)
set .wave[i].x = .x + xyArc*Cos(ang+0.5*bj_PI)
set .wave[i].y = .y + xyArc*Sin(ang+0.5*bj_PI)
if createFire then
call Fire.create(.wave[i].x,.wave[i].y,.lvl,GetOwningPlayer(.cast),WaveDur(.lvl))
endif
set i = i + 1
exitwhen i == .num
endloop
set .dist = .dist + .speed
set .count = .count + .speed
if .count >= WAVE_FIRE_SPACE then
set .count = 0
endif
else
if IsUnitAlive(.targ) then
call UnitDamageTarget(.cast,.targ,Damage(.lvl),false,true,ATK_TYPE,DMG_TYPE,WPN_TYPE)
endif
loop
set .wave[i].x = .targX
set .wave[i].y = .targY
call .wave[i].destroy()
set i = i + 1
exitwhen i == .num
endloop
set i = 0
loop
set newX = .targX + RingArea(.lvl)*Cos(TWO_PI/RingNumber(.lvl)*i)
set newY = .targY + RingArea(.lvl)*Sin(TWO_PI/RingNumber(.lvl)*i)
call Fire.create(newX,newY,.lvl,GetOwningPlayer(.cast),RingDur(.lvl))
set i = i + 1
exitwhen i == RingNumber(.lvl)
endloop
call ReleaseTimer(.tim)
call .destroy()
endif
endmethod
static method spellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.create(GetTriggerUnit(),GetSpellTargetUnit())
endif
return false
endmethod
endstruct
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition(function Data.spellActions))
static if DO_PRELOAD then
call XE_PreloadAbility(BURN_ID)
call Preload(WAVE_SFX)
call PreloadStart()
endif
endfunction
endscope