library ArcaneMissile uses CTL, SpellEffectEvent, UnitIndexer, WorldBounds, ArcaneBolts
/*******************************************************************************
CONFIGURABLES
*******************************************************************************/
globals
private constant integer ABIL_ID = 'ABAM' // raw code of ability "Arcane Missile"
private constant integer DUM_ABIL_ID = 'AAM0' // raw code of ability "Arcane Missile (Slow)"
private constant integer BUFF_ID = 'BBAM' // raw code of buff "Arcane Missile"
private constant integer DUMMY_ID = 'duAM' // raw code of unit "Arcane Missile Dummy"
private constant integer RED = 255 // red vertex color of spell projectile
private constant integer GREEN = 255 // green vertex color of spell projectile
private constant integer BLUE = 255 // blue vertex color of spell projectile
private constant integer TRANS = 255 // transparency of spell projectile, where 0 is fully transparent
private constant real SCALE = 1.5 // scale size of spell projectile
private constant real HEIGHT = 100. // flying height of spell projectile
private constant real SPEED = 700. // distance travelled per second by spell projectile
private constant real COLLISION = 100. // collision contact size of spell projectile
private constant real ANGLE_RATE = 60. // maximum turning degree of spell projectile per second
private constant string FX = "Abilities\\Spells\\Human\\SpellSteal\\SpellStealMissile.mdl" // special effect used when damgage is dealt
private constant string FX_AT = "chest" // attachment point of FX
private constant boolean PRELOAD = true // preloads resources if true
private constant real ENUM_RADIUS = 176. // max collision size of a unit in your map
private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type
private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type
endglobals
// area of effect
private constant function GetArea takes integer level returns real
return 150.0
endfunction
// damage dealt
private constant function GetDamage takes integer level returns real
return 40. * level + 60.
endfunction
// distance travelled by missile
private constant function GetDistance takes integer level returns real
return 1200.
endfunction
// slow duration
private constant function GetDuration takes integer level returns real
return 3.
endfunction
// homing detection area
private constant function GetHomeArea takes integer level returns real
return 600.
endfunction
// target types allowed for dealing damage
private constant function GetFilter takes unit caster, unit target returns boolean
return /*
*/ not IsUnitType(target, UNIT_TYPE_DEAD) and /* target is alive
*/ IsUnitEnemy(target, GetOwningPlayer(caster)) and /* target is an enemy of caster
*/ not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /* target is not a structure
*/ not IsUnitType(target, UNIT_TYPE_MECHANICAL) and /* target is not mechanic
*/ not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) /* target is not magic immune
*/
endfunction
/*******************************************************************************
END CONFIGURABLES
*******************************************************************************/
globals
private group G = bj_lastCreatedGroup
endglobals
private struct Slow extends array
private static constant real TIMEOUT = 0.031250000
private static integer array store
private unit u
private real dur
implement CTLExpire
set this.dur = this.dur - TIMEOUT
if this.dur <= 0 or IsUnitType(this.u, UNIT_TYPE_DEAD) or GetUnitTypeId(this.u) == 0 then
set thistype.store[GetUnitUserData(this.u)] = 0
call UnitRemoveAbility(this.u, DUM_ABIL_ID)
call UnitRemoveAbility(this.u, BUFF_ID)
call this.destroy()
endif
implement CTLEnd
public static method apply takes unit u, real dur returns nothing
local thistype this
local integer id = GetUnitUserData(u)
if thistype.store[id] == 0 then
set this = thistype.create()
set this.u = u
set thistype.store[id] = this
call UnitAddAbility(u, DUM_ABIL_ID)
call UnitMakeAbilityPermanent(u, true, DUM_ABIL_ID)
else
set this = thistype.store[id]
endif
set this.dur = dur
endmethod
endstruct
private struct Main extends array
private static constant real TIMEOUT = 0.031250000
private static constant real TRUE_SPEED = SPEED * TIMEOUT
private static constant real TRUE_ANGLE_RATE = ANGLE_RATE * TIMEOUT * bj_DEGTORAD
private static constant real PI_2 = bj_PI * 2
private unit u
private unit dummy
private unit target
private real area
private real dmg
private real dist
private real dur
private real homeArea
private real sin
private real cos
implement CTL
local unit u
local unit target
local boolean b = false
local real x
local real y
local real dx
local real dy
local real r
local real minDist
local real facing
implement CTLExpire
set target = null
set b = false
set x = GetUnitX(this.dummy)
set y = GetUnitY(this.dummy)
set minDist = this.homeArea * this.homeArea
call GroupEnumUnitsInRange(G, x, y, this.homeArea + ENUM_RADIUS, null)
loop
set u = FirstOfGroup(G)
exitwhen u == null
call GroupRemoveUnit(G, u)
set dx = GetUnitX(u) - x
set dy = GetUnitY(u) - y
set r = dx * dx + dy * dy
if GetFilter(this.u, u) and IsUnitInRangeXY(u, x, y, this.homeArea) and r < minDist then
if IsUnitInRangeXY(u, x, y, COLLISION) then
set b = true
call GroupClear(G)
exitwhen true
else
set minDist = r
set target = u
endif
endif
endloop
if b then
call GroupEnumUnitsInRange(G, x, y, this.area + ENUM_RADIUS, null)
loop
set u = FirstOfGroup(G)
exitwhen u == null
call GroupRemoveUnit(G, u)
if GetFilter(this.u, u) and IsUnitInRangeXY(u, x, y, this.area) then
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_AT))
call UnitDamageTarget(this.u, u, this.dmg, true, false, ATK, DMG, null)
call Slow.apply(u, this.dur)
endif
endloop
call KillUnit(this.dummy)
call this.destroy()
else
set this.target = target
set facing = GetUnitFacing(this.dummy) * bj_DEGTORAD
if this.target != null then
set r = Atan2(GetUnitY(this.target) - y, GetUnitX(this.target) - x)
if r < 0 then
set r = r + PI_2
endif
if facing < r then
set facing = facing + PI_2
endif
if facing - r < bj_PI then
set facing = facing - TRUE_ANGLE_RATE
elseif facing - r > bj_PI then
set facing = facing + TRUE_ANGLE_RATE
else
set facing = r
endif
endif
set x = x + TRUE_SPEED * Cos(facing)
set y = y + TRUE_SPEED * Sin(facing)
call SetUnitFacing(this.dummy, facing * bj_RADTODEG)
if x > WorldBounds.minX and y > WorldBounds.minY and x < WorldBounds.maxX and y < WorldBounds.maxY then
call SetUnitX(this.dummy, x)
call SetUnitY(this.dummy, y)
endif
set this.dist = this.dist - TRUE_SPEED
if this.dist <= 0 then
call KillUnit(this.dummy)
call this.destroy()
endif
endif
implement CTLNull
set u = null
set target = null
implement CTLEnd
private static method onCast takes nothing returns boolean
local thistype this = thistype.create()
local integer level
local real a
set this.u = GetTriggerUnit()
set a = Atan2(GetSpellTargetY() - GetUnitY(this.u), GetSpellTargetX() - GetUnitX(this.u))
set this.dummy = CreateUnit(GetOwningPlayer(this.u), DUMMY_ID, GetUnitX(this.u), GetUnitY(this.u), a * bj_RADTODEG)
set this.target = null
set level = GetUnitAbilityLevel(this.u, ABIL_ID)
set this.area = GetArea(level)
set this.dmg = GetDamage(level)
set this.dist = GetDistance(level)
set this.dur = GetDuration(level)
set this.homeArea = GetHomeArea(level)
call SetUnitVertexColor(this.dummy, RED, GREEN, BLUE, TRANS)
call SetUnitScale(this.dummy, SCALE, 0, 0)
call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
return false
endmethod
private static method onInit takes nothing returns nothing
static if PRELOAD then
local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, 0, 0, 0)
call UnitAddAbility(u, DUM_ABIL_ID)
call RemoveUnit(u)
set u = null
endif
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
endmethod
endstruct
endlibrary