library BlackMagic initializer Init requires GroupUtils, xebasic, xefx, xedamage, xemissile
private keyword SpellData
globals
public constant integer SPELL_ID = 'BkMg'
public constant integer BUFF_ID = 'BBMg'
public constant real PERIOD = 0.20 //The spell heals/damages every PERIOD seconds
public constant damagetype DMG_TYPE = DAMAGE_TYPE_MAGIC //see xedamage documentation if you want to change this
private constant real PI = 3.14159 //don't touch this one
endglobals
scope SFX
scope DRAIN
globals
public constant string MODEL = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl" //Need to use double backslash because the World Editor removes 1 \ when it saves... pretty dumb.
public constant real LAUNCH_ANGLE = PI/180*0.00 //launch angle (up from horizontal), we expect radians here so need to convert by multiplying pi/180
public constant real LAUNCH_ZOFFSET = 200.00
public constant real IMPACT_ZOFFSET = 0.00
public constant real SPEED = 725.00
endglobals
endscope
scope HEAL
globals
public constant string MODEL = "Abilities\\Weapons\\GargoyleMissile\\GargoyleMissile.mdl"
public constant real LAUNCH_ANGLE = PI/180*5.00 //launch angle (up from horizontal), we expect radians here so need to convert by multiplying pi/180
public constant real LAUNCH_ZOFFSET = 0.00
public constant real IMPACT_ZOFFSET = 200.00
public constant real SPEED = 1100.00
endglobals
endscope
endscope
private function DrainTargets takes integer LEVEL returns integer
return 3*LEVEL
endfunction
private function DrainRange takes integer LEVEL returns real
return 600.00*LEVEL
endfunction
private function DrainPerSecond takes integer LEVEL returns real
return 50.00*LEVEL
endfunction
private function HealPerSecond takes integer LEVEL returns real
return 50.00*LEVEL
endfunction
private function ChaosCap takes integer LEVEL returns real
return 500.00*LEVEL
endfunction
private function ValidTarget takes unit TARGET, unit HOST, player CASTER_OWNER, integer LEVEL returns boolean
return IsUnitEnemy(TARGET, CASTER_OWNER) /*
*/ and GetWidgetLife(TARGET) > 0.405 /* A unit dies when it has 0.405 health or less
*/ and not IsUnitType(TARGET, UNIT_TYPE_STRUCTURE) /*
*/ and not IsUnitType(TARGET, UNIT_TYPE_MAGIC_IMMUNE)/*
*/ and not IsUnitType(TARGET, UNIT_TYPE_HERO)
endfunction
private struct HealMissile extends xehomingmissile
SpellData SD
private method onHit takes nothing returns nothing
call .SD.AddChaos(.SD.Heal)
endmethod
endstruct
private struct DrainMissile extends xehomingmissile
SpellData SD
private method onHit takes nothing returns nothing
local real xTgt = .x
local real yTgt = .y
local real zTgt = GetUnitFlyHeight(.targetUnit)
local HealMissile HM = HealMissile.create(xTgt, yTgt, zTgt+SFX_HEAL_LAUNCH_ZOFFSET, .SD.Host, SFX_HEAL_IMPACT_ZOFFSET)
set HM.SD = .SD
set HM.fxpath = SFX_HEAL_MODEL
call HM.launch(SFX_HEAL_SPEED, SFX_HEAL_LAUNCH_ANGLE)
call SpellData.DamageData.damageTarget(.SD.Dummy, .targetUnit, .SD.Dmg)
endmethod
endstruct
private struct SpellData
unit Host
unit Target = null
integer Owner
real Chaos = 0.00
real ChaosMax = 200.00
integer Level
integer index
unit Dummy
real Dmg
real Heal
real HealTTL
static integer LoopTotal = 0
static thistype array LoopData
static timer LoopTimer = CreateTimer()
static boolexpr TargetFilter
static xedamage DamageData
static thistype TmpSD
private static unit Uclose
private static real Dclose
private static real xHost
private static real yHost
static method ForgetUnit takes unit u returns nothing
local integer i = 0
loop
exitwhen i >= LoopTotal
set TmpSD = LoopData[i]
if TmpSD.Host == u then
call TmpSD.destroy()
exitwhen true
endif
endloop
endmethod
method AddChaos takes real Amt returns nothing
set .Chaos = Chaos+Amt
if .Chaos > .ChaosMax then
set .Chaos = .ChaosMax
endif
endmethod
method HealHost takes nothing returns nothing
local real HP = GetWidgetLife(.Host)
local real MxHP = GetUnitState(.Host, UNIT_STATE_MAX_LIFE)
local real Amt = .Heal
if .Chaos > 0.00 then
if Amt > .Chaos then
set Amt = .Chaos
endif
if HP+Amt > MxHP then
set Amt = MxHP-HP
endif
call SetWidgetLife(.Host, HP+Amt)
set .Chaos = .Chaos-Amt
endif
endmethod
static method ValidTargetFilterFunc takes nothing returns boolean
local unit u = GetFilterUnit()
local real x
local real y
local real d2
local boolean b = ValidTarget(u, TmpSD.Host, Player(TmpSD.Owner), TmpSD.Level)
if b then
set x = GetUnitX(u)
set y = GetUnitY(u)
set d2 = (x-xHost)*(x-xHost)+(y-yHost)*(y-yHost)
if (d2 < Dclose) or (Uclose == null) then
set Uclose = u
set Dclose = d2
endif
endif
set u = null
return false
endmethod
static method Periodic takes nothing returns nothing
local integer i = 0
local DrainMissile DM
loop
exitwhen i >= LoopTotal
set TmpSD = LoopData[i]
if (TmpSD.Host == null) or (GetWidgetLife(TmpSD.Host) <= 0.405) or (GetUnitAbilityLevel(TmpSD.Host, BUFF_ID) == 0) then
call TmpSD.destroy()
if i == LoopTotal then
set i = i+1
endif
else
set Uclose = null
set Dclose = 0.00
set xHost = GetUnitX(TmpSD.Host)
set yHost = GetUnitY(TmpSD.Host)
call GroupEnumUnitsInArea(ENUM_GROUP, xHost, yHost, DrainRange(TmpSD.Level), TargetFilter)
if Uclose != null then
set DM = DrainMissile.create(xHost, yHost, GetUnitFlyHeight(TmpSD.Host)+SFX_DRAIN_LAUNCH_ZOFFSET, Uclose, SFX_DRAIN_IMPACT_ZOFFSET)
set DM.SD = TmpSD
set DM.fxpath = SFX_DRAIN_MODEL
call DM.launch(SFX_DRAIN_SPEED, SFX_DRAIN_LAUNCH_ANGLE)
endif
call TmpSD.HealHost()
call ClearTextMessages()
call BJDebugMsg("Chaos: "+R2S(TmpSD.Chaos)+"/"+R2S(TmpSD.ChaosMax))
set i = i+1
endif
endloop
endmethod
static method create takes unit H, player P, integer Level returns thistype
local thistype this = allocate()
set this.Host = H
set this.Owner = GetPlayerId(P)
set this.Level = Level
set this.index = LoopTotal
set this.Dummy = XE_NewDummyUnit(P, 0.00, 0.00, 0.00)
set this.Dmg = DrainPerSecond(Level)*PERIOD
set this.Heal = HealPerSecond(Level)*PERIOD
set this.ChaosMax = ChaosCap(Level)
set LoopData[LoopTotal] = this
set LoopTotal = LoopTotal + 1
if LoopTotal == 1 then
call TimerStart(LoopTimer, PERIOD, true, function thistype.Periodic)
endif
return this
endmethod
method onDestroy takes nothing returns nothing
call XE_ReleaseDummyUnit(.Dummy)
set .Dummy = null
set .Host = null
set .Target = null
set LoopTotal = LoopTotal-1
set LoopData[index] = LoopData[LoopTotal]
set LoopData[index].index = .index
if LoopTotal == 0 then
call PauseTimer(LoopTimer)
endif
endmethod
static method onInit takes nothing returns nothing
set TargetFilter = Filter(function thistype.ValidTargetFilterFunc)
set DamageData = xedamage.create()
set DamageData.dtype = DAMAGE_TYPE_MAGIC
endmethod
endstruct
function OnCast takes nothing returns nothing
local unit c = GetTriggerUnit()
local unit h = GetSpellTargetUnit()
local player p = GetOwningPlayer(c)
local integer l = GetUnitAbilityLevel(c, SPELL_ID)
if GetUnitAbilityLevel(h, BUFF_ID) > 0 then
call SpellData.ForgetUnit(h)
endif
call SpellData.create(h, p, l)
set c = null
set h = null
set p = null
endfunction
function CorrectSpell takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function CorrectSpell))
call TriggerAddAction(t, function OnCast)
debug call BJDebugMsg("Debug mode enabled!")
endfunction
endlibrary