//==========================================================================================
// Tainted Fountain v1.02a by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - TimerUtils
// - SoundUtils
// * GroupUtils
//##########################################################################################
// Importing:
// 1. Copy the ability, Tainted Fountain.
// 2. Copy the unit, Tainted Fountain
// 3. Implement the required libraries.
// 4. Copy this trigger.
// 5. Configure the spell.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
// - The spell will target wards if they aren't mechanical
// - The spell will bug if the fountain gets moved from where it is placed
//==========================================================================================
scope TaintedFountain initializer Init
native UnitAlive takes unit id returns boolean // Don't touch this
//==========================================================================================
// CONSTANTS
//==========================================================================================
globals
// General Spell Settings
private constant integer SPELL_ID = 'A000' // Raw id of the spell.
private constant integer FOUNTAIN_ID = 'o000' // Raw id of the Tainted Fountain unit
private constant integer FOUNTAIN_ANIM = 2 // The animation index the fountain will play
private constant integer BUFF_ID = 'BTLF' // Buff given to the fountain for expiration timer
private constant real TIMER_LOOP = 0.1 // Determines how often the timer will loop
private constant real FOUNTAIN_FACE = 270 // Determines the facing angle of hte fountain
// Damage settings
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPN_TYPE = null
// Special Effect Settings
private constant boolean PRELOAD = true // Determines if the effects and fountain unit should be preloaded or not.
private constant string HEAL_SFX = "Abilities\\Spells\\Other\\Drain\\DrainCaster.mdl" // The effect that is attached to units healing from the spell
private constant string HEAL_ATTACH = "origin" // Attachment point for HEAL_SFX
private constant string DRAIN_SFX = "Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl" // The effect that is attached to units getting drained from the spell
private constant string DRAIN_ATTACH = "origin" // Attachment point for DRAIN_SFX
private constant string EXCESS_SFX = "Abilities\\Spells\\Undead\\DarkRitual\\DarkRitualTarget.mdl" // The sfx that will be played on the fountain when it has too much mana.
private constant string EXCESS_ATTACH = "origin" // Attachment point for EXCESS_SFX
private constant real DELAY_DUR = 1.5 // Duration before EXCESS_SFX will play again
private constant string LIGHTNING = "DRAL" // Lightning path
private constant real OFFSET = 0. // The offset that lightning will be created.
// Vertex color for lightning. Note that they are expressed in decimals.
private constant real RED = 1
private constant real GREEN = 1
private constant real BLUE = 1
private constant real ALPHA = 1
// Sound settings:
private constant string SOUND_PATH = "Abilities\\Spells\\Other\\ANrl\\FountainOfLifeLoop1.wav" // The sound path that will be looped
private constant integer SOUND_DURATION = 3315 // The duration of the sound
endglobals
//==========================================================================================
// CONFIGURATIONS
//==========================================================================================
// The spell's area of effect
private constant function Area takes integer lvl returns real
return 500.
endfunction
// Determines how long a fountain will last
private constant function Duration takes integer lvl returns real
return 30.
endfunction
// Damage done to enemy units per second
private constant function Damage takes integer lvl returns real
return 10.
endfunction
// Determines how much mana the fountain will gain from one point of damage
private constant function DamageRate takes integer lvl returns real
return 1.5
endfunction
// Determines how much a unit can be healed per second
private constant function Heal takes integer lvl returns real
return 10. + 10*lvl
endfunction
// Determines how much life the fountain can heal from one mana point
private constant function ManaRate takes integer lvl returns real
return 0.5 + 0.25*lvl
endfunction
// Determines how much damage the fountain will deal from excess mana
private constant function ExcessManaRate takes integer lvl returns real
return 0.25
endfunction
// Targets that are affected by the fountain, both healing and damaging
private function AffectedTargets takes unit u, player owner returns boolean
// General settings
return UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and /*
// Settings to damage enemy units
*/ (IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
// Settings to heal allied units
*/ (IsUnitAlly(u, owner) and GetWidgetLife(u) < GetUnitState(u, UNIT_STATE_MAX_LIFE))
endfunction
// Targets that get hurt by excess mana damage
private function ExcessManaAffectedTargets takes unit u, player owner returns boolean
return UnitAlive(u) and IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
private keyword Fountain
globals
private location l = Location(0, 0)
private group g // Used if GroupUtils isn't present
endglobals
private function GetPointZ takes real x, real y returns real
call MoveLocation(l, x, y)
return GetLocationZ(l)
endfunction
// Main struct that affects the target units
private struct Target
unit targ
Fountain f // Gets info from fountain
real delay = 0 // The count that will determine when to play the EXCESS_SFX
effect sfx = null // Effect attached to target unit.
string path = DRAIN_SFX // The path for the sfx. Changes if the targ unit is an ally or an enemy.
string attach = DRAIN_ATTACH // The attachment point for the sfx. Changes if the targ unit is an ally or an enemy.
lightning light // Lightning that will be attached to the targ unit and fount unit.
boolean show = false // Determines when to show the special effects.
timer t
static thistype temp // Used to pass data for group enumerations
static real TempReal // Used to pass the excess mana damage
static method damageUnits takes nothing returns boolean
local unit u = GetFilterUnit()
if ExcessManaAffectedTargets(u, GetOwningPlayer(temp.f.u)) then
call UnitDamageTarget(temp.f.u, u, TempReal, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
endif
set u = null
return false
endmethod
method showEffects takes nothing returns nothing
set .sfx = AddSpecialEffectTarget(.path, .targ, .attach)
call SetLightningColor(.light, RED, GREEN, BLUE, ALPHA)
set .show = true
endmethod
method hideEffects takes nothing returns nothing
call DestroyEffect(.sfx)
set .sfx = null
call SetLightningColor(.light, RED, GREEN, BLUE, 0)
set .show = false
endmethod
// Method deals with the effects for any targets affected by the spell
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real tx
local real ty
local real tz
local real life
local real mana
local real max
local real need // Mana needed for healing
local real gain
if UnitAlive(.targ) and UnitAlive(.f.u) then
if IsUnitInRange(.targ, .f.u, Area(.f.lvl)) then
// Only set variables if the target is in range
// Target unit settings
set tx = GetUnitX(.targ)
set ty = GetUnitY(.targ)
set tz = GetUnitFlyHeight(.targ) + GetPointZ(tx, ty) + OFFSET
set life = GetWidgetLife(.targ)
// Fountain unit settings
set mana = GetUnitState(.f.u, UNIT_STATE_MANA)
set max = GetUnitState(.f.u, UNIT_STATE_MAX_MANA)
// Healing effects here
if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
set need = Heal(.f.lvl) / ManaRate(.f.lvl) * TIMER_LOOP
// Don't do anything if the fountain does not have any mana or the target has full health
if mana > 0.405 and life < GetUnitState(.targ, UNIT_STATE_MAX_LIFE) then
if mana - need > 0 then
// Has enough mana, don't worry about healing over
call SetWidgetLife(.targ, life + Heal(.f.lvl) * TIMER_LOOP)
else
// Can only heal with the mana that the fountain has
call SetWidgetLife(.targ, life + Heal(.f.lvl) * mana / need * TIMER_LOOP)
endif
// Reduces mana based on how much the target was healed by
call SetUnitState(.f.u, UNIT_STATE_MANA, mana - (GetWidgetLife(.targ)-life) / ManaRate(.f.lvl))
call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)
if not .show then
call .showEffects()
endif
// Don't show effects if the unit doesn't need healing
elseif .show then
call .hideEffects()
endif
else // Damaging effects here
call UnitDamageTarget(.f.u, .targ, Damage(.f.lvl)*TIMER_LOOP, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
set gain = DamageRate(.f.lvl)*(life-GetWidgetLife(.targ))
if mana + gain < max then
// No excess mana, give mana normally to the fountain
call SetUnitState(.f.u, UNIT_STATE_MANA, mana+gain)
else
// Damage enemy units with excess mana damage
if mana < max then
call SetUnitState(.f.u, UNIT_STATE_MANA, max)
endif
set temp = this
set TempReal = ExcessManaRate(.f.lvl) * (mana + gain - max) // Stores the damage to be dealt
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
else
call GroupEnumUnitsInRange(g, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
endif
// This tells the spell when to start counting for delay between playing EXCESS_SFX
if .delay == 0 then
call DestroyEffect(AddSpecialEffectTarget(EXCESS_SFX, .f.u, EXCESS_ATTACH))
set .delay = .01
endif
endif
call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)
if not .show then
call .showEffects()
endif
// Done to prevent EXCESS_SFX from playing too often.
if .delay > 0 then
if .delay == .01 then
set .delay = TIMER_LOOP
else
set .delay = .delay + TIMER_LOOP
endif
endif
if .delay > DELAY_DUR then
set .delay = 0
endif
endif
// Hide effects if they are shown and if the target is out of range
elseif .show then
call .hideEffects()
endif
else
// This is done in case the target revives so that the spell will properly target it again
if not UnitAlive(.targ) then
call GroupRemoveUnit(.f.affect, .targ)
endif
if .sfx != null then
call DestroyEffect(.sfx)
endif
call DestroyLightning(.light)
call ReleaseTimer(.t)
call .destroy()
endif
endmethod
static method create takes unit t, Fountain f returns thistype
local thistype this = thistype.allocate()
set .targ = t
set .f = f
call GroupAddUnit(f.affect, t)
// Create the ligtning and hides it
set .light = AddLightning(LIGHTNING, true, f.x, f.y, GetUnitX(t), GetUnitY(t))
call SetLightningColor(.light, RED, GREEN, BLUE, 0)
if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
set .path = HEAL_SFX
set .attach = HEAL_ATTACH
endif
set .t = NewTimer()
call SetTimerData(.t, this)
call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)
return this
endmethod
endstruct
private struct Fountain
unit u // Refers to the fountain unit that is created
integer lvl
real x
real y
real z
group affect // Store all the units currently affected by the spell.
sound sndLoop // Sound that loops when fountain is alive
timer t // Timer used to do looping actions by the spell
private static thistype temp // Used to pass data for group enumerations
// Creates the Target struct for affected units
static method filter takes nothing returns boolean
local unit u = GetFilterUnit()
if not IsUnitInGroup(u, temp.affect) and AffectedTargets(u, GetOwningPlayer(temp.u)) then
call Target.create(u, temp)
endif
set u = null
return false
endmethod
// Periodic method to enumerate units in range of the fountain
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if UnitAlive(.u) then
set temp = this
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .x, .y, Area(.lvl), Condition(function thistype.filter))
else
call GroupEnumUnitsInRange(g, .x, .y, Area(.lvl), Condition(function thistype.filter))
endif
else // Fountain is dead, clean up struct stuff
static if LIBRARY_GroupUtils then
call ReleaseGroup(.affect)
endif
call ReleaseSound(.sndLoop)
call ReleaseTimer(.t)
call .destroy()
endif
endmethod
static method create takes unit c, real x, real y returns thistype
local thistype this = thistype.allocate()
set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
static if LIBRARY_GroupUtils then
set .affect = NewGroup()
else
if .affect == null then
set .affect = CreateGroup()
else
call GroupClear(.affect)
endif
endif
// Fountain settings
set .u = CreateUnit(GetOwningPlayer(c), FOUNTAIN_ID, x, y, FOUNTAIN_FACE)
call UnitAddType(.u, UNIT_TYPE_SUMMONED)
call SetUnitAnimationByIndex(.u, FOUNTAIN_ANIM)
set .x = GetUnitX(.u)
set .y = GetUnitY(.u)
set .z = GetUnitFlyHeight(.u) + GetPointZ(.x, .y) + OFFSET
call UnitApplyTimedLife(.u ,BUFF_ID, Duration(.lvl))
// Sound creation
set .sndLoop = RunSoundAtPoint(DefineSound(SOUND_PATH, SOUND_DURATION, true, true), .x, .y, 0)
// Timer settings
set .t = NewTimer()
call SetTimerData(.t,this)
call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)
return this
endmethod
endstruct
private function SpellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Fountain.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
return false
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 SpellActions))
static if not LIBRARY_GroupUtils then
set g = CreateGroup()
endif
static if PRELOAD then
call Preload(HEAL_SFX)
call Preload(DRAIN_SFX)
call Preload(EXCESS_SFX)
call Preload(LIGHTNING)
call RemoveUnit(CreateUnit(Player(15),FOUNTAIN_ID,0,0,0))
endif
set t = null
endfunction
endscope