Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=20
//TESH.alwaysfold=0
library PrismaticSprayConfig
//===========================================================================================
//= Configuration =
//===========================================================================================
globals
public constant integer ABILITY_ID = 'A000'
public constant integer DUMMY = 'e000'
//The point after the beginning of the animation at which the rays should fire (animation-based)
public constant real EFFECT_POINT_TIME = 0.45
//The point after which each "round" of the spell ends
//The spell duration should be a multiple of this
public constant real EFFECT_END_TIME = 1.208
//The offset towards the target from the centre of the hero where the effect should begin (model-based)
public constant real EFFECT_POINT_XY_FORWARD = 75.
//The offset perpendicular to the target from the centre of the model where the effect should begin (model-based)
public constant real EFFECT_POINT_XY_PERP = 0.
//The height at which the effect should begin (model-based)
public constant real EFFECT_POINT_Z = 30.
//The number of SFX displayed in a line per ray
public constant integer RAY_EFFECT_COUNT = 15
//The length of each ray
public constant real RAY_EFFECT_LENGTH = 500.
//The width of each ray's effect
public constant real RAY_EFFECT_WIDTH = 100.
//The elevation of each ray's special effects
public constant real RAY_EFFECT_HEIGHT = 30.
//The length of time that each ray applies its effect
//.9s is appropriate for most sfx.
public constant real RAY_EFFECT_DURATION = .9
//Frequency at which ray effects are checked
public constant real RAY_TIMER_TICK = .03
//The size of the arc (in radians) in which rays are emitted
public constant real RAY_EFFECT_ARC = bj_PI/2.
//Enemies below this much health die instantly when hit by a dark ray.
public constant real DARK_RAY_KILL_THRESHOLD = 200.
//Special effect models
public constant string RAY_OF_FIRE_EFFECT = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
public constant string RAY_OF_FROST_EFFECT = "Abilities\\Weapons\\ZigguratFrostMissile\\ZigguratFrostMissile.mdl"
public constant string HOLY_RAY_EFFECT = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"
public constant string DARK_RAY_EFFECT = "Abilities\\Weapons\\BansheeMissile\\BansheeMissile.mdl"
public constant string ARCANE_RAY_EFFECT = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
//Spell IDs and OrderIDs
public constant integer ARCANE_RAY_HASTE_ID = 'A001'
public constant integer ARCANE_RAY_SLOW_ID = 'A002'
public constant integer RAY_OF_FROST_SLOW_ID = 'A003'
public constant integer RAY_OF_FIRE_BURN_ID = 'A004'
public constant integer DARK_RAY_STUN_ID = 'A005'
public constant integer ARCANE_RAY_HASTE_ORDER_ID = 852101
public constant integer ARCANE_RAY_SLOW_ORDER_ID = 852075
public constant integer RAY_OF_FROST_SLOW_ORDER_ID = 852226
public constant integer RAY_OF_FIRE_BURN_ORDER_ID = 852662
public constant integer DARK_RAY_STUN_ORDER_ID = 852095
endglobals
//Level-based effects
public function RayOfFrostDamageTick takes integer level returns real
return 2. + I2R(level)
endfunction
public function RayOfFireDamageTick takes integer level returns real
return 3. + 2.*I2R(level)
endfunction
public function HolyRayHealingTick takes integer level returns real
return 5. + 3.*I2R(level)
endfunction
//You can use GetRandomInt to randomize the number of rays somewhat
public function RaysPerLevel takes integer level returns integer
return GetRandomInt(3,5) + level
endfunction
public function TargetFilter takes nothing returns boolean
return GetWidgetLife(GetFilterUnit()) > 0. and not (IsUnitType(GetFilterUnit(),UNIT_TYPE_FLYING) or IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE))
endfunction
endlibrary
//TESH.scrollpos=39
//TESH.alwaysfold=0
library PrismaticSprayGeometry requires PrismaticSprayConfig
struct Rectangle
real p1x
real p2x
real p3x
real p4x
real p1y
real p2y
real p3y
real p4y
real x12diff
real x23diff
real x34diff
real x41diff
real y12diff
real y23diff
real y34diff
real y41diff
//1 and 3 (and also 2 and 4) are opposite corners
static method create takes real x1, real y1, real x2, real y2, real x3, real y3, real x4, real y4 returns thistype
local thistype this = thistype.allocate()
set this.p1x = x1
set this.p2x = x2
set this.p3x = x3
set this.p4x = x4
set this.p1y = y1
set this.p2y = y2
set this.p3y = y3
set this.p4y = y4
set this.x12diff = x2 - x1
set this.x23diff = x3 - x2
set this.x34diff = x4 - x3
set this.x41diff = x1 - x4
set this.y12diff = y2 - y1
set this.y23diff = y3 - y2
set this.y34diff = y4 - y3
set this.y41diff = y1 - y4
return this
endmethod
static method createFacing takes real px, real py, real width, real length, real angle returns thistype
local real perpAngle = angle + bj_PI/2.
local real offsetCosP = width/2. * Cos(perpAngle)
local real offsetSinP = width/2. * Sin(perpAngle)
local real lenCos = length * Cos(angle)
local real lenSin = length * Sin(angle)
local real p1x = px - offsetCosP
local real p1y = py - offsetSinP
local real p2x = px + offsetCosP
local real p2y = py + offsetSinP
return thistype.create(p1x, p1y, p2x, p2y, p2x + lenCos, p2y + lenSin, p1x + lenCos, p1y + lenSin)
endmethod
method containsPoint takes real px, real py returns boolean
return (py - this.p1y)*this.x12diff <= (px - this.p1x)*this.y12diff and (py - this.p2y)*this.x23diff <= (px - this.p2x)*this.y23diff and (py - this.p3y)*this.x34diff <= (px - this.p3x)*this.y34diff and (py - this.p4y)*this.x41diff <= (px - this.p4x)*this.y41diff
endmethod
endstruct
//Requires Count >= 2
function DisplayEffectInLine takes string model, real px, real py, real distance, real angle, real height, integer count returns nothing
local real dx = px + distance * Cos(angle)
local real dy = py + distance * Sin(angle)
local real dist = SquareRoot((dx - px)*(dx - px) + (dy - py)*(dy - py))
local real vx = distance/I2R(count - 1) * Cos(angle)
local real vy = distance/I2R(count - 1) * Sin(angle)
local unit u
loop
set count = count - 1
exitwhen count <= 0
set u = CreateUnit(Player(15),PrismaticSprayConfig_DUMMY,px,py,270.)
call UnitApplyTimedLife(u,'BTLF',2.)
call SetUnitFlyHeight(u,height,0.)
call SetUnitExploded(u,true)
call DestroyEffect(AddSpecialEffectTarget(model,u,"origin"))
set px = px + vx
set py = py + vy
endloop
set u = null
endfunction
endlibrary
//TESH.scrollpos=31
//TESH.alwaysfold=0
library PrismaticSprayRays requires PrismaticSprayGeometry, PrismaticSprayConfig
globals
private constant hashtable rays = InitHashtable()
private constant timer T = CreateTimer()
private constant group G = CreateGroup()
private integer numRays = 0
private constant real RADIUS = 2.*RMaxBJ(PrismaticSprayConfig_RAY_EFFECT_LENGTH,PrismaticSprayConfig_RAY_EFFECT_WIDTH)
endglobals
public function interface RayEffect takes unit source, unit target, integer level returns nothing
public function NoInitialEffect takes unit source, unit target, integer level returns nothing
endfunction
public function NoOngoingEffect takes unit source, unit target, integer level returns nothing
endfunction
private struct RayStruct
Rectangle rayRect
unit owner
real lifespan
group targetedUnits
integer level
RayEffect callbackInitial
RayEffect callbackOngoing
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.targetedUnits = CreateGroup()
return this
endmethod
method onDestroy takes nothing returns nothing
call GroupClear(this.targetedUnits)
call DestroyGroup(this.targetedUnits)
set this.targetedUnits = null
set this.owner = null
call this.rayRect.destroy()
endmethod
endstruct
private function RayTimerLoop takes nothing returns nothing
local RayStruct current
local integer i = 0
local unit u
loop
exitwhen i == numRays
set current = LoadInteger(rays,i,0)
call GroupEnumUnitsInRange(G, current.rayRect.p1x, current.rayRect.p1y, RADIUS, Filter(function PrismaticSprayConfig_TargetFilter))
loop
set u = FirstOfGroup(G)
exitwhen u == null
if current.rayRect.containsPoint(GetUnitX(u),GetUnitY(u)) then
if not IsUnitInGroup(u, current.targetedUnits) then
call current.callbackInitial.evaluate(current.owner,u,current.level)
call GroupAddUnit(current.targetedUnits, u)
endif
call current.callbackOngoing.evaluate(current.owner,u,current.level)
endif
call GroupRemoveUnit(G,u)
endloop
set current.lifespan = current.lifespan - PrismaticSprayConfig_RAY_TIMER_TICK
if current.lifespan <= 0. then
call current.destroy()
call SaveInteger(rays,i,0,LoadInteger(rays,0,numRays - 1))
set numRays = numRays - 1
call FlushChildHashtable(rays,numRays)
else
set i = i + 1
endif
endloop
if numRays == 0 then
call PauseTimer(T)
endif
endfunction
function FireRay takes unit owner, string sfx, real sourcex, real sourcey, real angle, real height, integer level, RayEffect initialEffect, RayEffect ongoingEffect returns nothing
local RayStruct ray = RayStruct.create()
set ray.rayRect = Rectangle.createFacing(sourcex,sourcey,PrismaticSprayConfig_RAY_EFFECT_WIDTH,PrismaticSprayConfig_RAY_EFFECT_LENGTH,angle)
set ray.owner = owner
set ray.callbackInitial = initialEffect
set ray.callbackOngoing = ongoingEffect
set ray.lifespan = PrismaticSprayConfig_RAY_EFFECT_DURATION
set ray.level = level
call DisplayEffectInLine(sfx,sourcex,sourcey,PrismaticSprayConfig_RAY_EFFECT_LENGTH,angle,PrismaticSprayConfig_RAY_EFFECT_HEIGHT,PrismaticSprayConfig_RAY_EFFECT_COUNT)
call SaveInteger(rays,numRays,0,ray)
set numRays = numRays + 1
if numRays == 1 then
call TimerStart(T,PrismaticSprayConfig_RAY_TIMER_TICK,true,function RayTimerLoop)
endif
endfunction
endlibrary
//TESH.scrollpos=28
//TESH.alwaysfold=0
scope PrismaticSpray initializer init
globals
private constant hashtable timers = InitHashtable()
endglobals
private function CastSpell takes unit caster, unit target, integer level, integer spell, integer order returns nothing
local unit dumm = CreateUnit(GetOwningPlayer(caster),PrismaticSprayConfig_DUMMY,GetUnitX(caster),GetUnitY(caster),Atan2(GetUnitY(target)-GetUnitY(caster),GetUnitX(target)-GetUnitX(caster))*bj_RADTODEG)
call UnitApplyTimedLife(dumm,'BTLF',5.)
call SetUnitExploded(dumm,true)
call UnitAddAbility(dumm,spell)
call SetUnitAbilityLevel(dumm,spell,level)
call IssueTargetOrderById(dumm,order,target)
set dumm = null
endfunction
private function RayOfFrostInitial takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
call CastSpell(source,target,level,PrismaticSprayConfig_RAY_OF_FROST_SLOW_ID,PrismaticSprayConfig_RAY_OF_FROST_SLOW_ORDER_ID)
endif
endfunction
private function RayOfFrostOngoing takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
call UnitDamageTarget(source,target,PrismaticSprayConfig_RayOfFrostDamageTick(level),false,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
endif
endfunction
private function RayOfFireInitial takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
call CastSpell(source,target,level,PrismaticSprayConfig_RAY_OF_FIRE_BURN_ID,PrismaticSprayConfig_RAY_OF_FIRE_BURN_ORDER_ID)
endif
endfunction
private function RayOfFireOngoing takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
call UnitDamageTarget(source,target,PrismaticSprayConfig_RayOfFireDamageTick(level),false,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
endif
endfunction
private function HolyRayOngoing takes unit source, unit target, integer level returns nothing
if IsPlayerAlly(GetOwningPlayer(target),GetOwningPlayer(source)) then
call SetWidgetLife(target,GetWidgetLife(target) + PrismaticSprayConfig_HolyRayHealingTick(level))
endif
endfunction
private function DarkRayInitial takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
if GetWidgetLife(target) < PrismaticSprayConfig_DARK_RAY_KILL_THRESHOLD then
call UnitDamageTarget(source,target,99999999.,false,true,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
else
call CastSpell(source,target,level,PrismaticSprayConfig_DARK_RAY_STUN_ID,PrismaticSprayConfig_DARK_RAY_STUN_ORDER_ID)
endif
endif
endfunction
private function ArcaneRayInitial takes unit source, unit target, integer level returns nothing
if IsPlayerEnemy(GetOwningPlayer(target),GetOwningPlayer(source)) then
call CastSpell(source,target,level,PrismaticSprayConfig_ARCANE_RAY_SLOW_ID,PrismaticSprayConfig_ARCANE_RAY_SLOW_ORDER_ID)
elseif IsPlayerAlly(GetOwningPlayer(target),GetOwningPlayer(source)) then
call CastSpell(source,target,level,PrismaticSprayConfig_ARCANE_RAY_HASTE_ID,PrismaticSprayConfig_ARCANE_RAY_HASTE_ORDER_ID)
endif
endfunction
private struct Data
unit caster
integer level
real angle
boolean roundEnd
method onDestroy takes nothing returns nothing
set this.caster = null
endmethod
endstruct
private function ShootRays takes Data dat returns nothing
local real sourcex = GetUnitX(dat.caster) + PrismaticSprayConfig_EFFECT_POINT_XY_FORWARD * Cos(dat.angle) + PrismaticSprayConfig_EFFECT_POINT_XY_PERP * Cos(dat.angle + bj_PI/2.)
local real sourcey = GetUnitY(dat.caster) + PrismaticSprayConfig_EFFECT_POINT_XY_FORWARD * Sin(dat.angle) + PrismaticSprayConfig_EFFECT_POINT_XY_PERP * Sin(dat.angle + bj_PI/2.)
local integer numRays = PrismaticSprayConfig_RaysPerLevel(dat.level)
local real angle = dat.angle - PrismaticSprayConfig_RAY_EFFECT_ARC/2.
local real angleIncrement = PrismaticSprayConfig_RAY_EFFECT_ARC/I2R(numRays - 1)
local integer rayType
local PrismaticSprayRays_RayEffect initialEffect
local PrismaticSprayRays_RayEffect ongoingEffect
local string sfx
loop
exitwhen numRays == 0
set rayType = GetRandomInt(0,4)
if rayType == 0 then //Fire
set initialEffect = RayOfFireInitial
set ongoingEffect = RayOfFireOngoing
set sfx = PrismaticSprayConfig_RAY_OF_FIRE_EFFECT
elseif rayType == 1 then //Frost
set initialEffect = RayOfFrostInitial
set ongoingEffect = RayOfFireOngoing
set sfx = PrismaticSprayConfig_RAY_OF_FROST_EFFECT
elseif rayType == 2 then //Holy
set initialEffect = PrismaticSprayRays_NoInitialEffect
set ongoingEffect = HolyRayOngoing
set sfx = PrismaticSprayConfig_HOLY_RAY_EFFECT
elseif rayType == 3 then //Dark
set initialEffect = DarkRayInitial
set ongoingEffect = PrismaticSprayRays_NoOngoingEffect
set sfx = PrismaticSprayConfig_DARK_RAY_EFFECT
elseif rayType == 4 then //Arcane
set initialEffect = ArcaneRayInitial
set ongoingEffect = PrismaticSprayRays_NoOngoingEffect
set sfx = PrismaticSprayConfig_ARCANE_RAY_EFFECT
endif
call FireRay(dat.caster,sfx,sourcex,sourcey,angle,PrismaticSprayConfig_EFFECT_POINT_Z,dat.level,initialEffect,ongoingEffect)
set numRays = numRays - 1
set angle = angle + angleIncrement
endloop
endfunction
private function TimerExpires takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data dat = GetTimerData(t)
//Create rays if appropriate, then go to next iteration
if dat.roundEnd then
call TimerStart(t,PrismaticSprayConfig_EFFECT_POINT_TIME,false,function TimerExpires)
else
call ShootRays(dat)
call TimerStart(t,PrismaticSprayConfig_EFFECT_END_TIME - PrismaticSprayConfig_EFFECT_POINT_TIME,false,function TimerExpires)
endif
set dat.roundEnd = not dat.roundEnd
set t = null
endfunction
private function Actions takes nothing returns nothing
local Data dat = Data.create()
local timer t = NewTimer()
local real x = GetUnitX(GetTriggerUnit())
local real y = GetUnitY(GetTriggerUnit())
set dat.caster = GetTriggerUnit()
set dat.level = GetUnitAbilityLevel(dat.caster,GetSpellAbilityId())
set dat.angle = Atan2(GetSpellTargetY() - y,GetSpellTargetX() - x)
set dat.roundEnd = false
call SaveTimerHandle(timers,GetHandleId(dat.caster),0,t)
call SetTimerData(t,dat)
call TimerStart(t,PrismaticSprayConfig_EFFECT_POINT_TIME,false,function TimerExpires)
set t = null
endfunction
private function EndActions takes nothing returns nothing
local timer t = LoadTimerHandle(timers,GetHandleId(GetTriggerUnit()),0)
local Data dat = GetTimerData(t)
call FlushChildHashtable(timers,GetHandleId(GetTriggerUnit()))
call dat.destroy()
call PauseTimer(t)
call ReleaseTimer(t)
set t = null
endfunction
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == PrismaticSprayConfig_ABILITY_ID
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 Conditions))
call TriggerAddAction(t,function Actions)
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_FINISH)
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t,Condition(function Conditions))
call TriggerAddAction(t,function EndActions)
endfunction
endscope