//==========================================================================================
// Wrath v1.02b by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - New Table http://www.hiveworkshop.com/forums/jass-functions-413/snippet-new-table-188084/
// - TimerUtils http://www.wc3c.net/showthread.php?t=101322
// - 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, Wrath
// 2. Copy the unit, Wrath (Dummy)
// 3. Implement the required libraries
// 4. Copy this trigger
// 5. Configure the spell
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
// - N/A
//==========================================================================================
scope Wrath
//==========================================================================================
// CONSTANTS
//==========================================================================================
globals
//------------------------------------------------------------------------------------------
// General spell settings
//------------------------------------------------------------------------------------------
private constant integer SPELL_ID = 'A000' // Raw id of the Wish ability
private constant integer DUMMY_ID = 'h000' // Raw id of the Wrath dummy unit
private constant real TIMER_LOOP = 0.03 // How often the timer loops, affects the smoothness of the spell
//------------------------------------------------------------------------------------------
// Wrath-specific settings
//------------------------------------------------------------------------------------------
private constant boolean RECYCLE_DUMMY = true // Determines whether or not to recylce the wrath unit; good idea if the spell is used frequently
private constant integer ANIM_SUMMON = 2 // Animation index the wrath plays while being summoned
private constant string ANIM_REPEAT = "attack" // Animation the wrath plays every ANIM_REPEAT_INTERVAL until MISSILE_TIME seconds has elapsed
private constant real ANIM_REPEAT_INT = 0.5 // Determines how often the wrath plays ANIM_REPEAT and when DAMAGE_TIME_SFX will be played
private constant string ANIM_FIN = "spell" // Animation the wrath plays when the spell deals the delayed damage
private constant real WRATH_EXPIRE = 1.4 // Determines how long the wrath will remain after playing ANIM_FIN
//------------------------------------------------------------------------------------------
// Missile-specfic settings
//------------------------------------------------------------------------------------------
private constant string MISSILE_SFX = "Abilities\\Weapons\\ZigguratMissile\\ZigguratMissile.mdl" // SFX path for the missile
private constant real MISSILE_ROTATE = bj_PI/2 // Determines how many radians the missiles will orbit per second
private constant integer MAX_MISSILES = 8 // The maximum number of missiles that would be created
//------------------------------------------------------------------------------------------
// Shared Settings for the Wrath and Missiles
//------------------------------------------------------------------------------------------
// Values with an INIT and FIN are affected by how long the spell was channeled for
// Vertex Colors
private constant integer RED = 255 // Red value
private constant integer GREEN = 255 // Green value
private constant integer BLUE = 255 // Blue value
private constant integer ALPHA_INIT = 50 // Initial alpha
private constant integer ALPHA_FIN = 150 // Final alpha
// Scaling
private constant real SCALE_INIT = 0.65 // Initial scaling
private constant real SCALE_FIN = 1.35 // Final scaling
// Height
private constant real HEIGHT_INIT = 50. // Initial height
private constant real HEIGHT_FIN = 200. // Final height
//------------------------------------------------------------------------------------------
// Other SFX settings
//------------------------------------------------------------------------------------------
private constant boolean PRELOAD = true // Determines if the spell should preload the sfxs and Wrath dummy unit
private constant string DMG_SFX = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl" //The sfx that will be played on units damaged by the Delayed damage of the spell
private constant string DMG_ATTACH = "chest" // Attachment point for DMG_SFX
private constant string DMG_TIME_SFX = "Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayDamage.mdl" //The sfx that will play on units affected by the DoT filter. Note that this sfx will be played as often as MISSILE_TIME_LOOP
private constant string DMG_TIME_ATTACH = "origin" //Attachment point for DMG_TIME_SFX
private constant string POINT_SFX = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" //The sfx that will be played on the wrath unit
//------------------------------------------------------------------------------------------
// Damage Settings
//------------------------------------------------------------------------------------------
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPN_TYPE = null
//------------------------------------------------------------------------------------------
// Texttag settings
//------------------------------------------------------------------------------------------
private constant real FONT_SIZE = .038 // Font size for the texttag
private constant real TEXT_VELOCITY = 0.04 // Velocity for texttag
private constant real TEXT_FADE = 1.5 // Fading age for texttag
private constant real TEXT_LIFE = 2. // Lifespan for texttag
// Vertex color
private constant integer TEXT_R = 150 // Red value
private constant integer TEXT_G = 0 // Green value
private constant integer TEXT_B = 255 // Blue value
private constant integer TEXT_A = 255 // Alpha value
endglobals
//==========================================================================================
// OTHER CONFIGURATION
//==========================================================================================
// The target area of the spell
private constant function Area takes integer lvl returns real
return 225. + 75*lvl
endfunction
// Determines which units are 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_MAGIC_IMMUNE) and /*
*/ not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
// The casting time for the spell
private constant function CastTime takes integer lvl returns real
return 3.
endfunction
// The number of missiles that will orbit around the wrath; change MAX_MISSILES accordingly
private constant function MissileNumber takes integer lvl returns integer
return 8
endfunction
// How long it should take for the missile to reach the wrath
private constant function MissileTime takes integer lvl returns real
return 2.5
endfunction
// Damage done to units every second for MissileTime seconds
private constant function PeriodicDamage takes integer lvl returns real
return 10. + 10*lvl
endfunction
// Damage done to enemy units MissileTime seconds after the wrath has been summoned
private constant function DelayedDamage takes integer lvl returns real
return 100. + 75*lvl
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
globals
private Table info // Passes info for spell channeling purposes
endglobals
private struct Data
unit cast // Caster of the spell
unit wrath // Wrath dummy unit
integer lvl // The level of the ability of the caster when it was casted
integer missileNum // Number of missiles created
real x // The x of the spell target point
real y // The y of the spell target point
real area // spell target area
real missileTime // How long the missiles will take to ascend
real percent // Stores ratio of how long the wrath was summoned to CastTime
real count = 0 // Count duration for the spell
real animCount = 0 // A more specific count that determines when the wrath should play ANIM_REPEAT
real ang = 0 // Angle for the missiles to rotate around the wrath
timer t // Main timer used by the spell to carry out actions
xefx array fx[MAX_MISSILES] // Stores missiles created
static thistype temp // Passes information from a struct to the filter method
static real Total // Stores total amount of damage done by the delayed damage for the texttag
// Cleans up the final parts of the spell
static method timedDestroy takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
static if RECYCLE_DUMMY then
call ShowUnit(.wrath, false)
else
call RemoveUnit(.wrath)
endif
call ReleaseTimer(.t)
call .destroy()
endmethod
// Damages units by the delayed portion of the spell
static method dealDelayedDmg takes nothing returns boolean
local unit u = GetFilterUnit()
local real life
if AffectedTargets(u, GetOwningPlayer(temp.cast)) then
set life = GetWidgetLife(u)
call DestroyEffect(AddSpecialEffectTarget(DMG_SFX, u, DMG_ATTACH))
call UnitDamageTarget(temp.cast, u, DelayedDamage(temp.lvl)*temp.percent, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
set Total = Total + life - GetWidgetLife(u)
endif
set u = null
return false
endmethod
// Damages units in the periodic portion of the spell
static method dealPeriodicDamage takes nothing returns boolean
local unit u = GetFilterUnit()
if AffectedTargets(u, GetOwningPlayer(temp.cast)) then
// Play sfx about the same time the wrath plays its animation
if temp.animCount == 0 then
call DestroyEffect(AddSpecialEffectTarget(DMG_TIME_SFX, u, DMG_TIME_ATTACH))
endif
call UnitDamageTarget(temp.cast, u, PeriodicDamage(temp.lvl)*temp.percent*TIMER_LOOP, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
endif
set u = null
return false
endmethod
// Takes care of most of the spell's bulk:
// - playing the wrath's animation
// - setting the missiles
// - dealing damage over time
// - dealing delayed damage
static method onEffectLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer i = 0
local real offset
local texttag t
set temp = this // For group enumeration
if .count + TIMER_LOOP < .missileTime then
set .count = .count + TIMER_LOOP
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .x, .y, .area, Condition(function thistype.dealPeriodicDamage))
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, .x, .y, .area, Condition(function thistype.dealPeriodicDamage))
endif
// Done this way so wrath will play its animation immediately after being summoned
if .animCount == 0 then
call SetUnitAnimation(.wrath,ANIM_REPEAT)
endif
set .animCount = .animCount + TIMER_LOOP
if .animCount >= ANIM_REPEAT_INT then
set .animCount = 0
endif
// Missile movement
set .ang = .ang + MISSILE_ROTATE * TIMER_LOOP
set offset = .area - .area / .missileTime * .count
loop
set .fx[i].x = .x + offset * Cos(.ang + bj_PI * 2 / .missileNum * i)
set .fx[i].y = .y + offset * Sin(.ang + bj_PI * 2 / .missileNum * i)
set .fx[i].z = (HEIGHT_INIT + (HEIGHT_FIN - HEIGHT_INIT) * .percent) * .count / .missileTime
set i = i + 1
exitwhen i == MissileNumber(.lvl)
endloop
else
call DestroyEffect(AddSpecialEffect(POINT_SFX, .x, .y))
set Total = 0
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .x, .y, .area, Condition(function thistype.dealDelayedDmg))
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, .x, .y, .area, Condition(function thistype.dealDelayedDmg))
endif
// Create a texttag if damage was dealt
if Total > 0 then
set t = CreateTextTag()
call SetTextTagPos(t, .x, .y, HEIGHT_INIT + (HEIGHT_FIN - HEIGHT_INIT) * .percent)
call SetTextTagText(t, "-"+I2S(R2I(Total))+"!", FONT_SIZE)
call SetTextTagColor(t, TEXT_R, TEXT_G, TEXT_B, TEXT_A)
call SetTextTagVelocity(t, 0, TEXT_VELOCITY)
call SetTextTagFadepoint(t, TEXT_FADE)
call SetTextTagLifespan(t, TEXT_LIFE)
call SetTextTagPermanent(t, false)
set t = null
endif
// Clean up the missiles
loop
call .fx[i].destroy()
set i = i + 1
exitwhen i == .missileNum
endloop
// Allow wrath to play ANIM_FIN before getting removed
call SetUnitAnimation(.wrath, ANIM_FIN)
call TimerStart(.t, WRATH_EXPIRE, false, function thistype.timedDestroy)
endif
endmethod
// Takes effect when the caster stops channeling the spell for any reason and then starts the effects
static method spellStop takes nothing returns boolean
local thistype this
local integer i = 0
local integer alpha
local real mx
local real my
local real scale
if GetSpellAbilityId() == SPELL_ID then
set this = info[GetHandleId(GetTriggerUnit())]
set .percent = .count / CastTime(.lvl)
// For precision, since Jass is stupid
if .percent == 1. then
set .percent = 1
endif
set alpha = R2I(ALPHA_INIT + (ALPHA_FIN-ALPHA_INIT)*.percent)
set scale = SCALE_INIT + (SCALE_FIN-SCALE_INIT) * .percent
loop
set mx = .x + .area*Cos(bj_PI * 2 / missileNum*i)
set my = .y + .area*Sin(bj_PI * 2 / missileNum*i)
set .fx[i] = xefx.create(mx, my, 0)
set .fx[i].fxpath = MISSILE_SFX
call .fx[i].recolor(RED, GREEN, BLUE, alpha)
set .fx[i].scale = scale
set i = i + 1
exitwhen i == .missileNum
endloop
set .count = 0
call TimerStart(.t, TIMER_LOOP, true, function thistype.onEffectLoop)
call info.remove(GetHandleId(.cast)) // No longer need to store this info since spell channeling is done
endif
return false
endmethod
// Sets the visuals of the wrath as it's being summoned
static method onChannel takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
set .count = .count + TIMER_LOOP
set .percent = .count / CastTime(.lvl)
call SetUnitScale(.wrath, SCALE_INIT +(SCALE_FIN-SCALE_INIT) * .percent, 0, 0)
call SetUnitVertexColor(.wrath, RED, GREEN, BLUE, R2I(ALPHA_INIT + (ALPHA_FIN-ALPHA_INIT) * .percent))
call SetUnitFlyHeight(.wrath, HEIGHT_INIT + (HEIGHT_FIN-HEIGHT_INIT) * .percent, 0)
endmethod
static method startSpell takes unit c, real x, real y returns nothing
local thistype this = thistype.allocate()
local real ang
set .cast = c
set .x = x
set .y = y
set .lvl = GetUnitAbilityLevel(.cast, SPELL_ID)
// Variables that are set as they're used a lot in calculations
set .area = Area(.lvl)
set .missileNum = MissileNumber(.lvl)
set .missileTime = MissileTime(.lvl)
// Ensures that the ghost will have a proper facing angle if the spell is cast directly on the caster
if y - GetUnitY(c) > 0 and x - GetUnitX(c) > 0 then
set ang = bj_RADTODEG*Atan2(y - GetUnitY(c), x - GetUnitX(c))
else
set ang = GetUnitFacing(c)
endif
// Wrath settings
static if RECYCLE_DUMMY then
if .wrath == null then
set .wrath = CreateUnit(Player(15), DUMMY_ID, x, y, ang)
else
call SetUnitX(.wrath, x)
call SetUnitY(.wrath, y)
call SetUnitFacing(.wrath, ang)
call ShowUnit(.wrath, true)
call UnitRemoveAbility(.wrath, 'Aloc')
call UnitAddAbility(.wrath, 'Aloc')
endif
else
set .wrath = CreateUnit(Player(15), DUMMY_ID, x, y, ang)
endif
call SetUnitScale(.wrath, SCALE_INIT, 0, 0)
call SetUnitVertexColor(.wrath, RED, GREEN, BLUE, ALPHA_INIT)
call SetUnitFlyHeight(.wrath, HEIGHT_INIT, 0)
call SetUnitAnimationByIndex(.wrath, ANIM_SUMMON)
set info[GetHandleId(.cast)] = this
set .t = NewTimer()
call SetTimerData(.t, this)
call TimerStart(.t, TIMER_LOOP, true, function thistype.onChannel)
endmethod
static method spellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call thistype.startSpell(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, Condition(function thistype.spellActions))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t, Condition(function thistype.spellStop))
set info = Table.create()
// Preloading
static if PRELOAD then
call RemoveUnit(CreateUnit(Player(15),DUMMY_ID,0,0,0))
call Preload(MISSILE_SFX)
call Preload(DMG_SFX)
call Preload(DMG_TIME_SFX)
call Preload(POINT_SFX)
endif
set t = null
endmethod
endstruct
endscope