/*
=====Spell Name: Fire Escort v1.2
=====Created by: Mckill2009
REQUIRES:
- JassNewGenPack by Vexorian
- SpellEffectEvent by Bribe
OPTIONAL REQUIREMENTS:
- BoundSentinel by Vexorian
HOW TO INSTALL:
- Copy ALL the custom units/abilities/buff from the object editor to your map.
- This has custom buff to remove the stun buff from Firebolt spell.
- Copy ALL that is inside the folder 'FireEscort' to your trigger editor.
- If you want to make your own units/abilities/buff, make sure you change the rawID indicated in the code.
- If you change the DUMMY_SPELL_ID rawID, make sure it will be casted by ORDER_ID.
- To view rawID, press CTRL+D in the object editor.
- Save and Done!
*/
library FireEscort uses SpellEffectEvent optional BoundSentinel
globals
/**************************************************************
* RAW IDs: change the IDs according to your object editor
* The DUMMY_SPELL_ID and ORDER_ID must match!
***************************************************************/
private constant integer SPELL_ID = 'A002' //Howl of Terror custom spell (rawID)
private constant integer DUMMY_SPELL_ID = 'A000' //Firebolt custom spell (rawID)
private constant integer ORDER_ID = 852231 //Firebolt (orderID)
private constant integer DUMMY_ID = 'h000' //rawID
private constant integer BOMB_ID = 'h001' //rawID
/**************************************************************
* CONFIGURABLE GLOBALS
***************************************************************/
//It is recommended not to exceed 2, you may use negative values
private constant real ROTATION_SPEED = 0.05
//Fireball move speed towards the caster or target
private constant real FOLLOW_SPEED = 7
//Fireballs MAX offset from dummy's center
private constant real EXPAND_RANGE = 300
//Fireballs move speed away from hero to EXPAND_RANGE
private constant real EXPAND_SPEED = 1
//Sets how many seconds the dummy will search for new target
private constant real TARGET_SEARCH_INTERVAL = 5
//Cast interval of the Fireballs
private constant real MISSILE_CAST_INTERVAL = 1
//The FireballLifeDuration will start here, when fireballs at MAX height
private constant real MAX_FLY_HEIGHT = 400
//Fly speed from ground to MAX_FLY_HEIGHT
private constant real FLY_SPEED = 2
/**************************************************************
* NON-CONFIGURABLE GLOBALS
***************************************************************/
private constant real INTERVAL = 0.03125
private group g = CreateGroup()
private integer count = 0
private unit TempU = null
endglobals
/**************************************************************
CONFIGURABLES
***************************************************************/
private function FilterEnemies takes unit u returns boolean
return not (IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE))
endfunction
private function FireballLifeDuration takes integer level returns real
return 5. * level + 5 //10/15/20/25/30
endfunction
//Range of Fireball casting Firebolt
private function FireballCastRange takes integer level returns real
return 150. * level + 250 //400/550/700/850/1000
endfunction
//How many fireballs created
private function FireballCount takes integer level returns integer
return 2 * level + 4 //6/8/10/12/14
endfunction
//Fireballs search for nearby enemies
private function SearchEnemyRange takes integer level returns real
return 200. * level + 600 //800/1000/1200/1400/1600
endfunction
/**************************************************************
NON-CONFIGURABLES
***************************************************************/
private function UnitAlive takes unit u returns boolean
return not IsUnitType(u, UNIT_TYPE_DEAD) and u!=null
endfunction
private function GetDistance takes real x1, real y1, real x2, real y2 returns real
return (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1)
endfunction
private function FilterThem takes unit caster, unit target returns boolean
return UnitAlive(target) and IsUnitEnemy(caster, GetOwningPlayer(target)) and FilterEnemies(target)
endfunction
private function FilterTarget takes nothing returns boolean
return FilterThem(TempU, GetFilterUnit())
endfunction
private function EnumUnits takes nothing returns nothing
set count = count+1
endfunction
private function SetUnitXY takes unit u, real x, real y, real angle returns nothing
call SetUnitX(u, x + FOLLOW_SPEED * Cos(angle))
call SetUnitY(u, y + FOLLOW_SPEED * Sin(angle))
endfunction
private function GetRandomUnitInArea takes real x, real y, real aoe, boolexpr b returns unit
local integer ran
call GroupEnumUnitsInRange(g, x, y, aoe, b)
set count = 0
call ForGroup(g, function EnumUnits)
set ran = GetRandomInt(1, count)
set count = 0
loop
set TempU = FirstOfGroup(g)
exitwhen TempU==null
set count = count + 1
if count==ran then
exitwhen true
endif
call GroupRemoveUnit(g, TempU)
endloop
call GroupClear(g)
return TempU
endfunction
private struct EscortRotation
unit leader
unit escort
real angle
real aoe
real expand
real height
real duration
real castInterval
static timer t = CreateTimer()
static integer index = 0
static integer array indexAR
static method periodic takes nothing returns nothing
local thistype this
local integer i = 0
local unit tar
local real xEscort
local real yEscort
local real xLeader
local real yLeader
loop
set i = i + 1
set this = indexAR[i]
if .duration > 0 then
set .angle = .angle + ROTATION_SPEED
set xLeader = GetUnitX(.leader)
set yLeader = GetUnitY(.leader)
if EXPAND_RANGE > .expand then
set .expand = .expand + EXPAND_SPEED
endif
if MAX_FLY_HEIGHT > .height then
set .height = .height + FLY_SPEED
call SetUnitFlyHeight(.escort, .height, 0)
elseif .height >= MAX_FLY_HEIGHT then
set .duration = .duration - INTERVAL
set .castInterval = .castInterval + INTERVAL
if .castInterval > MISSILE_CAST_INTERVAL then
set .castInterval = 0
set xEscort = GetUnitX(.escort)
set yEscort = GetUnitY(.escort)
set TempU = .escort
set tar = GetRandomUnitInArea(xEscort, yEscort, .aoe, Filter(function FilterTarget))
if tar!=null then
call IssueTargetOrderById(.escort, ORDER_ID, tar)
set tar = null
endif
endif
endif
call SetUnitX(.escort, xLeader + .expand * Cos(.angle))
call SetUnitY(.escort, yLeader + .expand * Sin(.angle))
else
if UnitAlive(.leader) then
call KillUnit(.leader)
endif
call KillUnit(.escort)
set .leader = null
set .escort = null
call .destroy()
set indexAR[i] = indexAR[index]
set indexAR[index] = this
set i = i - 1
set index = index - 1
if index==0 then
call PauseTimer(t)
endif
endif
exitwhen i==index
endloop
endmethod
static method register takes unit leader, unit escort, real angle, real duration, real aoe returns nothing
local thistype this = allocate()
set .leader = leader
set .escort = escort
set .angle = angle*bj_DEGTORAD
set .expand = 0
set .castInterval = 0
set .height = 0
set .duration = duration
set .aoe = aoe
if index==0 then
call TimerStart(t, INTERVAL, true, function thistype.periodic)
endif
set index = index + 1
set indexAR[index] = this
endmethod
endstruct
private struct Cast
unit caster
unit target
unit dummy
real search
real aoe
real goBack
real height
static timer t = CreateTimer()
static integer index = 0
static integer array indexAR
static method periodic takes nothing returns nothing
local thistype this
local integer i = 0
local unit first
local real xCaster
local real yCaster
local real xDum
local real yDum
loop
set i = i+1
set this = indexAR[i]
if UnitAlive(.caster) and UnitAlive(.dummy) then
set xCaster = GetUnitX(.caster)
set yCaster = GetUnitY(.caster)
set xDum = GetUnitX(.dummy)
set yDum = GetUnitY(.dummy)
if .target==null then
//search for enemies from dummy's aoe
set .target = GetRandomUnitInArea(xCaster, yCaster, .aoe, Filter(function FilterTarget))
if .target==null then //back to caster
call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
endif
else
//this is not the height of the fireballs
set .height = .height + FLY_SPEED
if .height >= MAX_FLY_HEIGHT then
if GetDistance(xCaster, yCaster, xDum, yDum) > .goBack then
call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
else
//goes targets if caster is also near the target's aoe
call SetUnitXY(.dummy, xDum, yDum, Atan2(GetUnitY(.target) - yDum, GetUnitX(.target) - xDum))
endif
else
call SetUnitXY(.dummy, xDum, yDum, Atan2(yCaster - yDum, xCaster - xDum))
endif
//This refreshes/nulls the targets to search for a new one
set .search = .search + INTERVAL
if .search > TARGET_SEARCH_INTERVAL then
set .search = 0
set .target = null
endif
endif
else //End the Spell
if UnitAlive(.dummy) then
call KillUnit(.dummy)
endif
set .caster = null
set .target = null
set .dummy = null
call .destroy()
set indexAR[i] = indexAR[index]
set indexAR[index] = this
set i = i - 1
set index = index - 1
if index==0 then
call PauseTimer(t)
endif
endif
exitwhen i==index
endloop
endmethod
static method onCast takes nothing returns nothing
local thistype this = allocate()
local integer level
local integer i = 0
local integer countEscorts
local real g = 0
local real gap
local real fireRange
local unit bomb
local real duration
set .caster = GetTriggerUnit()
set level = GetUnitAbilityLevel(.caster, SPELL_ID)
set .target = null
set .dummy = CreateUnit(GetTriggerPlayer(), DUMMY_ID, GetUnitX(.caster), GetUnitY(.caster), 0)
set .search = 0
set .aoe = SearchEnemyRange(level)
set .goBack = .aoe*.aoe
set .height = 0
set countEscorts = FireballCount(level)
set duration = 120. //FireballLifeDuration(level)
set fireRange = FireballCastRange(level)
set gap = (360/countEscorts)
loop
exitwhen i==countEscorts
set bomb = CreateUnit(GetTriggerPlayer(), BOMB_ID, 0, 0, 0)
call SetUnitFlyHeight(bomb, 0, 0)
call UnitAddAbility(bomb, DUMMY_SPELL_ID)
call SetUnitAbilityLevel(bomb, DUMMY_SPELL_ID, level)
call EscortRotation.register(.dummy, bomb, g, duration, fireRange)
set g = g + gap
set i = i + 1
endloop
if index==0 then
call TimerStart(t, INTERVAL, true, function thistype.periodic)
endif
set index = index + 1
set indexAR[index] = this
set bomb = null
endmethod
static method onInit takes nothing returns nothing
local unit u = CreateUnit(Player(15), DUMMY_ID,0,0,0)
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
call UnitAddAbility(u, DUMMY_SPELL_ID)
call KillUnit(u)
set u = null
endmethod
endstruct
endlibrary