//*****************************************************************
//* THE SPEAR OF LONGINUS *
//* Spell created for the Spell Contest #10 *
//* at WC3Campaigns.net *
//* *
//* By moyack *
//* V 2.0 *
//*****************************************************************
scope Longinus initializer init
//***************
// CONFIGURATION
//***************
globals
private constant integer SpellID = 'A06M' //Spell ID
private constant integer BuffID = 'B00Y' //Buff ID
private constant integer DummyID = 'n028' //Dummy unit ID.
private constant real Height = 100. //Sets the missile height
private constant real dt = 0.034 //Sets the timer duration.
private constant real Speed = 900. //Sets this value equal to the value of the unit missile speed.
private constant real Radius = 90. //Sets the range in which the projectile will catch other units
private constant real Sep = 50. //Sets the separation distance between impaled units.
private constant real Scale = 2. //Sets the spear model scale.
private constant real Cooldwn = 0.8 //Sets the attack animation cooldown. The purpose of this value is that the hero can't abuse of this ability, if it's set to 0, the hero could cast this spell too many times with one attack.
endglobals
// sets the damage of each attack
private constant function Damage takes integer l returns real
return 100. + 50. * (l - 1)
endfunction
// sets the chance to use the spell
private constant function Chance takes integer l returns real
return 0.2 + 0.2 * (l - 1)
endfunction
// sets the max distance that are throwed the affected units
private constant function MaxDist takes integer l returns real
return 800. + 100. * (l - 1)
endfunction
// sets the max number of units which are impaled by the spear
private constant function Impaled takes integer l returns integer
return 5 + 1 * (l - 1)
endfunction
// sets the mana consumed per effective casting
private constant function Mana takes integer l returns real
return 10. - 2. * (l - 1)
endfunction
// Change this function in order to catch all the units that can be impaled by the spear
private function GetUnits takes nothing returns boolean
return IsUnitType(GetFilterUnit(), UNIT_TYPE_MECHANICAL) == false and GetWidgetLife(GetFilterUnit()) > 0.405
endfunction
//*******************
// END CONFIGURATION
//*******************
globals
// please DO NOT TOUCH this part if you don't know what are you doing!!!
private group G = CreateGroup()
private region R
private rect T
private string SonicFX
private string BloodFX
endglobals
private keyword Data
private constant function True takes nothing returns boolean
return true
endfunction
private function CountDest takes nothing returns nothing
set bj_forceCountPlayers = bj_forceCountPlayers + 1
endfunction
private function MoveImpaled takes nothing returns nothing
local Data D = Data(bj_destInRegionDiesCount)
call SetUnitX(GetEnumUnit(), GetUnitX(D.dummy) + Sep * bj_groupCountUnits * D.cos)
call SetUnitY(GetEnumUnit(), GetUnitY(D.dummy) + Sep * bj_groupCountUnits * D.sin)
call MoveRectTo(T, GetUnitX(GetEnumUnit()), GetUnitY(GetEnumUnit()))
set bj_forceCountPlayers = 0
call EnumDestructablesInRect(T, Condition(function True), function CountDest)
if not IsUnitInRegion(R, GetEnumUnit()) or bj_forceCountPlayers > 0 then
call D.clean()
endif
set bj_groupCountUnits = bj_groupCountUnits + 1
endfunction
private function DealDamage takes nothing returns nothing
local Data D = Data(bj_destInRegionDiesCount)
if IsUnitInRangeXY(GetEnumUnit(), GetUnitX(D.dummy) + Radius * D.cos, GetUnitY(D.dummy) + Radius * D.sin, Radius) and not IsUnitInGroup(GetEnumUnit(), D.Units) then
call UnitDamageTarget(D.caster, GetEnumUnit(), Damage(GetUnitAbilityLevel(D.caster, SpellID)), true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_CLAW_HEAVY_SLICE)
call DestroyEffect(AddSpecialEffectTarget(BloodFX, GetEnumUnit(), "chest"))
call PauseUnit(GetEnumUnit(), true)
call GroupAddUnit(D.Units, GetEnumUnit())
set D.count = D.count + 1
endif
endfunction
interface Updater
method Update takes nothing returns nothing
endinterface
private struct indexer extends Updater
static timer T = CreateTimer()
static integer counter = 0
static indexer array indexers
integer i
boolean on = true
private method Update takes nothing returns nothing
debug call DisplayTimedTextFromPlayer(GetLocalPlayer(), 0,0,2, "Dummy update function has been executed...")
endmethod
static method DoUpdate takes Updater U returns nothing
call U.Update()
endmethod
method onDestroy takes nothing returns nothing
set indexer.counter = indexer.counter - 1
set indexer.indexers[indexer.counter].i = .i
set indexer.indexers[.i] = indexer.indexers[indexer.counter]
if indexer.counter < 1 then
call PauseTimer(indexer.T)
endif
set .on = false
endmethod
private static method Loop takes nothing returns nothing
local integer i = 0
loop
exitwhen i > indexer.counter
call indexer.DoUpdate(indexer.indexers[i])
set i = i + 1
endloop
endmethod
static method create takes nothing returns indexer
local indexer I = indexer.allocate()
set I.i = indexer.counter
set indexer.indexers[indexer.counter] = integer(I)
set indexer.counter = indexer.counter + 1
if indexer.counter == 1 then
call TimerStart(indexer.T, dt, true, function indexer.Loop)
endif
return I
endmethod
endstruct
private struct cooldown extends indexer
private static group Cool = CreateGroup()
unit u
real time = 0.
real dur
static method start takes unit u, real dur returns nothing
local cooldown C
local integer i = 1
if not IsUnitInGroup(u, .Cool) then
call GroupAddUnit(.Cool, u)
set C = cooldown.allocate()
set C.u = u
set C.dur = dur
endif
endmethod
method onDestroy takes nothing returns nothing
call GroupRemoveUnit(cooldown.Cool, .u)
set .u = null
endmethod
private method Update takes nothing returns nothing
set .time = .time + dt
if .time >= .dur and .on then
call .destroy()
endif
endmethod
static method IsInCooldown takes unit u returns boolean
return IsUnitInGroup(u, .Cool)
endmethod
endstruct
private struct Data extends indexer
unit caster
unit dummy
group Units
integer count = 0
real angle
real cos
real sin
real dist = 0.
static method Start takes unit caster, unit target returns nothing
local Data D = Data.allocate()
if D.Units == null then
set D.Units = CreateGroup()
endif
set D.caster = caster
set D.angle = Atan2(GetUnitY(target)-GetUnitY(caster), GetUnitX(target)-GetUnitX(caster))
set D.cos = Cos(D.angle)
set D.sin = Sin(D.angle)
set D.dummy = CreateUnit(GetOwningPlayer(caster), DummyID, GetUnitX(caster) + Radius * D.cos, GetUnitY(caster) + Radius * D.sin, D.angle * bj_RADTODEG)
call UnitAddAbility(D.dummy, 'Amrf')
call SetUnitScale(D.dummy, Scale, Scale, Scale)
call SetUnitFlyHeight(D.dummy, Height, 0.)
call DestroyEffect(AddSpecialEffectTarget(SonicFX, D.dummy, "origin"))
endmethod
private static method Release takes nothing returns nothing
call PauseUnit(GetEnumUnit(), false)
call SetUnitPosition(GetEnumUnit(), GetUnitX(GetEnumUnit()), GetUnitY(GetEnumUnit()))
endmethod
private method onDestroy takes nothing returns nothing
call ForGroup(.Units, function Data.Release)
call RemoveUnit(.dummy)
set .caster = null
set .dummy = null
call GroupClear(.Units)
endmethod
private method Update takes nothing returns nothing
set .dist = .dist + Speed * dt
if .dist > MaxDist(GetUnitAbilityLevel(.caster, SpellID)) and .on then
call .destroy()
else
call SetUnitX(.dummy, GetUnitX(.dummy) + Speed * dt * .cos)
call SetUnitY(.dummy, GetUnitY(.dummy) + Speed * dt * .sin)
call GroupEnumUnitsInRange(G, GetUnitX(.dummy), GetUnitY(.dummy), Radius, Condition(function GetUnits))
call GroupRemoveUnit(G, .caster)
set bj_destInRegionDiesCount = integer(this)
set bj_groupCountUnits = 0
if .count < Impaled(GetUnitAbilityLevel(.caster, SpellID)) then
call ForGroup(G, function DealDamage)
endif
call ForGroup(.Units, function MoveImpaled)
call GroupClear(G)
endif
endmethod
method clean takes nothing returns nothing
set .dist = 2 * MaxDist(GetUnitAbilityLevel(.caster, SpellID))
endmethod
endstruct
private function Actions takes nothing returns boolean
if GetUnitAbilityLevel(GetAttacker(), BuffID) > 0 and GetRandomReal(0., 1.) < Chance(GetUnitAbilityLevel(GetAttacker(), SpellID)) and GetUnitState(GetAttacker(), UNIT_STATE_MANA) >= Mana(GetUnitAbilityLevel(GetAttacker(), SpellID)) and not cooldown.IsInCooldown(GetAttacker()) then
call cooldown.start(GetAttacker(), Cooldwn)
call Data.Start(GetAttacker(), GetTriggerUnit())
call SetUnitState(GetAttacker(), UNIT_STATE_MANA, GetUnitState(GetAttacker(), UNIT_STATE_MANA) - Mana(GetUnitAbilityLevel(GetAttacker(), SpellID)))
endif
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
call TriggerAddCondition(t, Condition(function Actions))
set T = Rect(0, Radius, 0, Radius)
set R = CreateRegion()
call RegionAddRect(R, bj_mapInitialPlayableArea)
set SonicFX = GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, 0) //Sonic effect model
set BloodFX = GetAbilityEffectById(SpellID, EFFECT_TYPE_SPECIAL, 1) //Blood effect model
call Preload(SonicFX)
call Preload(BloodFX)
set t = null
endfunction
endscope