// Elune's arrow by Ruke
// ---------------------
//
// REQUIRES: TimerUtils -> [url]http://www.hiveworkshop.com/forums/submissions-414/system-timerutils-204500/[/url]
// REQUIRES: Timer32 -> [url]http://www.thehelper.net/forums/showthread.php/132538-Timer32[/url]
//
// The spell does what?
// --------------------
// Fires an arrow to a location with deadly precision, dealing large damage and stunning
// the first unit it strikes. Stun duration increases based on how far the target
// is, ranging from 0.5 to 5 seconds. Has 3000 range.
// More information: [url]http://www.playdota.com/heroes/priestess-of-the-moon#skill178[/url]
//
// How to import?
// --------------
// 1 ) Copy this trigger
// 2 ) Copy the dummy's unit (Object Editor -> Unit)
// 3 ) Copy Stun Elune (Object Editor -> Spell)
// 4 ) Copy Elune's Arrow (Object Editor -> Spell)
library EluneIsArrow initializer Init requires TimerUtils
//==========================================================================================//
// CONFIGURABLE //
//==========================================================================================//
globals
private constant integer DUMMY = 'h003' // Stun's dummy
private constant integer DUMMY_ARROW = 'h002' // Arrow's dummy
private constant integer STUN_RAWCODE = 'A002' // Stun's spell
private constant integer STUN_BUFF = 'BPSE' // Stun's buff
private constant integer SPELL_RAWCODE = 'A001' // Spell's rawcode
private constant damagetype DMG_TYPE = DAMAGE_TYPE_MAGIC // Damage type
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL // Attack type
private constant boolean AFFECT_INVIS = false // Affects the stun to invisible units?
endglobals
// Returns the spell's damage
private constant function GetDamage takes integer level returns real
return 90. * level
endfunction
// Returns the max distance that the arrow can cover
// cycles * 100 to get distance (as you can see, 100 cycles = 3000 max distance)
private constant function GetCycles takes nothing returns integer
return 100
endfunction
// Arrow's detection range
private constant function GetArrowDetect takes nothing returns real
return 115.
endfunction
// Returns the stun's max duration
private constant function GetStunMaxDuration takes integer level returns integer
return 5 * level
endfunction
// Returns the units that can be affected by the spell
private function GetAffectedUnits takes unit caster, unit target returns boolean
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_STRUCTURE))
endfunction
//==========================================================================================//
// CONFIGURABLE's END //
//==========================================================================================//
globals
private constant group G = CreateGroup()
endglobals
private struct Spell
timer t // Stun's timer
unit caster // Caster
unit target // Stun's target
unit dummy // Dummy's arrow
integer level // Spell's level
real a // Dummy's angle
integer cycles // Arrow's distance -> To get the distance: cycles * 30 (30 is how much the arrow's dummy moves in every period)
static thistype data // For filter
method destroy takes nothing returns nothing
call this.stopPeriodic()
// Killing the arrow's dummy
call KillUnit(this.dummy)
set this.dummy = null
// If this.target is not null, means that we need
// start another timer with the stun's duration.
// So, when thistype.stun' run, we will remove
// the stun to the target's unit
if this.target != null then
set this.t = NewTimer()
call SetTimerData(this.t, this)
call TimerStart(this.t, ((this.cycles * 30) / 150) * 0.5, false, function thistype.stun)
else
call PauseTimer(this.t)
call ReleaseTimer(this.t)
set this.t = null
set this.caster = null
set this.target = null
call this.deallocate()
endif
endmethod
static method stun takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call UnitRemoveAbility(this.target, STUN_BUFF)
set this.target = null
call this.destroy()
endmethod
static method filter takes nothing returns boolean
local unit u = GetFilterUnit()
local unit dummy
if GetAffectedUnits(data.caster, u) then
// The stun can only stay for STUN_M_DURATION seconds
if (((data.cycles * 30) / 150) * 0.5) > GetStunMaxDuration(data.level) then
set data.cycles = GetCycles() / 2
endif
set data.target = u
// Applying stun to target's unit
if IsUnitInvisible(u, GetOwningPlayer(u)) then
static if AFFECT_INVIS then
set dummy = CreateUnit(GetOwningPlayer(data.caster), DUMMY, GetUnitX(u), GetUnitY(u), 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1.)
call UnitAddAbility(dummy, STUN_RAWCODE)
call IssueTargetOrder(dummy, "thunderbolt", u)
endif
else
set dummy = CreateUnit(GetOwningPlayer(data.caster), DUMMY, GetUnitX(u), GetUnitY(u), 0)
call UnitApplyTimedLife(dummy, 'BTLF', 1.)
call UnitAddAbility(dummy, STUN_RAWCODE)
call IssueTargetOrder(dummy, "thunderbolt", u)
endif
// Making damage...
call UnitDamageTarget(data.caster, u, GetDamage(data.level), false, true, ATK_TYPE, DMG_TYPE, null)
endif
set u = null
set dummy = null
return false
endmethod
private method periodic takes nothing returns nothing
//local thistype this = GetTimerData(GetExpiredTimer())
// Moving the dummy's arrow
call SetUnitPosition(this.dummy, GetUnitX(this.dummy) + 30. * Cos(this.a), GetUnitY(this.dummy) + 30. * Sin(this.a))
set this.cycles = this.cycles + 1
// Checking if the arrow has already covered the max distance
if this.cycles < GetCycles() then
// If a unit is in the ARROW_DETECT range...
if IsUnitInRangeXY(this.dummy, GetUnitX(this.dummy), GetUnitY(this.dummy), GetArrowDetect()) then
set data = this
call GroupEnumUnitsInRange(G, GetUnitX(this.dummy), GetUnitY(this.dummy), GetArrowDetect(), Condition(function thistype.filter))
set this.target = data.target
set this.cycles = data.cycles
// If we've a target then...
if this.target != null then
// Invisible's check...
if AFFECT_INVIS then
set this.target = null
endif
call this.destroy()
endif
endif
else
call this.destroy()
endif
endmethod
implement T32x
static method create takes unit caster, real a, integer level returns thistype
local thistype this = thistype.allocate()
set this.caster = caster
set this.dummy = CreateUnit(GetOwningPlayer(caster), DUMMY_ARROW, GetUnitX(caster), GetUnitY(caster), a * bj_RADTODEG)
set this.level = level
set this.a = a
set this.cycles = 0
return this
endmethod
endstruct
private function Conditions takes nothing returns boolean
local unit caster
local real x
local real y
local real a
local integer level
if (GetSpellAbilityId() == SPELL_RAWCODE) then
set caster = GetTriggerUnit()
set x = GetSpellTargetX()
set y = GetSpellTargetY()
// Angle for the dummy's arrow
set a = Atan2(y - GetUnitY(caster), x - GetUnitX(caster))
set level = GetUnitAbilityLevel(caster, SPELL_RAWCODE)
call Spell.create(caster, a, level).startPeriodic()
endif
set caster = null
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local unit u
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function Conditions))
// Preloading ability and dummy unit
set u = CreateUnit(Player(0), DUMMY, 0, 0, 0)
call UnitAddAbility(u, STUN_RAWCODE)
call RemoveUnit(u)
set t = null
set u = null
endfunction
endlibrary