scope MarchOfTheMachines
//v1.12
//by Flux
//http://www.hiveworkshop.com/forums/members/flux/
/*
Description:
Enlists an army of robotic minions to destroy
enemy units in an area around Tinker.
NOTES:
- optionally uses SpellEffectEvent
- optionally uses MissileRecyler
SPELL NOTES:
- The spawn radius is centered and fixed around the
cast point, not on Tinker's location.
- The spawn area is a line with a configured length,
which is centered in a configured range behind the
targeted cast point.
- Robots do not spawn at points of the spawning
line which are outside of the map boundaries
- When an enemy comes within collision radius of
a robot, the robot deals damage in explosion radius
around itself and then disappears.
Credits:
WILL THE ALMIGHTY - Laser Strike Model (edited)
Bribe - SpellEffectEvent and MissileRecycler
Magtheridon96 - RegisterPlayerUnitEvent
Vexorian - Attachable Dummy Model
*/
native UnitAlive takes unit u returns boolean
//=============================================================
//==================== CONFIGURATION ========================
//=============================================================
globals
//Rawcode of the Spell
private constant integer SPELL_ID = 'Amrc'
//Rawcode of the map dummy unit
private constant integer DUMMY_ID = 'dumi'
//If you have MissileRecyler, set owner of the
//missiles
private constant player DUMMY_OWNER = Player(15)
//Path to the robot model file
private constant string ROBOT_PATH = "Units\\Creeps\\HeroTinkerRobot\\HeroTinkerRobot.mdl"
//attack and damage types
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
//if true, robots will spawn at a random location
//if false, the spawn location has a pattern
private constant boolean RANDOM_SPAWN = false
//if RANDOM_SPAWN = false, this will determine
//how many robots spawn in the rectangle width
//it always spawn at the two edge of the rectangle width
private constant integer ROBOTS_PER_WIDTH = 8
private constant real ROBOTS_SCALE = 1.10
//if RANDOM_SPAWN = false, setting this to false
//make the spawn pattern zigzag.
private constant boolean SPAWN_PATTERN_NORMAL = true
//Visual Effects
private constant string AREA_SFX = "Abilities\\Spells\\Undead\\ReplenishMana\\SpiritTouchTarget.mdl"
private constant string SPAWN_SFX = "war3mapImported\\LaserEntry.mdx"
private constant string EXPLODE_SFX = "Objects\\Spawnmodels\\Human\\FragmentationShards\\FragBoomSpawn.mdl"
//How long will AREA_SFX last
private constant real AREA_SFX_DURATION = 0.8
//Determines how much AREA_SFX is created
private constant integer SFX_PER_LENGTH = 8
private constant integer SFX_PER_WIDTH = 8
//If true, your enemies can also see the AREA_SFX
private constant boolean SFX_VISIBLE = false
//Spell Periodic Timeout
private constant real TIMEOUT = 0.03125000
endglobals
//The radius which determines when a robot has collided
//with an enemy
private function CollideRadius takes integer lvl returns real
return lvl*10 + 40.0
endfunction
//Robots colliding will explode and deals damage to an
//area
private function ExplodeRadius takes integer lvl returns real
return lvl*50.0 + 50.0
endfunction
//When robots collide into enemy units, it will deal damage to
//the unit it collided with
private function CollideDamage takes integer lvl returns real
return lvl*5.0 //5, 10, 15
endfunction
//When robots collide into enemy units, it will explode and deal
//damage within a certain ExplodeRadius
private function ExplodeDamage takes integer lvl returns real
return lvl*5.0 + 25 //30, 35, 40
endfunction
//Area of Effect Width
private function SpellWidth takes integer lvl returns real
return lvl*100.0 + 600.0
endfunction
//Area of Effect Length
private function SpellLength takes integer lvl returns real
return lvl*100.0 + 600.0
endfunction
//Spawn Rate of robots
private function RobotsPerSecond takes integer lvl returns integer
return lvl*2 + 13
endfunction
//Robot movement speed
private function RobotSpeed takes integer lvl returns real
return lvl*50.0 + 300.0
endfunction
//How long spawning will last
private function SpawnDuration takes integer lvl returns real
return lvl*1.0 + 4.0
endfunction
//Determines what units will get hit
private function TargetFilter takes unit robot, unit target returns boolean
return (IsUnitEnemy(target, GetOwningPlayer(robot)) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and UnitAlive(target))
endfunction
//=============================================================
//================== END CONFIGURATION ======================
//=============================================================
private keyword spell
private struct robot
private real dist
private unit u
private effect model
private spell s
readonly thistype next
readonly thistype prev
private static group g = CreateGroup()
method destroy takes nothing returns nothing
//Remove Unit
call DestroyEffect(model)
static if LIBRARY_MissileRecycler then
call SetUnitOwner(.u, DUMMY_OWNER, true)
call RecycleMissile(.u)
else
call KillUnit(.u)
endif
//Remove from the list
set .next.prev = .prev
set .prev.next = .next
set .u = null
set .model = null
call .deallocate()
endmethod
method move takes nothing returns nothing
local unit u
local real x
local real y
local boolean explode = false
if .dist < s.length then
set x = GetUnitX(.u) + s.dx
set y = GetUnitY(.u) + s.dy
set .dist = .dist + s.moveSpeed
call SetUnitX(.u, x)
call SetUnitY(.u, y)
call GroupEnumUnitsInRange(g, x, y, s.radCollide, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if TargetFilter(.u, u) then
//deals collision damage
call UnitDamageTarget(.u, u, s.dmgCollide, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
set u = null
set explode = true
exitwhen true
endif
endloop
if explode then
call GroupEnumUnitsInRange(g, x, y, s.radExplode, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if TargetFilter(.u, u) then
//deals collision damage
call UnitDamageTarget(.u, u, s.dmgExplode, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
endloop
//SFX
call DestroyEffect(AddSpecialEffect(EXPLODE_SFX, x, y))
call .destroy()
endif
else
call .destroy()
endif
endmethod
static method allocateHead takes nothing returns thistype
local thistype this = allocate()
set .next = 0
set .prev = 0
return this
endmethod
static method create takes real x, real y, real facing, spell instance returns thistype
local thistype this = allocate()
set .s = instance
//insert in the list
set .next = .s.head.next
set .prev = .s.head
set .prev.next = this
set .next.prev = this
set .dist = 0
static if LIBRARY_MissileRecycler then
set .u = GetRecycledMissile(x, y, 0, facing*bj_RADTODEG)
set model = AddSpecialEffectTarget(ROBOT_PATH, .u, "origin")
call SetUnitOwner(.u, s.owner, true)
else
set .u = CreateUnit(s.owner, DUMMY_ID, x, y, facing*bj_RADTODEG)
set model = AddSpecialEffectTarget(ROBOT_PATH, .u, "origin")
call PauseUnit(.u, true)
endif
if ROBOTS_SCALE != 1 then
call SetUnitScale(.u, ROBOTS_SCALE, ROBOTS_SCALE, ROBOTS_SCALE)
endif
return this
endmethod
endstruct
globals
private constant integer RPW = ROBOTS_PER_WIDTH - 1
endglobals
private struct spell
//center of the rectangle
private real x //point of cast
private real y //point of cast
readonly real dx
readonly real dy
//spawning counter
private real spawnCtr
private real spawnTime
//spell data
private real duration
readonly player owner
readonly real dmgCollide
readonly real dmgExplode
readonly real radCollide
readonly real radExplode
readonly real moveSpeed
//spell area of effect
readonly real angle
readonly real cosA
readonly real sinA
readonly real length
private real width
//UnitGroup head
robot head
static if not RANDOM_SPAWN then
private real lastSpawnWidth
static if not SPAWN_PATTERN_NORMAL then
private boolean spawnWidthIncreasing
endif
endif
//Special effects variables
private real sfxlength
private real sfxCtr
private static timer t = CreateTimer()
private static integer array next
private static integer array prev
private method destroy takes nothing returns nothing
//Remove from the List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
if next[0] == 0 then
call PauseTimer(t)
endif
set .owner = null
call .deallocate()
endmethod
private method periodic takes nothing returns nothing
local robot r = head.next
static if RANDOM_SPAWN then
local real rand = GetRandomReal(-.width, .width)
endif
local real x
local real y
//sfx variables
local real sfxwidth
local real x2 //rotated
local real y2 //rotated
local string str
//Move all the robot
loop
exitwhen r == 0
call r.move()
set r = r.next
endloop
//Update duration
set .duration = .duration - TIMEOUT
if .duration < 0 then
if head.next == 0 then
call .destroy()
endif
else
set .spawnCtr = .spawnCtr + TIMEOUT
if .spawnCtr > .spawnTime then
set .spawnCtr = .spawnCtr - .spawnTime
//Spawn robots
static if RANDOM_SPAWN then
set x = .x - 0.5*(.length*cosA - rand*sinA)
set y = .y - 0.5*(.length*sinA + rand*cosA)
else
static if SPAWN_PATTERN_NORMAL then
set .lastSpawnWidth = .lastSpawnWidth + 2*.width/RPW
if .lastSpawnWidth > .width then
set .lastSpawnWidth = -.width
endif
else
if .spawnWidthIncreasing then
set .lastSpawnWidth = .lastSpawnWidth + 2*.width/RPW
if .lastSpawnWidth > .width then
set .spawnWidthIncreasing = false
endif
else
set .lastSpawnWidth = .lastSpawnWidth - 2*.width/RPW
if .lastSpawnWidth < -.width then
set .spawnWidthIncreasing = true
endif
endif
endif
set x = .x - 0.5*(.length*cosA - .lastSpawnWidth*sinA)
set y = .y - 0.5*(.length*sinA + .lastSpawnWidth*cosA)
endif
//-------- CREATE ROBOT TYPE STRUCT ----------
call robot.create(x, y, .angle, this)
call DestroyEffect(AddSpecialEffect(SPAWN_SFX, x, y))
endif
//Special Effects
if .sfxlength <= 0.5*.length then
set sfxCtr = sfxCtr + TIMEOUT
if sfxCtr > AREA_SFX_DURATION/SFX_PER_LENGTH then
set sfxCtr = sfxCtr - AREA_SFX_DURATION/SFX_PER_LENGTH
set sfxwidth = -0.5*.width
loop
exitwhen sfxwidth > 0.5*.width
//copy
set x = .sfxlength
set y = sfxwidth
//rotate
set x2 = x*cosA - y*sinA
set y2 = y*cosA + x*sinA
//translate
set x = x2 + .x
set y = y2 + .y
set str = AREA_SFX
if not IsPlayerAlly(GetLocalPlayer(), .owner) then
set str = ""
endif
//Create SFX
call DestroyEffect(AddSpecialEffect(AREA_SFX, x, y))
set sfxwidth = sfxwidth + .width/SFX_PER_WIDTH
endloop
set .sfxlength = .sfxlength + .length/SFX_PER_LENGTH
endif
endif
endif
endmethod
private static method pickAll takes nothing returns nothing
local thistype this = next[0]
loop
exitwhen this == 0
call .periodic()
set this = next[this]
endloop
endmethod
private static method onCast takes nothing returns boolean
local thistype this = allocate()
local unit caster = GetTriggerUnit()
local integer lvl = GetUnitAbilityLevel(caster, SPELL_ID)
set .owner = GetTriggerPlayer()
set .x = GetSpellTargetX()
set .y = GetSpellTargetY()
set .angle = Atan2(.y - GetUnitY(caster), .x - GetUnitX(caster))
set .cosA = Cos(.angle)
set .sinA = Sin(.angle)
//Level-dependent variables
set .dmgCollide = CollideDamage(lvl)
set .dmgExplode = ExplodeDamage(lvl)
set .radCollide = CollideRadius(lvl)
set .radExplode = ExplodeRadius(lvl)
set .duration = SpawnDuration(lvl)
set .spawnCtr = 0
set .spawnTime = 1.0/RobotsPerSecond(lvl)
set .width = SpellWidth(lvl)
set .length = SpellLength(lvl)
set .moveSpeed = RobotSpeed(lvl)*TIMEOUT
set .dx = .moveSpeed*.cosA
set .dy = .moveSpeed*.sinA
//robots group head node
set .head = robot.allocateHead()
static if not RANDOM_SPAWN then
set lastSpawnWidth = -.width
static if not SPAWN_PATTERN_NORMAL then
set spawnWidthIncreasing = true
endif
endif
//Area Effect
set sfxlength = -0.5*.length// - .length/SFX_PER_LENGTH
set sfxCtr = 0
//Insert in the list
set next[this] = 0
set prev[this] = prev[0]
set next[prev[this]] = this
set prev[0] = this
if prev[this] == 0 then
call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
endif
set caster = null
return false
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method condition takes nothing returns boolean
return (GetSpellAbilityId() == SPELL_ID and thistype.onCast() )
endmethod
endif
private static method onInit takes nothing returns nothing
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.condition))
set t = null
endif
endmethod
endstruct
endscope
//Even though the laboratory has since been sealed off,
//the ability to radio in robotic drones is still in
//working order.