library TouchOfLight initializer Init
// Spell Touch of Light by The_Witcher
//
// Creates orbs that patrol in a area, draining life from enemies.
// When their time has come they return and heal the caster and his allies!
//
// THE SETUP PART:
globals
// the rawcode of the spell
private constant integer SPELL_ID = 'A000'
// the rawcode of the dummy used as orb
private constant integer ORB_ID = 'h001'
// the range in which the orbs can drain
private constant integer ORB_DRAIN_RANGE = 150
// the timer interval (increase if laggy)
private constant real INTERVAL = 0.02
// the flying height of the orbs
private constant real ORB_HEIGHT = 80
// the time it takes a orb to drain life
private constant real DRAIN_TIME = 0.5
// false = only caster is healed
// true = caster and near allies are healed
private constant boolean AOE_HEAL = true
// the sfx created on units while healing
private constant string HEAL_SFX = "Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl"
// the attachement point for HEAL_SFX
private constant string HEAL_SFX_TARGET = "origin"
// the sfx created on units while draining
private constant string DRAIN_SFX = "Abilities\\Spells\\Human\\Slow\\SlowCaster.mdl"
// the sfx created on a orb when it returns to the caster
private constant string EXPLODE_SFX = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl"
// the lightning created while draining
private constant string LIGHTNING = "HWPB"
// false = lightning is normal
// true = lightnings endings are switched
private constant boolean LIGHTNING_TURNED = true
endglobals
private function GetOrbAmount takes integer lvl returns integer
return 4 * lvl // 4 orbs more each level !!CAN NEVER BE HIGHER THAN 50!!
endfunction
private function GetOrbDrainCooldown takes integer lvl returns real
return 1.1 - 0.1 * lvl // 1sec/0.9sec/0.8sec
endfunction
private function GetOrbAreaSize takes integer lvl returns real
return 300. + 50 * lvl // 350/400/450 radius for the area where the orbs do their work
endfunction
private function GetDrainPercentage takes integer lvl returns real
return 0.08 * lvl //draines 8% more each level
endfunction
private function GetDuration takes integer lvl returns real
return 5. * lvl //lasts for 4/8/12/... seconds
endfunction
private function GetHealPercentage takes integer lvl returns real
return 0.1 * lvl //heals 10% more each level
endfunction
private function GetHealRange takes integer lvl returns real
return 300. + 100 * lvl //heals in a AOE of 300/400/500/...
endfunction
private function GetOrbSpeed takes integer lvl returns real
return 5. + 1. * lvl // 6/7/8 per interval...
endfunction
//---------------------------------------------------------
//-----------Don't modify anything below this line---------
//---------------------------------------------------------
private struct data
unit u
real x
real y
integer lvl
unit array orb [50]
unit array target [50]
integer array phase [50]
real array drained [50]
real array delay [50]
lightning array light [50]
timer tim = CreateTimer()
integer orbs = 0
real temp = 19
real t = 0
endstruct
globals
private hashtable h = InitHashtable()
private data DATA
private group g = CreateGroup()
private location loc = Location(0,0)
endglobals
private function AngleBetweenCoords takes real x, real y, real xx, real yy returns real
return Atan2(yy - y, xx - x)
endfunction
private function DistanceBetweenCoords takes real x , real y , real xx , real yy returns real
return SquareRoot((x-xx)*(x-xx)+(y-yy)*(y-yy))
endfunction
private function DistanceBetweenUnits takes unit a, unit b returns real
local real dx = GetUnitX(b) - GetUnitX(a)
local real dy = GetUnitY(b) - GetUnitY(a)
return SquareRoot(dx * dx + dy * dy)
endfunction
private function AngleBetweenUnits takes unit a, unit b returns real
return Atan2(GetUnitY(b) - GetUnitY(a), GetUnitX(b) - GetUnitX(a))
endfunction
private function EnemiesOnly takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(DATA.u)) and not (IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) or GetUnitTypeId(GetFilterUnit()) == 0 )
endfunction
private function AlliesOnly takes nothing returns boolean
return IsUnitAlly(GetFilterUnit(),GetOwningPlayer(DATA.u)) and not (IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) or GetUnitTypeId(GetFilterUnit()) == 0 )
endfunction
private function AOEheal takes nothing returns nothing
call SetWidgetLife(GetEnumUnit(),GetWidgetLife(GetEnumUnit()) + DATA.temp * GetHealPercentage(DATA.lvl))
call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX,GetEnumUnit(),HEAL_SFX_TARGET))
endfunction
function RandomEnemyUnit takes real x, real y, real range returns unit
local unit a=null
local unit i
local integer b=0
call GroupClear(g)
call GroupEnumUnitsInRange(g,x,y,range,Condition(function EnemiesOnly))
loop
set i=FirstOfGroup(g)
exitwhen i==null
set b=b+1
if (GetRandomInt(1,b) == 1) then
set a = i
endif
call GroupRemoveUnit(g,i)
endloop
set bj_groupRandomCurrentPick=a
set a=null
return bj_groupRandomCurrentPick
endfunction
private function Execute takes nothing returns nothing
local data dat = LoadInteger(h,GetHandleId(GetExpiredTimer()),0)
local real X
local real Y
local real a
local integer i = 0
//****check whether caster is alive****
if (IsUnitType(dat.u,UNIT_TYPE_DEAD) or GetUnitTypeId(dat.u) == 0 ) then
loop
exitwhen i >= dat.orbs
call RemoveUnit(dat.orb[i])
call DestroyLightning(dat.light[i])
set i = i + 1
endloop
set dat.orbs = 0
else
//****increase time****
set dat.t = dat.t + INTERVAL
//****new orbs****
if dat.orbs < IMinBJ(GetOrbAmount(dat.lvl),50) and dat.t <= GetDuration(dat.lvl) then
set dat.temp = dat.temp + 1
if dat.temp == 20 then
set dat.orb[dat.orbs] = CreateUnit(GetOwningPlayer(dat.u),ORB_ID,GetUnitX(dat.u),GetUnitY(dat.u),0)
call UnitAddAbility(dat.orb[dat.orbs], 'Aloc')
call UnitAddAbility(dat.orb[dat.orbs], 'Amrf')
call SetUnitFlyHeight(dat.orb[dat.orbs],ORB_HEIGHT,0)
set dat.temp = 0
set DATA = dat
set dat.target[dat.orbs] = RandomEnemyUnit(dat.x,dat.y,GetOrbAreaSize(dat.lvl))
set dat.orbs = dat.orbs + 1
endif
endif
//****process all active orbs****
loop
exitwhen i >= dat.orbs
if dat.phase[i] == 0 then
//****search a target and drain****
//****reduce cooldown****
if dat.delay[i] > 0 then
set dat.delay[i] = dat.delay[i] - INTERVAL
if dat.delay[i] < 0 then
set dat.delay[i] = 0
endif
if dat.delay[i] <= GetOrbDrainCooldown(dat.lvl) then
if IsUnitPaused(dat.orb[i]) then
call DestroyLightning(dat.light[i])
call PauseUnit(dat.orb[i],false)
//****new target****
set DATA = dat
set dat.target[i] = RandomEnemyUnit(dat.x,dat.y,GetOrbAreaSize(dat.lvl))
endif
else
call MoveLocation(loc,GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]))
set a = GetLocationZ(loc)
call MoveLocation(loc,GetUnitX(dat.target[i]),GetUnitY(dat.target[i]))
if LIGHTNING_TURNED then
call MoveLightningEx(dat.light[i],true,GetUnitX(dat.target[i]),GetUnitY(dat.target[i]),GetLocationZ(loc)+50,GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]),a+ORB_HEIGHT)
else
call MoveLightningEx(dat.light[i],true,GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]),a+ORB_HEIGHT,GetUnitX(dat.target[i]),GetUnitY(dat.target[i]),GetLocationZ(loc)+50)
endif
endif
endif
//****if time is up and the orb isn't draining, start returning to the caster****
if dat.delay[i] <= GetOrbDrainCooldown(dat.lvl) and dat.t > GetDuration(dat.lvl) then
set dat.phase[i] = 1
elseif dat.target[i] != null then
//****distance to target check*****
if DistanceBetweenUnits(dat.target[i],dat.orb[i]) < ORB_DRAIN_RANGE then
//****no cooldown left?****
if dat.delay[i] == 0 then
//****drain****
set X = GetUnitX(dat.target[i])
set Y = GetUnitY(dat.target[i])
set a = GetWidgetLife(dat.target[i]) * GetDrainPercentage(dat.lvl)
set dat.drained[i] = dat.drained[i] + a
call SetWidgetLife(dat.target[i], GetWidgetLife(dat.target[i]) - a)
//****effects****
call DestroyEffect(AddSpecialEffect(DRAIN_SFX,X,Y))
if LIGHTNING_TURNED then
set dat.light[i] = AddLightningEx(LIGHTNING,true,X,Y,GetUnitFlyHeight(dat.target[i])+50,GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]),ORB_HEIGHT)
else
set dat.light[i] = AddLightningEx(LIGHTNING,true,GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]),ORB_HEIGHT,X,Y,GetUnitFlyHeight(dat.target[i])+50)
endif
//****cooldown****
call PauseUnit(dat.orb[i],true)
set dat.delay[i] = GetOrbDrainCooldown(dat.lvl) + DRAIN_TIME
endif
//****distance too huge so go nearer****
elseif not IsUnitPaused(dat.orb[i]) then
set a = AngleBetweenUnits(dat.orb[i],dat.target[i])
set X = GetUnitX(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Cos(a)
set Y = GetUnitY(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Sin(a)
call SetUnitX(dat.orb[i],X)
call SetUnitY(dat.orb[i],Y)
endif
else
//****new target****
set DATA = dat
set dat.target[i] = RandomEnemyUnit(dat.x,dat.y,GetOrbAreaSize(dat.lvl))
endif
else
//****Time is up so return to caster****
set a = AngleBetweenCoords(GetUnitX(dat.orb[i]),GetUnitY(dat.orb[i]),GetUnitX(dat.u),GetUnitY(dat.u))
set X = GetUnitX(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Cos(a)
set Y = GetUnitY(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Sin(a)
call SetUnitX(dat.orb[i],X)
call SetUnitY(dat.orb[i],Y)
//****caster reached so heal****
if DistanceBetweenCoords(X,Y,GetUnitX(dat.u),GetUnitY(dat.u)) < 10 then
call RemoveUnit(dat.orb[i])
call DestroyEffect(AddSpecialEffect(EXPLODE_SFX,X,Y))
if AOE_HEAL then
set DATA = dat
set dat.temp = dat.drained[i]
call GroupEnumUnitsInRange(g,X,Y,GetHealRange(dat.lvl),Condition(function AlliesOnly))
call ForGroup(g, function AOEheal)
else
call SetWidgetLife(dat.u,GetWidgetLife(dat.u) + dat.drained[i] * GetHealPercentage(dat.lvl))
call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX,dat.u,HEAL_SFX_TARGET))
endif
set dat.orbs = dat.orbs - 1
set dat.orb[i] = dat.orb[dat.orbs]
set i = i - 1
endif
endif
set i = i + 1
endloop
endif
//****no orbs left ==> end spell****
if dat.orbs == 0 then
call FlushChildHashtable(h,GetHandleId(dat.tim))
call DestroyTimer(dat.tim)
call dat.destroy()
endif
endfunction
private function Cast takes nothing returns boolean
local data dat
local integer i = 0
if GetSpellAbilityId() == SPELL_ID then
set dat = data.create()
loop
exitwhen i == 50
set dat.phase[i] = 0
set dat.drained[i] = 0
set dat.delay[i] = 0
set i = i + 1
endloop
set dat.x = GetSpellTargetX()
set dat.y = GetSpellTargetY()
set dat.u = GetTriggerUnit()
set dat.lvl = GetUnitAbilityLevel(dat.u,SPELL_ID)
call SaveInteger(h,GetHandleId(dat.tim),0,dat)
call TimerStart(dat.tim,INTERVAL,true,function Execute)
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerAddCondition(t,Condition(function Cast))
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
endfunction
endlibrary