library ShadowStep uses SpellEffectEvent, UnitIndexer
//===========================================================================
// CONFIGURABLES
//===========================================================================
globals
private constant integer ABILID = 'ABSS' // raw code of ability "Shadow Step"
private constant integer DUMABILID1 = 'ASS1' // raw code of ability "Shadow Swap"
private constant integer DUMABILID2 = 'ASS0' // raw code of ability "Shadow Step (Illusion)"
private constant integer CASTID = 'cAST' // raw code of unit "Caster Dummy"
private constant real OFFSET = 100. // offset distance from the target
private constant real PERIOD = 0.1 // update interval for shadow fade and illusion timer
private constant real SHADOW_DIST = 50. // distance interval where a shadow is created
private constant real TRANS = 150. // 0 = completely transparent, maximum of 255
private constant real FADE_DUR = 1. // time for shadows to fade
private constant real FADE_RATE = TRANS * PERIOD / FADE_DUR // rate of fading
private constant real TURN_RATE = 0.6 // caster's turn rate
private constant string ANIM = "slam" // animation to be played by shadows
private constant real ANIM_TIME = 0.5665 // animation time for ANIM
private constant real ANIM_TIME_SCALE = ANIM_TIME / FADE_DUR // time scale for shadows
private constant attacktype ATK = ATTACK_TYPE_HERO // attack type of damage
private constant damagetype DMG = DAMAGE_TYPE_NORMAL // damage type of damage
endglobals
// damage dealt
private constant function GetDamage takes integer level returns real
return 25. * level + 15.
endfunction
// duration of illusion
private constant function GetDuration takes integer level returns real
return 5.
endfunction
//===========================================================================
// END CONFIGURABLES
//===========================================================================
globals
private constant player NEUTRAL_PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
endglobals
private struct Fade
thistype next
thistype prev
unit u
real fade = TRANS
static timer linkTimer = CreateTimer()
private static method iterate takes nothing returns nothing
local thistype this = thistype(0)
loop
set this = this.next
exitwhen this == 0
if this.fade <= 0 then
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == thistype(0) then
call PauseTimer(linkTimer)
endif
call RemoveUnit(this.u)
call this.deallocate()
else
set this.fade = this.fade - FADE_RATE
call SetUnitVertexColor(this.u, 255, 255, 255, R2I(this.fade))
endif
endloop
endmethod
public static method create takes player p, integer typeid, real x, real y, real a returns thistype
local thistype this = thistype.allocate()
if thistype(0).next == thistype(0) then
call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
endif
set thistype(0).next.prev = this
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
set this.u = CreateUnit(NEUTRAL_PASSIVE, typeid, x, y, a * bj_RADTODEG)
call SetUnitColor(this.u, GetPlayerColor(p))
call UnitAddAbility(this.u, 'Aloc')
call SetUnitTimeScale(this.u, ANIM_TIME_SCALE)
call SetUnitAnimation(this.u, ANIM)
call SetUnitX(this.u, x)
call SetUnitY(this.u, y)
return this
endmethod
endstruct
private struct Data
thistype next
thistype prev
unit u
unit illu
unit target
boolean b = true
real dur
real a
real x
real y
static thistype tempData
static unit castDummy
static integer array store
static timer linkTimer = CreateTimer()
private static method iterate takes nothing returns nothing
local thistype this = thistype(0)
loop
set this = this.next
exitwhen this == 0
if this.dur <= 0 or IsUnitType(this.illu, UNIT_TYPE_DEAD) then
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == thistype(0) then
call PauseTimer(linkTimer)
endif
if this.b then
call UnitRemoveAbility(this.u, DUMABILID1)
call SetPlayerAbilityAvailable(GetOwningPlayer(this.u), ABILID, true)
endif
call this.deallocate()
else
set this.dur = this.dur - PERIOD
endif
endloop
endmethod
private static method swap takes nothing returns boolean
local thistype this = store[GetUnitId(GetTriggerUnit())]
local real a = GetUnitFacing(this.u)
local real x = GetUnitX(this.u)
local real y = GetUnitY(this.u)
local real b = GetUnitFacing(this.illu)
local real dx = GetUnitX(this.illu)
local real dy = GetUnitY(this.illu)
set this.b = false
call SetUnitPathing(this.u, false)
call SetUnitTurnSpeed(this.u, 99999)
call SetUnitTurnSpeed(this.illu, 99999)
call SetUnitX(this.u, dx)
call SetUnitY(this.u, dy)
call SetUnitFacing(this.u, b)
call IssueImmediateOrder(this.u, "stop")
call SetUnitX(this.illu, x)
call SetUnitY(this.illu, y)
call SetUnitFacing(this.illu, a)
call SetUnitTurnSpeed(this.u, TURN_RATE)
call SetUnitTurnSpeed(this.illu, TURN_RATE)
if IsUnitType(this.target, UNIT_TYPE_DEAD) then
call IssueImmediateOrder(this.illu, "stop")
else
call IssueTargetOrder(this.illu, "attack", this.target)
endif
call SetUnitPathing(this.u, true)
call UnitRemoveAbility(this.u, DUMABILID1)
call SetPlayerAbilityAvailable(GetOwningPlayer(this.u), ABILID, true)
return false
endmethod
private static method onSummon takes nothing returns boolean
if thistype(0).next == thistype(0) then
call TimerStart(linkTimer, PERIOD, true, function thistype.iterate)
endif
set thistype(0).next.prev = tempData
set tempData.next = thistype(0).next
set thistype(0).next = tempData
set tempData.prev = thistype(0)
set tempData.illu = GetSummonedUnit()
call SetUnitX(tempData.illu, tempData.x)
call SetUnitY(tempData.illu, tempData.y)
call SetUnitFacing(tempData.illu, tempData.a * bj_RADTODEG)
call UnitApplyTimedLife(tempData.illu, 'BTLF', tempData.dur)
return false
endmethod
private static method onCast takes nothing returns boolean
local thistype this = thistype.allocate()
local player p
local integer i
local integer typeid
local real dx
local real dy
local real dist
local real damage
set this.u = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set i = GetUnitAbilityLevel(this.u, ABILID)
set this.dur = GetDuration(i)
set damage = GetDamage(i)
set store[GetUnitId(this.u)] = this
set p = GetOwningPlayer(this.u)
set typeid = GetUnitTypeId(this.u)
set this.x = GetUnitX(this.u)
set this.y = GetUnitY(this.u)
set dx = GetUnitX(this.target) - this.x
set dy = GetUnitY(this.target) - this.y
set this.a = Atan2(dy, dx)
set dist = SquareRoot(dx * dx + dy * dy) - OFFSET
set i = R2I(dist / SHADOW_DIST)
loop
exitwhen i == 0
call Fade.create(p, typeid, this.x + SHADOW_DIST * i * Cos(this.a), this.y + SHADOW_DIST * i * Sin(this.a), this.a)
set i = i - 1
endloop
set tempData = this
call SetUnitOwner(castDummy, p, false)
call IssueTargetOrderById(castDummy, 852274, this.u)
call SetUnitX(this.u, this.x + dist * Cos(this.a))
call SetUnitY(this.u, this.y + dist * Sin(this.a))
call SetUnitFacing(this.u, this.a * bj_RADTODEG)
call IssueTargetOrder(this.u, "attack", this.target)
call SetPlayerAbilityAvailable(p, ABILID, false)
call UnitAddAbility(this.u, DUMABILID1)
call UnitDamageTarget(this.u, this.target, damage, true, false, ATK, DMG, null)
set p = null
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call RegisterSpellEffectEvent(ABILID, function thistype.onCast)
call RegisterSpellEffectEvent(DUMABILID1, function thistype.swap)
set castDummy = CreateUnit(NEUTRAL_PASSIVE, CASTID, 0, 0, 0)
call UnitAddAbility(castDummy, DUMABILID2)
call TriggerRegisterUnitEvent(t, castDummy, EVENT_UNIT_SUMMON)
call TriggerAddCondition(t, Condition(function thistype.onSummon))
set t = null
endmethod
endstruct
endlibrary