library GuardianAngel requires xefx, TimerUtils, GroupUtils, xedamage, BonusMod optional BoundSentinel
//===============================================================================================
//Guardian Angel v1.01c by scorpion182
//
//requires:
//- xe, TimerUtils, BoundSentinel by vexorian
//- GroupUtils by Rising_Dusk
//- BonusMod by Earth-Fury
//
//===============================================================================================
//============CALIBRATION SECTION================================================================
globals
private keyword data //don't touch this
private constant integer SPELL_ID = 'A000' //ability rawcode
private constant real CASTING_TIME = 1.8 //ability casting time
private constant string ANGEL_PATH = "Abilities\\Spells\\Human\\Resurrect\\ResurrectCaster.mdl" //path for angel fx
private constant string HEAL_PATH = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl" //path for healing missile
private constant string HEAL_FX = "Abilities\\Spells\\Items\\ResourceItems\\ResourceEffectTarget.mdl" //path for on-heal fx
private constant string HEAL_ATTCH = "origin" //heal fx attachment point
private constant real SCALE = 1. //missile scale
private constant real MIN_DIST = 80. //how close the missile to heal the target
private constant real INIT_H = 500. //initial height for the angel
private constant integer RED = 128 //vertex coloring in RGB
private constant integer GREEN = 255
private constant integer BLUE = 128
private constant integer ALPHA = 255
private constant boolean BONUS_STACK = true //armor bonuses are stack if true
private constant boolean PRELOAD_FX = true //preload the fx?
endglobals
private constant function GetHeal takes integer lvl returns real
return 100. + lvl * 0. //heal amount of hp
endfunction
private constant function GetBonusArmor takes integer lvl returns integer
return 3 + lvl * 0 //add bonus armor
endfunction
private constant function GetDuration takes integer lvl returns real
return 10. + lvl * 0. //bonus armor duration in seconds
endfunction
private constant function GetSpeed takes integer lvl returns real
return 800. * XE_ANIMATION_PERIOD + lvl * 0. //missile speed
endfunction
private constant function GetAoE takes integer lvl returns real
return 400. + lvl * 0. //aoe must match the object editor value
endfunction
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
set spellDamage.visibleOnly = true
set spellDamage.damageSelf = true
set spellDamage.damageAllies = true //heals for amount of
set spellDamage.allyfactor = -1.0 //hitpoints to allies.
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 IsUnitType(u,UNIT_TYPE_STRUCTURE)==false and IsUnitAlly(u,GetOwningPlayer(s.caster))==true and IsUnitVisible(u,GetOwningPlayer(s.caster))==true
endfunction
//==========END OF CALIBRATION SECTION===========================================================
globals
private xedamage xed
endglobals
//struct for the missile
private struct heal
timer t
unit caster
unit target
xefx fx
real x
real y
integer lvl
boolean bonus=false
static method create takes unit c, unit tr, real x, real y, integer lvl returns thistype
local thistype this = heal.allocate()
local real a = Atan2(y-GetUnitY(tr),x-GetUnitX(tr))
set .x = x
set .y = y
set .caster = c
set .target = tr
set .lvl = lvl
set .t = NewTimer()
set .fx = xefx.create(x,y,a)
set .fx.fxpath = HEAL_PATH
set .fx.z = INIT_H
set .fx.scale = SCALE
call .fx.recolor(RED, GREEN, BLUE, ALPHA)
call SetTimerData(.t, this)
call TimerStart(.t, XE_ANIMATION_PERIOD, true, function heal.onMove)
return this
endmethod
static method onMove takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
local real x = GetUnitX(.target) - .fx.x
local real y = GetUnitY(.target) - .fx.y
local real z = (GetUnitFlyHeight(.target)-.fx.z) + GetSpeed(.lvl)
local real distance = SquareRoot(x*x + y*y + z*z)
local real angle1 = Atan2(y, x)
local real angle2 = Acos(z / distance)
local real angle3 = Atan2(z, SquareRoot(x * x + y * y))
if not IsUnitType(.target, UNIT_TYPE_DEAD) then
if (distance > MIN_DIST) then
set .fx.xyangle = angle1
set .fx.x = .fx.x + GetSpeed(.lvl) * Cos(angle1) * Sin(angle2)
set .fx.y = .fx.y + GetSpeed(.lvl) * Sin(angle1) * Sin(angle2)
set .fx.z = .fx.z + GetSpeed(.lvl) * Cos(angle2)
set .fx.zangle = angle3
else
if not BONUS_STACK then //static ifs doesn't work here --a
if GetUnitBonus(.target, BONUS_ARMOR)==0 then
call AddUnitBonus(.target, BONUS_ARMOR, GetBonusArmor(.lvl))
set .bonus=true
endif
else
call AddUnitBonus(.target, BONUS_ARMOR, GetBonusArmor(.lvl))
set .bonus = true
endif
call xed.damageTarget(.caster, .target, GetHeal(.lvl))
call DestroyEffect(AddSpecialEffectTarget(HEAL_FX, .target, HEAL_ATTCH))
call .fx.destroy()
call SetTimerData(.t, this)
call TimerStart(.t, GetDuration(.lvl), false, function thistype.onFinish)
endif
else
call .fx.destroy()
call .destroy()
endif
endmethod
static method onFinish takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
if .bonus then
call SetUnitBonus(.target, BONUS_ARMOR, GetUnitBonus(.target, BONUS_ARMOR) - GetBonusArmor(.lvl))
endif
call .destroy()
endmethod
private method onDestroy takes nothing returns nothing
call ReleaseTimer(.t)
endmethod
endstruct
//struct for the angel
private struct data
unit caster
timer t
xefx fx
real x
real y
integer lvl
private static thistype temp
static method create takes unit c, real x, real y returns thistype
local thistype this = data.allocate()
local real a = Atan2(y - GetUnitY(c), x - GetUnitX(c))
set .caster = c
set .lvl = GetUnitAbilityLevel(c,SPELL_ID)
set .x = x
set .y = y
set .t = NewTimer()
set .fx = xefx.create(x,y,a)
set .fx.fxpath = ANGEL_PATH
//set .fx.z = INIT_H //the angel already fly?
set .fx.scale = SCALE
call SetTimerData(.t, this)
call TimerStart(.t, CASTING_TIME, false, function thistype.onFinish)
return this
endmethod
static method onFinish takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
set temp = this
call GroupEnumUnitsInRange(ENUM_GROUP, .x, .y, GetAoE(.lvl), function data.VictimFilter)
call .destroy()
endmethod
private method onDestroy takes nothing returns nothing
call ReleaseTimer(.t)
call .fx.destroy()
endmethod
static method VictimFilter takes nothing returns boolean
local unit u = GetFilterUnit()
local heal h
if IsValidTarget(u, temp) then
set h = heal.create(temp.caster, u, temp.x, temp.y, temp.lvl)
endif
set u = null
return false
endmethod
static method SpellEffect takes nothing returns boolean
local thistype this
if GetSpellAbilityId()==SPELL_ID then
set this = thistype.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
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, function thistype.SpellEffect)
//init xedamage
set xed = xedamage.create()
call DamageOptions(xed)
static if PRELOAD_FX then
call Preload(ANGEL_PATH)
call Preload(HEAL_PATH)
call Preload(HEAL_FX)
endif
endmethod
endstruct
endlibrary