//******************************************************************************
//* *
//* Barrage *
//* v1.02 *
//* *
//* By: Rising_Dusk *
//* *
//******************************************************************************
//**************************************
//* LIBRARY REQUIREMENTS:
//* - GroupUtils
//*
scope Barrage initializer Init
globals
//*************************************************************
//* Configuration Constants
//* Raw codes
private constant integer SPELL_ID = 'AHbz'
private constant integer DUMMY_ID = 'hpea'
//* System related
private constant real TIMER_INTERVAL = 0.04
//* Movement constants
private constant real AOE_BUFFER = 30.
private constant real RANGE_THRESHOLD_SQUARED = 2025.
//* Dummy unit configuration
private constant real DUMMY_MOVE_SPEED = 900.
private constant real DUMMY_UNIT_HEIGHT = 65.
//* Damage properties
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL
//* Offsets for the starting point of the projectiles
private constant real X_OFFSET = -15.
private constant real Y_OFFSET = 40.
//* Effect placement
private constant string EFFECT_POINT = "origin"
//*************************************************************
//* System Variables
private timer MoveTimer = CreateTimer()
private boolean ToStart = false
private boolexpr Checker = null
private player TempPlayer = null
private integer Counter = 0
private integer array Arrows
private trigger Trg = CreateTrigger()
private constant integer SPELL_TARGET = 1
private constant integer SPELL_POINT = 2
endglobals
//*****************************************************************
//* Configuration Functions
private constant function Damage takes integer lvl returns real
//* Spell Damage
return 7.5*lvl*lvl + 32.5*lvl + 35.
endfunction
private constant function AreaOfEffect takes integer lvl returns real
//* Spell Area of Effect Radius
return 160.+40.*lvl
endfunction
private constant function Targets takes integer lvl returns integer
//* Number of targets to acquire
return lvl+2
endfunction
private function Check takes nothing returns boolean
//* The boolexpr for striking a unit
return IsUnitEnemy(GetFilterUnit(), TempPlayer) and IsUnitType(GetFilterUnit(), UNIT_TYPE_GROUND) and not IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) and GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(), UNIT_TYPE_RESISTANT) and GetUnitAbilityLevel(GetFilterUnit(), 'Avul') <= 0.
endfunction
//*****************************************************************
private struct arrow
integer Flag = 0
integer Level = 0
unit Source = null
unit Target = null
unit Dummy = null
effect Fx = null
real Angle = 0.
real x = 0.
real y = 0.
static method create takes unit u, unit t, real xi, real yi, real xf, real yf, integer flag, integer lvl, real angle returns arrow
local arrow a = arrow.allocate()
set a.Source = u
set a.Target = t
set a.Dummy = CreateUnit(GetOwningPlayer(u), DUMMY_ID, xi, yi, angle*bj_RADTODEG)
set a.Level = lvl
set a.Flag = flag
set a.Angle = angle
set a.Fx = AddSpellEffectTargetById(SPELL_ID, EFFECT_TYPE_MISSILE, a.Dummy, "origin")
call SetUnitX(a.Dummy, xi)
call SetUnitY(a.Dummy, yi)
if IsUnitType(a.Dummy, UNIT_TYPE_GROUND) then
call UnitAddAbility(a.Dummy, 'Amrf')
call UnitRemoveAbility(a.Dummy, 'Amrf')
endif
call SetUnitFlyHeight(a.Dummy, DUMMY_UNIT_HEIGHT, 0.)
if flag == SPELL_POINT then
set a.x = xf
set a.y = yf
else
set a.x = GetUnitX(t)
set a.y = GetUnitY(t)
endif
return a
endmethod
private method onDestroy takes nothing returns nothing
//* Clean up all effects
call DestroyEffect(this.Fx)
if this.Flag == 1 then
call DestroyEffect(AddSpellEffectTargetById(SPELL_ID, EFFECT_TYPE_TARGET, this.Target, EFFECT_POINT))
else
call DestroyEffect(AddSpellEffectById(SPELL_ID, EFFECT_TYPE_TARGET, this.x, this.y))
endif
call KillUnit(this.Dummy)
endmethod
endstruct
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == SPELL_ID
endfunction
private function Callback takes nothing returns nothing
local unit u = null
local unit d = null
local unit t = null
local arrow a = 0
local integer i = Counter - 1
local integer j = 0
local integer lvl = 0
local real xi = 0.
local real yi = 0.
local real xf = 0.
local real yf = 0.
local real di = 0.
local real an = 0.
//* Loop through all arrow instances and do effects
loop
exitwhen i < 0
set a = Arrows[i]
set u = a.Source
set t = a.Target
set d = a.Dummy
set lvl = a.Level
set xi = GetUnitX(d)
set yi = GetUnitY(d)
if a.Flag == SPELL_TARGET then
//* Update position/angle to track the enemy unit
set a.x = GetUnitX(t)
set a.y = GetUnitY(t)
set a.Angle = Atan2(a.y-yi, a.x-xi)
call SetUnitFacing(d, a.Angle*bj_RADTODEG)
endif
set di = (a.x-xi)*(a.x-xi)+(a.y-yi)*(a.y-yi)
if di <= RANGE_THRESHOLD_SQUARED then
//* Finished, deal damage if SPELL_TARGET
if a.Flag == SPELL_TARGET then
call UnitDamageTarget(u, t, Damage(lvl), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
call a.destroy()
set Counter = Counter - 1
if Counter < 0 then
call PauseTimer(MoveTimer)
set Counter = 0
else
set Arrows[i] = Arrows[Counter]
endif
else
//* Not finished, move forward in space and time
set di = DUMMY_MOVE_SPEED*TIMER_INTERVAL
set xf = xi + di*Cos(a.Angle)
set yf = yi + di*Sin(a.Angle)
call SetUnitX(d, xf)
call SetUnitY(d, yf)
endif
set i = i - 1
endloop
set u = null
set d = null
set t = null
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local unit s = null
local location l = GetSpellTargetLoc()
local integer lvl = GetUnitAbilityLevel(u, SPELL_ID)
local integer c = 0
local integer o = Counter
local integer m = Targets(lvl)
local arrow a = 0
local real xi = GetUnitX(u)
local real yi = GetUnitY(u)
local real xf = GetLocationX(l)
local real yf = GetLocationY(l)
local real di = 0.
local real ag = Atan2(yf-yi, xf-xi)
local real x = 0.
local real y = 0.
local real an = 0.
//* Move the starting point based on constant offsets
if X_OFFSET != 0. then
//* X offset
set an = ag-bj_PI/2
set xi = xi + X_OFFSET*Cos(an)
set yi = yi + X_OFFSET*Sin(an)
endif
if Y_OFFSET != 0. then
//* Y offset
set an = ag
set xi = xi + Y_OFFSET*Cos(an)
set yi = yi + Y_OFFSET*Sin(an)
endif
set TempPlayer = GetOwningPlayer(u)
call GroupEnumUnitsInRange(ENUM_GROUP, xf, yf, AreaOfEffect(lvl), Checker)
//* Create up to Targets(lvl) number of arrows
loop
//* Pick a randomly selected unit in the group
set bj_groupRandomConsidered = 0
set bj_groupRandomCurrentPick = null
call ForGroup(ENUM_GROUP, function GroupPickRandomUnitEnum)
set s = bj_groupRandomCurrentPick
exitwhen s == null or c >= m
set an = Atan2(GetUnitY(s)-yi, GetUnitX(s)-xi)
set a = arrow.create(u, s, xi, yi, 0., 0., SPELL_TARGET, lvl, an)
set Arrows[Counter] = integer(a)
set Counter = Counter + 1
call GroupRemoveUnit(ENUM_GROUP, s)
set c = c + 1
endloop
//* Create the leftover arrows as random effects so the spell isn't ugly
if c < m then
loop
exitwhen c >= m
set di = GetRandomReal(AOE_BUFFER*2, AreaOfEffect(lvl)-AOE_BUFFER)
set ag = GetRandomReal(0, 2*bj_PI)
set x = xf + di*Cos(ag)
set y = yf + di*Sin(ag)
set an = Atan2(y-yi, x-xi)
set a = arrow.create(u, null, xi, yi, x, y, SPELL_POINT, lvl, an)
set Arrows[Counter] = integer(a)
set Counter = Counter + 1
set c = c + 1
endloop
endif
if o == 0 then
call TimerStart(MoveTimer, TIMER_INTERVAL, true, function Callback)
endif
call GroupClear(ENUM_GROUP)
call RemoveLocation(l)
set l = null
set u = null
set s = null
endfunction
private function Init takes nothing returns nothing
call TriggerAddAction(Trg, function Actions)
call TriggerAddCondition(Trg, Condition(function Conditions))
call TriggerRegisterAnyUnitEventBJ(Trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
set Checker = Condition(function Check)
endfunction
endscope