library Heartbreaker uses Alloc, SpellEffectEvent, T32, Table
//===========================================================================
// CONFIGURABLES
//===========================================================================
globals
private constant integer ABILID = 'ABHe' // raw code of ability "Heartbreaker"
private constant integer DUM_ABILID_1 = 'AHe0' // raw code of ability "Heartbreaker (Stun)"
private constant integer DUM_ABILID_2 = 'AHe1' // raw code of ability "Heartbreaker (Buff)"
private constant integer BUFFID_1 = 'BHe0' // raw code of buff "Heartbreaker (Stun)"
private constant integer BUFFID_2 = 'BHe1' // raw code of buff "Heartbreaker (Buff)"
private constant integer DUMMYID = 'dHEA' // raw code of unit "Heartbreaker Dummy"
private constant integer CASTERID = 'cAST' // raw code of unit "Caster Dummy"
private constant integer PRELOADID = 'prel' // raw code of unit "Preloader"
private constant string FX = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl" // effect used upon being stunned
private constant string FX_AT = "origin" // attachment point of FX
private constant string PROJ_FX = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl" // effect used upon projectile impact
private constant boolean SHOWTEXT = true // true if floating text (displaying stun duration) is displayed
private constant string COLOR = "|cffff0000" // color code for floating text
private constant real SPEED = 1000.0 // distance traveled by projectile per second
private constant real TRUESPEED = SPEED * T32_PERIOD // distance per interval
private constant real COL_SIZE = 50. // collision size of projectile
private constant real PERIOD = 0.1 // timer update frequency for stun
private constant real ENUM_RADIUS = 176. // max collision size of a unit in your map
private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type of damage
private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type of damage
endglobals
// damage dealt
private constant function GetDamage takes integer level returns real
return 50. * level
endfunction
// damage limit for curse
private constant function GetDamageLimit takes integer level returns real
return 100. * level
endfunction
// stun limit for curse
private constant function GetStunLimit takes integer level returns real
return 1. * level
endfunction
// delay time before curse takes effect
private constant function GetDelayTime takes integer level returns real
return 3.
endfunction
// curse area of effect
private constant function GetArea takes integer level returns real
return 400.
endfunction
// target filter
private constant function GetFilter takes unit c, unit u returns boolean
return /*
*/ not IsUnitType(u, UNIT_TYPE_DEAD) /* // target is alive
*/ and IsUnitEnemy(u, GetOwningPlayer(c)) /* // target is an enemy
*/ and not IsUnitType(u, UNIT_TYPE_STRUCTURE) /* // target is not a structure
*/
endfunction
//===========================================================================
// END CONFIGURABLES
//===========================================================================
globals
private constant real TRUE_COL_SIZE = COL_SIZE * COL_SIZE
private group G = bj_lastCreatedGroup
endglobals
private struct Curse extends array
implement Alloc
thistype next
thistype prev
unit u
unit tar
trigger trig
integer level
real damage
real damagelimit
real dur
static unit castDummy
static Table table
static timer linkTimer = CreateTimer()
private method destroy takes nothing returns nothing
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == 0 then
call PauseTimer(linkTimer)
endif
call UnitRemoveAbility(this.tar, BUFFID_1)
call this.deallocate()
endmethod
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.tar, UNIT_TYPE_DEAD) or GetUnitTypeId(this.tar) == 0 then
call this.destroy()
else
if GetUnitAbilityLevel(this.tar, BUFFID_1) == 0 then
call IssueTargetOrder(castDummy, "firebolt", this.tar)
endif
set this.dur = this.dur - PERIOD
endif
endloop
endmethod
private method periodic takes nothing returns nothing
local texttag t
local integer digitone
local integer digittwo
local integer id
if this.dur <= 0 or this.damage >= this.damagelimit or IsUnitType(this.tar, UNIT_TYPE_DEAD) then
if not IsUnitType(this.tar, UNIT_TYPE_DEAD) and this.damage > 0 then
if this.damage > this.damagelimit then
set this.damage = this.damagelimit
endif
set this.dur = (this.damage / this.damagelimit) * GetStunLimit(this.level)
static if SHOWTEXT then
set digitone = R2I(this.dur)
set digittwo = R2I((this.dur - digitone) * 10)
set t = CreateTextTag()
call SetTextTagText(t, COLOR + I2S(digitone) + "." + I2S(digittwo) + "!|r", 0.0253)
call SetTextTagPosUnit(t, this.tar, 0)
call SetTextTagColor(t, 255, 255, 255, 255)
call SetTextTagVelocity(t, 0.0355 * Cos(bj_PI / 2), 0.0355 * Sin(bj_PI / 2))
call SetTextTagPermanent(t, false)
call SetTextTagLifespan(t, 2.50)
endif
call IssueTargetOrder(castDummy, "firebolt", this.tar)
call DestroyEffect(AddSpecialEffectTarget(FX, this.tar, FX_AT))
if thistype(0).next == 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)
endif
call UnitRemoveAbility(this.tar, DUM_ABILID_2)
call UnitRemoveAbility(this.tar, BUFFID_2)
call table.remove(GetHandleId(this.trig))
call DestroyTrigger(this.trig)
call this.stopPeriodic()
if IsUnitType(this.tar, UNIT_TYPE_DEAD) then
call this.deallocate()
endif
else
set this.dur = this.dur - T32_PERIOD
endif
set t = null
endmethod
implement T32x
private static method onDamage takes nothing returns boolean
local thistype this = table[GetHandleId(GetTriggeringTrigger())]
if this.tar == GetTriggerUnit() then
set this.damage = this.damage + GetEventDamage()
endif
return false
endmethod
public static method create takes unit u, unit tar, integer level returns thistype
local thistype this = thistype.allocate()
set this.u = u
set this.tar = tar
set this.trig = CreateTrigger()
set this.level = level
set this.damage = 0
set this.damagelimit = GetDamageLimit(this.level)
set this.dur = GetDelayTime(this.level)
call UnitAddAbility(this.tar, DUM_ABILID_2)
call TriggerRegisterUnitEvent(this.trig, this.tar, EVENT_UNIT_DAMAGED)
call TriggerAddCondition(this.trig, Condition(function thistype.onDamage))
set table[GetHandleId(this.trig)] = this
call this.startPeriodic()
return this
endmethod
private static method onInit takes nothing returns nothing
set table = Table.create()
set castDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTERID, 0, 0, 0)
call UnitAddAbility(castDummy, DUM_ABILID_1)
endmethod
endstruct
private struct Main extends array
implement Alloc
unit u
unit tar
unit dummy
integer level
private method destroy takes nothing returns nothing
call KillUnit(this.dummy)
call this.deallocate()
endmethod
private method periodic takes nothing returns nothing
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local real dx = GetUnitX(this.tar) - x
local real dy = GetUnitY(this.tar) - y
local real a
local unit u
if dx * dx + dy * dy <= TRUE_COL_SIZE then
set dx = dx + x
set dy = dy + y
set a = GetArea(this.level)
call DestroyEffect(AddSpecialEffect(PROJ_FX, x, y))
call UnitDamageTarget(this.u, this.tar, GetDamage(this.level), true, false, ATK, DMG, null)
call GroupEnumUnitsInRange(G, dx, dy, a + ENUM_RADIUS, null)
loop
set u = FirstOfGroup(G)
exitwhen u == null
call GroupRemoveUnit(G, u)
if IsUnitInRangeXY(u, dx, dy, a) and GetFilter(this.u, u) then
call Curse.create(this.u, u, this.level)
endif
endloop
call this.stopPeriodic()
call this.destroy()
else
set a = Atan2(dy, dx)
call SetUnitX(this.dummy, x + TRUESPEED * Cos(a))
call SetUnitY(this.dummy, y + TRUESPEED * Sin(a))
call SetUnitFacing(this.dummy, a * bj_RADTODEG)
endif
endmethod
implement T32x
private static method onCast takes nothing returns boolean
local thistype this
local real x
local real y
set this = thistype.allocate()
set this.u = GetTriggerUnit()
set this.tar = GetSpellTargetUnit()
set x = GetUnitX(this.u)
set y = GetUnitY(this.u)
set this.dummy = CreateUnit(GetOwningPlayer(this.u), DUMMYID, x, y, Atan2(GetUnitY(this.tar) - y, GetUnitX(this.tar) - x) * bj_RADTODEG)
set this.level = GetUnitAbilityLevel(this.u, ABILID)
call this.startPeriodic()
return false
endmethod
private static method onInit takes nothing returns nothing
local unit u
call RegisterSpellEffectEvent(ABILID, function thistype.onCast)
// preload
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), CASTERID, 0, 0, 0)
call UnitAddAbility(u, DUM_ABILID_1)
call UnitAddAbility(u, DUM_ABILID_2)
call RemoveUnit(u)
call RemoveUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMYID, 0, 0, 0))
set u = null
endmethod
endstruct
endlibrary