//==========================================================================================
// SoulConsumption v1.02 by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - T32 http://www.thehelper.net/forums/showthread.php/132538-Timer32
// - xe system http://www.wc3c.net/showthread.php?t=101150
// * BoundSentinel http://www.wc3c.net/showthread.php?t=102576
// * GroupUtils http://www.wc3c.net/showthread.php?t=104464
//##########################################################################################
// Importing:
// 1. Copy the ability, Soul Consumption
// 2. Implement the required libraries
// 3. Copy this trigger
// 4. Configure the spell
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
// - Right now, the spell will make missiles from units that ordinarily cannot be raised if
// the spell is used before the unit decays completely. (Example: Ghost creep)
// - If the caster moves a great distance suddenly, the missiles will teleport to the
// caster instead of moving to that point like a real missile.
//==========================================================================================
scope SoulConsumption
//==========================================================================================
// CONSTANTS
//==========================================================================================
globals
//------------------------------------------------------------------------------------------
// General spell settings
//------------------------------------------------------------------------------------------
private constant integer SPELL_ID = 'A000' // The raw id of the spell
//------------------------------------------------------------------------------------------
// Missile settings
//------------------------------------------------------------------------------------------
private constant string MISSILE_SFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilMissile.mdl" // SFX for the missile
private constant real MISSILE_ROTATION = bj_PI // Determines how fast the missile will orbit the caster in radians
private constant real MISSILE_HEIGHT = 60. // Height of the missile
private constant real MISSILE_AREA = 50. // Area for the missile to damage enemy units
private constant real MAX_MISSILE_DUR = 2. // Max duration missiles will take to get to the caster
//------------------------------------------------------------------------------------------
// Other SFX settings
//------------------------------------------------------------------------------------------
private constant boolean PRELOAD = true // Determines if the spell preloads sfxs
private constant string DEAD_SFX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" // Sfx that plays on the dead units
private constant string HIT_SFX = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl"
private constant string HIT_ATTACH = "chest" // Attachment point for HIT_SFX
private constant string HEAL_SFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl" // Sfx for healing
private constant string HEAL_ATTACH = "origin" // Attachment point for HEAL_SFX
//------------------------------------------------------------------------------------------
// 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
//==========================================================================================
// OTHER CONFIGURATION
//==========================================================================================
// Determines the area of the spell
private constant function Area takes integer lvl returns real
return 450.+50*lvl
endfunction
// Determines which units will be consumed by the spell
private function DeadTargets takes unit target returns boolean
return IsUnitType(target, UNIT_TYPE_DEAD) and /*
*/ not IsUnitType(target, UNIT_TYPE_MECHANICAL) and /*
*/ not IsUnitType(target, UNIT_TYPE_HERO) and /*
*/ not IsUnitType(target, UNIT_TYPE_FLYING)
endfunction
// Determines which units will be damaged by the spell
private function AffectedTargets takes unit target, player owner returns boolean
return not IsUnitType(target, UNIT_TYPE_DEAD) and /*
*/ IsUnitEnemy(target, owner) and /*
*/ not IsUnitType(target, UNIT_TYPE_MECHANICAL) and /*
*/ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
// Damage dealt to enemy units
private constant function Damage takes integer lvl returns real
return 35. + 30*lvl
endfunction
// The % of dead unit's max life that will be used to heal the caster
private constant function HealPercent takes integer lvl returns real
return .05 + .05*lvl
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
globals
private unit TempCast // Stores caster unit for group enumeration
private integer TempCount // Used to count how many dead units are enumerated to determine rotation direction
endglobals
private struct Data
unit cast // Caster of the spell
integer lvl // Level of the spell when it was first cast
xefx missile // Displays the missile
real heal // Stores how much to heal the caster by
real ang // Stores the angle for the missile rotation and its facing
real dist // The distance between the dead unit & caster
real count = 0 // Counts how long the missile has lasted
real expire // Used to determine when the missile should reach the caster.
integer way // Determines how the missiles will rotate, clockwise or counter-clockwise
group hit // Ensures that units won't be damaged twice by the same missile
static thistype temp // For group enumeration
// Damages units that collide with the missile
static method damageUnits takes nothing returns boolean
local unit u = GetFilterUnit()
if not IsUnitInGroup(u, temp.hit) and AffectedTargets(u, GetOwningPlayer(temp.cast)) then
call DestroyEffect(AddSpecialEffectTarget(HIT_SFX, u, HIT_ATTACH))
call UnitDamageTarget(temp.cast, u, Damage(temp.lvl), false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
call GroupAddUnit(temp.hit, u)
endif
set u = null
return false
endmethod
// Moves the missile and damages nearby units
method periodic takes nothing returns nothing
local boolean dead = IsUnitType(.cast, UNIT_TYPE_DEAD) or GetUnitTypeId(.cast) == 0
if .count < .expire and not dead then
set .count = .count + T32_PERIOD
set .ang = .ang + .way * MISSILE_ROTATION * T32_PERIOD
set .missile.x = GetUnitX(.cast) + (.dist-.dist/.expire * .count)*Cos(.ang)
set .missile.y = GetUnitY(.cast) + (.dist-.dist/.expire * .count)*Sin(.ang)
set .missile.xyangle = .ang + bj_PI // Adds PI for correct facing
set temp = this
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .missile.x, .missile.y, MISSILE_AREA, Filter(function thistype.damageUnits))
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, .missile.x, .missile.y, MISSILE_AREA, Filter(function thistype.damageUnits))
endif
else
// Don't heal the caster if it is already dead
if not dead then
call SetWidgetLife(.cast, GetWidgetLife(.cast) + .heal)
call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, .cast, HEAL_ATTACH))
else
call DestroyEffect(AddSpecialEffect(HEAL_SFX, .missile.x, .missile.y))
endif
call .missile.destroy()
static if LIBRARY_GroupUtils then
call ReleaseGroup(.hit)
else
call GroupClear(.hit)
endif
call .stopPeriodic()
call .destroy()
endif
endmethod
implement T32x
// Creates the missile from the dead unit
static method createMissile takes unit c, unit u returns nothing
local thistype this = thistype.allocate()
local real cx = GetUnitX(c)
local real cy = GetUnitY(c)
local real ux = GetUnitX(u)
local real uy = GetUnitY(u)
local real dx = ux - cx
local real dy = uy - cy
set .cast = c
set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
set .heal = HealPercent(.lvl)*GetUnitState(u, UNIT_STATE_MAX_LIFE)
set .ang = Atan2(dy, dx)
set .dist = SquareRoot(dx * dx + dy * dy)
set .expire = .dist/Area(.lvl) * MAX_MISSILE_DUR
set .missile = xefx.create(ux, uy, .ang + bj_PI)
set .missile.fxpath = MISSILE_SFX
set .missile.z = MISSILE_HEIGHT
// Determines whether the missile will rotate clockwise or counter-clockwise
if ModuloInteger(TempCount, 2) == 0 then
set .way = -1
else
set .way = 1
endif
call DestroyEffect(AddSpecialEffect(DEAD_SFX, ux, uy))
call RemoveUnit(u) // To remove the corpse.
static if LIBRARY_GroupUtils then
set .hit = NewGroup()
else
if .hit == null then
set .hit = CreateGroup()
endif
endif
call .startPeriodic()
endmethod
// Enumerates dead units to make missiles
static method enumDeadUnits takes nothing returns boolean
if DeadTargets(GetFilterUnit()) then
call thistype.createMissile(TempCast, GetFilterUnit())
set TempCount = TempCount + 1
endif
return false
endmethod
// Spell actions
static method spellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
set TempCast = GetTriggerUnit()
set TempCount = 0
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(TempCast), GetUnitY(TempCast), Area(GetUnitAbilityLevel(TempCast,SPELL_ID)), Filter(function thistype.enumDeadUnits))
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(TempCast), GetUnitY(TempCast), Area(GetUnitAbilityLevel(TempCast,SPELL_ID)), Filter(function thistype.enumDeadUnits))
endif
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, Condition(function thistype.spellActions))
set t = null
static if PRELOAD then
call Preload(MISSILE_SFX)
call Preload(HIT_SFX)
call Preload(HEAL_SFX)
call Preload(DEAD_SFX)
endif
endmethod
endstruct
endscope