Name | Type | is_array | initial_value |
//TESH.scrollpos=48
//TESH.alwaysfold=0
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
*/
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.
//TESH.scrollpos=3
//TESH.alwaysfold=0
library MissileRecycler initializer PreInit requires optional UnitIndexer, optional UnitDex, optional UnitIndexerGUI /*
MissileRecycler v 1.4.0.1
=========================================================================
Credits:
-------------------------------------------------------------------------
Written by Bribe
Vexorian, Anitarf and iNfraNe for the dummy.mdx model file
Nestharus for the Queue data structure and for finding that paused units
consume very few CPU resources.
=========================================================================
Introduction:
-------------------------------------------------------------------------
Recycling dummy units is important because the CreateUnit call is one of,
if not the, most processor-intensive native in the entire game. Creating
just a couple dozen dummy units in a single thread causes a visible frame
glitch for that instant. The overhead is even higher if you are using a
Unit Indexing library in the map which causes some extra evaluations per
new unit.
There are also reports of removed units leaving a little trail of RAM
surplus in their wake. I have not been able to reproduce this so I don't
know how serious it is.
I was motivated to create this system because removed units might be un-
safe in very large numbers and CreateUnit is a very heavy process.
The thing that makes this system different than others is the fact that
it considers the facing angle of the dummies being recycled, which I have
never seen another system even attempt before this. Since then,
MissileRecycler has inspired Anitarf to update XE with the same angle-retaining
capability and Nestharus has created Dummy. Considering the facing angle is
important because it takes 0.73 seconds for the unit to turn around,
which - when overlooked - looks especially weird if you are creating a unit-trail
or if you are shooting arrow-shaped objects as projectiles. For fireball effects or
effects that generally don't depend on facing angle, this system would be
a bit wasteful.
With default settings and the worst-case-scenario, it will take 0.09 seconds for
the projectile to turn to the angle you need. This is 1/8 of the normal worst case
scenario if you weren't recycling dummies considering facing. On average, it takes
roughly 0.045 seconds to turn to the angle you need (which is not noticable).
However, I have made this completely configurable and you are
able to change the values to whatever needs you have.
=========================================================================
Calibration Guide:
-------------------------------------------------------------------------
The thing that surprised me the most about this system was, no matter how
complex it turned out, it became very configurable. So I should let you
know what the constants do so you know if/how much you want to modify.
constant real DEATH_TIME = 2.0 //seconds
- Should not be less than the maximum time a death animation needs to play.
Should not be lower than .73 to ensure enough time to turn.
Should not be too high otherwise the dummies will take too long to recycle.
constant integer ANG_N = 8
- How many different angles are recognized by the system. Don't do
360 different angles because then you're going to have thousands of dummy
units stored and that's ridiculous, the game lags enough at 1000 units.
Increasing ANG_N increases realism but decreases the chance that a dummy
unit will be available to be recycled. I don't recommend making this any
lower, and the max I'd recommend would be 16.
constant integer ANG_STORAGE_MAX = 12
- How many dummy units are stored per angle. This limit is important
because you might have a spike at one point in the game where many units
are created, which could result in too high of a dummy population.
In general, I advise that the product of ANG_N x ANG_STORAGE_MAX does
not exceed 100 or 200. More than that is excessive, but you can
hypothetically have it up to 8190 if Warcraft 3's memory management
were better.
Preloads ANG_N x ANG_STORAGE_MAX dummy units. Preloading dummies is
useful as it dumps a lot of CreateUnit calls in initialization where you
won't see a frame glitch. In the 1.4 update, preloading is done 0 seconds
into the game to ensure any Indexers have already initialized.
private function ToggleIndexer takes boolean flag returns nothing
- Put what you need in here to disable/enable any indexer in your
map. if flag is true, enable indexer. If false, disable.
=========================================================================
API Guide:
-------------------------------------------------------------------------
You obviously need some functions so you can get a recycled dummy unit or
recycle it. Therefore I provide these:
function GetRecycledMissile
takes real x, real y, real z, real facing
returns unit
Returns a new dummy unit that acts as a projectile missile. The args
are simply the last three arguments you'd use for a CreateUnit call,
with the addition of a z parameter to represent the flying height -
it isn't the absolute z but relative to the ground because it uses
SetUnitFlyHeight on that value directly.
function RecycleMissile
takes unit u
returns nothing
When you are done with that dummy unit, recycle it via this function.
This function is pretty intelligent and resets that unit's animation
and its facing angle so you don't have to.
*/
//=======================================================================
// Save the map, then delete the exclaimation mark in the following line.
// Make sure that you don't have an object in your map with the rawcode
// 'dumi' and also configure the model path (war3mapImported\dummy.mdl)
// to the dummy.mdx model created by Vexorian.
//! external ObjectMerger w3u ewsp dumi unam "Missile Dummy" ubui "" uhom 1 ucol 0.01 umvt "None" umvr 1.00 utar "" uspa "" umdl "war3mapImported\dummy.mdl" umxr 0.00 umxp 0.00 ushr 0 uerd 0.00 udtm 0.00 ucbs 0.00 uble 0.00 uabi "Aloc,Amrf"
//Thanks to Vexorian that Optimizer 5.0 no longer kills natives
native UnitAlive takes unit id returns boolean
globals
//-------------------------------------------------------------------
// You must configure the dummy unit with the one created from the
// ObjectMerger statement above.
//
private constant integer DUMMY_ID = 'dumi' //The rawcode of the dummy unit.
private player OWNER = Player(15) //The owner of the dummy unit.
private constant integer ANG_N = 8 //# of indexed angles. Higher value increases realism but decreases recycle frequency.
private constant integer ANG_STORAGE_MAX = 20 //Max dummies per indexed angle. I recommend lowering this if you increase ANG_N.
private constant real DEATH_TIME = 2. //Allow the special effect on
//the unit to complete its "death" animation in this timeframe. Must
//be higher than 0.74 seconds to allow the unit time to turn. This
//number should not be lower than the maximum death-animation time of
//your missile-units' effect attachments, just to be safe.
endglobals
private function ToggleIndexer takes boolean flag returns nothing
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = flag
elseif LIBRARY_UnitIndexerGUI then
set udg_UnitIndexerEnabled = flag
elseif LIBRARY_UnitDex then
set UnitDex.Enabled = flag
endif
endfunction
globals
private constant integer ANG_VAL = 360 / ANG_N //Generate angle value from ANG_N.
private constant integer ANG_MID = ANG_VAL / 2 //The middle value of angle value.
//Misc vars
private unit array stack //Recycled dummy units.
private real array timeStamp //Prevents early recycling of units.
private integer array queueNext
private integer array queueLast
private integer recycle = 0
private timer gameTime = CreateTimer() //Used for visual continuity.
private integer array queueStack
private integer queueStackN = 0 //Used to avoid searching the queues.
endglobals
static if DEBUG_MODE then
private function Print takes string s returns nothing
//Un-comment this next line if you want to know how the system works:
//call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 999, "[MissileRecycler] " + s)
endfunction
endif
//=======================================================================
// Get a recycled dummy missile unit. If there are no recycled dummies
// that are already facing the angle you need, it creates a new dummy for
// you.
//
function GetRecycledMissile takes real x, real y, real z, real facing returns unit
local integer i = ModuloInteger(R2I(facing), 360) / ANG_VAL
local integer this = queueNext[i]
local unit u
if this != 0 and TimerGetElapsed(gameTime) >= timeStamp[this] then
//Dequeue this
set queueNext[i] = queueNext[this]
if queueNext[i] == 0 then
set queueLast[i] = i
endif
//Recycle this index
set queueLast[this] = recycle
set recycle = this
//Add queue index to available stack
set queueStack[queueStackN] = i
set queueStackN = queueStackN + 1
//Old unit will return as new
set u = stack[this]
call SetUnitFacing(u, facing)
call SetUnitUserData(u, 0)
//Reset the dummy's properties.
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitAnimationByIndex(u, 90)
call SetUnitScale(u, 1, 0, 0)
//call PauseUnit(u, false) -- you can disable "resets" that you don't need to worry about.
debug call Print("Recycling")
else
debug call Print("Creating new")
call ToggleIndexer(false)
set u = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call ToggleIndexer(true)
call PauseUnit(u, true)
endif
call SetUnitX(u, x)
call SetUnitY(u, y)
call SetUnitFlyHeight(u, z, 0)
set bj_lastCreatedUnit = u
set u = null
return bj_lastCreatedUnit
endfunction
//=======================================================================
// You should recycle the dummy missile unit when its job is done.
//
function RecycleMissile takes unit u returns nothing
local integer i
local integer this = recycle
if GetUnitTypeId(u) == DUMMY_ID and UnitAlive(u) and GetUnitUserData(u) != -1 then
if queueStackN == 0 then
debug call Print("Stack is full - removing surplus unit")
call UnitApplyTimedLife(u, 'BTLF', DEATH_TIME)
return
endif
//Recycle this
set recycle = queueLast[this]
//Index the dummy unit to an available facing angle.
//Get the last vacant angle index.
set queueStackN = queueStackN - 1
set i = queueStack[queueStackN]
//Enqueue this
set queueNext[queueLast[i]] = this
set queueLast[i] = this
set queueNext[this] = 0
//Allow a time barrier for the effect to destroy/turn to complete.
set timeStamp[this] = TimerGetElapsed(gameTime) + DEATH_TIME
set stack[this] = u
call SetUnitFacing(u, i * ANG_VAL + ANG_MID)
//Prevent double-free of this unit.
call SetUnitUserData(u, -1)
debug else
debug call BJDebugMsg("[MissileRecycler] Error: Attempt to recycle invalid unit.")
endif
endfunction
//=======================================================================
// I didn't need this function after all
//
function RecycleMissileDelayed takes unit u, real r returns nothing
call RecycleMissile(u)
endfunction
//=======================================================================
// Map the dummy units to their facing angles (map below is if ANG_N is
// 4 and ANG_STORAGE_MAX is 3).
//
// angle[0] (0) - [4] [5] [6]
// angle[1] (90) - [7] [8] [9]
// angle[2] (180) - [10][11][12]
// angle[3] (270) - [13][14][15]
//
private function Init takes nothing returns nothing
local integer end
local integer i = ANG_N
local integer n = i
local integer angle
local real x = GetRectMaxX(bj_mapInitialPlayableArea)
local real y = GetRectMaxY(bj_mapInitialPlayableArea)
local unit u
call ToggleIndexer(false)
loop
set i = i - 1
set queueNext[i] = n
set angle = i * ANG_VAL + ANG_MID
set end = n + ANG_STORAGE_MAX
set queueLast[i] = end - 1
loop
set queueNext[n] = n + 1
set u = CreateUnit(OWNER, DUMMY_ID, x, y, angle)
set stack[n] = u
call PauseUnit(u, true)
call SetUnitUserData(u, -1)
set n = n + 1
exitwhen n == end
endloop
set queueNext[n - 1] = 0
exitwhen i == 0
endloop
call ToggleIndexer(true)
call TimerStart(gameTime, 1000000., false, null)
set u = null
endfunction
private function PreInit takes nothing returns nothing
static if LIBRARY_UnitIndexerGUI then
call OnUnitIndexerInitialized(function Init)
else
call Init()
endif
endfunction
endlibrary
//TESH.scrollpos=16
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
/*
============= Why this is important? =================
1. Does not generate 16 events per spell.
2. This uses one trigger evaluation instead of one for each
individual spell (some maps have quite a few). This helps keep
framerate high and fluid when a spell is cast.
*/
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary